Выгрузка в двух кодировках сразу
Каждый раз, когда кажется, что тема кодировок закрыта, приходит выгрузка, где половина колонок в UTF-8, а половина в CP1251. В одном файле. Так бывает, когда его собирали конкатенацией из двух источников.
Сначала посмотреть, потом читать
Главная ошибка — сразу пытаться прочитать файл в надежде угадать кодировку. Быстрее посмотреть на байты:
file -i data.csv
head -c 2000 data.csv | hexdump -C | head -20
Если в начале ef bb bf — это BOM, файл почти наверняка UTF-8,
и его сделал Excel. Если вместо русских букв идут одиночные байты в диапазоне
c0–ff — это CP1251. Пары вида d0 xx —
уже UTF-8.
Когда кодировки смешаны
Тут не помогает ни один флаг чтения. Что сработало — читать построчно в байтах и решать по каждой строке:
def decode_line(raw: bytes) -> str:
try:
return raw.decode('utf-8')
except UnicodeDecodeError:
# не UTF-8 — значит однобайтовая; для наших данных это CP1251
return raw.decode('cp1251', errors='replace')
with open('data.csv', 'rb') as f:
rows = [decode_line(line) for line in f]
Важно именно так, а не наоборот: CP1251 декодирует почти любые байты без ошибки, поэтому если начать с неё, UTF-8 превратится в «Ð¿Ñ€Ð¸Ð²ÐµÑ‚» молча. UTF-8 же строгий и честно падает на чужих байтах — этим и пользуемся.
Что делать, чтобы не повторялось
Договориться с источником об UTF-8 без BOM — идеальный вариант, но он редко достижим, если выгрузку делает чужая система. Реалистичнее — проверка на входе пайплайна: считать долю строк, не декодируемых в UTF-8, и падать, если она выросла. Тихая порча данных хуже упавшего джоба.
И не открывать такие файлы в Excel «просто посмотреть». Он сохранит обратно в своей кодировке, и вы получите третий вариант.