Если вместо «Хлеб» в консоли появляется «РҐР»РµР±» или скрипт падает с UnicodeDecodeError, причина одна и та же: файл записан в одной кодировке, а Python читает его в другой. Починка — один аргумент функции open(): encoding="cp1251" для выгрузок из Excel и 1С, encoding="utf-8" для всего остального.
Ниже — почему одна причина даёт то молчаливый мусор, то исключение, как дочитать сообщение об ошибке до конца и что делать, если кодировку файла никто уже не помнит.
Кракозябры и UnicodeDecodeError — это одна и та же ошибка?
Да, корень у них общий: Python предположил не ту кодировку. А симптом зависит только от того, оказались ли байты файла допустимыми в предполагаемом кодеке. Однобайтовые кодировки вроде cp1251 и koi8-r назначают символ почти каждому из 256 возможных байтов, поэтому падают они крайне редко — почти всегда они молча выдают мусор. UTF-8 устроен строже: в нём есть запрещённые сочетания байтов, и на первом же таком Python возбуждает исключение.
Вот файл в UTF-8, прочитанный как cp1251, — ни ошибки, ни предупреждения:
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("Хлеб;45.90")
with open("notes.txt", encoding="cp1251") as f:
print(f.read())
Хлеб;45.90
А вот обратное направление: файл в cp1251, прочитанный как UTF-8. Этот код сломан намеренно — он падает:
with open("prices.txt", "w", encoding="cp1251") as f:
f.write("Хлеб;45.90\nМолоко;89.50\nСыр;349.00\n")
with open("prices.txt", encoding="utf-8") as f:
text = f.read()
print(text)
Traceback (most recent call last):
File "read_prices.py", line 5, in <module>
text = f.read()
^^^^^^^^
File "<frozen codecs>", line 322, in decode
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd5 in position 0: invalid continuation byte
Вывод неочевидный: падение — это удача. Оно останавливает программу сразу. А кракозябры спокойно доезжают до базы, отчёта и клиента.
Откуда берутся в Python кракозябры при чтении файла?
Из строки, в которой ты ничего не написал. Вызов open("prices.txt") без аргумента encoding не означает «UTF-8» — он означает «возьми кодировку, которую предпочитает система». На русской Windows это cp1251, на Linux и macOS — обычно UTF-8. Поэтому один и тот же скрипт у автора на макбуке работает, а у коллеги на рабочей Windows показывает вместо русских букв символы при чтении txt-файла: код одинаковый, предположение разное.
Какую кодировку использует open() по умолчанию?
Фиксированной кодировки по умолчанию у open() нет. В текстовом режиме он берёт locale.getpreferredencoding(False) — значение, которое зависит от операционной системы и региональных настроек. Именно поэтому фраза «у меня работает» здесь ничего не доказывает: она описывает локаль автора, а не файл.
Что предпочитает конкретная машина, видно в интерактивном режиме. На русской Windows это выглядит так:
>>> import locale
>>> locale.getpreferredencoding(False)
'cp1251'
Снять зависимость от локали можно двумя способами:
| Способ |
Что делает |
Когда применять |
encoding="utf-8" в каждом open() |
Убирает догадку в конкретном месте |
Всегда, это основной рабочий приём |
PYTHONUTF8=1 или запуск python -X utf8 script.py |
Включает режим UTF-8 на весь процесс |
Чужой скрипт, который не хочется править |
Первый способ надёжнее: он едет вместе с кодом, а переменная окружения остаётся на одной машине. Открыть файл в кодировке cp1251 можно тем же способом — open(path, encoding="cp1251"); базовые формы чтения и записи собраны в справочнике: чтение и запись текстовых файлов.
Читаем сообщение об ошибке: байт, позиция, кодек
В строке UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd5 in position 0: invalid continuation byte четыре факта, и полезны все четыре:
'utf-8' codec — кодек, в котором Python пытался читать. Это твоё предположение, а не свойство файла.
byte 0xd5 — байт, на котором всё сломалось. 0xD5 — это буква «Х» в cp1251.
in position 0 — смещение от начала прочитанного блока. Позиция 0 значит, что файл «не тот» с самого первого символа, а не в одной случайной строке посередине.
invalid continuation byte — в UTF-8 такой байт обязан начинать многобайтовую последовательность и получить продолжение вида 10xxxxxx, а получил что-то другое.
Те же данные достаются из объекта исключения — это удобно, когда файлов много и падение надо залогировать, а не разглядывать:
with open("prices.txt", "w", encoding="cp1251") as f:
f.write("Хлеб;45.90")
try:
with open("prices.txt", encoding="utf-8") as f:
text = f.read()
except UnicodeDecodeError as e:
print("кодек: ", e.encoding)
print("позиция:", e.start)
print("байт: ", hex(e.object[e.start]))
print("причина:", e.reason)
кодек: utf-8
позиция: 0
байт: 0xd5
причина: invalid continuation byte
Ловить нужно именно UnicodeDecodeError, а не голый except без типа: иначе в ту же ветку попадёт и опечатка в имени файла. Разбор приёма — в карточке перехват конкретного исключения.
cp1251, koi8-r и UTF-8: как отличить по симптому
Кракозябры не случайны. Каждой паре «реальная кодировка → предполагаемая» соответствует свой узнаваемый почерк, и по нему кодировку файла часто можно назвать, ничего не запуская. Слово «Хлеб» выглядит так:
| Файл записан в |
Читаем как |
Что видно |
| UTF-8 |
cp1251 |
Хлеб |
| UTF-8 |
koi8-r |
п╔п╩п╣п╠ |
| koi8-r |
cp1251 |
иМЕВ |
| cp1251 |
UTF-8 |
падение с UnicodeDecodeError |
| cp1251 |
cp1251 |
Хлеб |
Заглавные «Р» и «С», между которыми стоит по одному постороннему знаку, — это почти всегда UTF-8, прочитанный как cp1251: у кириллицы в UTF-8 первый байт равен 0xD0 или 0xD1, а в cp1251 это ровно «Р» и «С». Псевдографика вида п╔п╩ — UTF-8, прочитанный как koi8-r. Осмысленные, но неправильные русские буквы («иМЕВ» вместо «Хлеб») — koi8-r, прочитанный как cp1251.
errors='replace' при чтении файла: диагностика, а не лечение
Аргумент errors="replace" заставляет open() не падать, а подставлять символ � вместо каждого куска байтов, который не удалось декодировать (для cp1251-текста, прочитанного как UTF-8, это ровно один символ на букву). Это не починка, а способ увидеть, где именно ломается файл:
with open("prices.txt", "w", encoding="cp1251") as f:
f.write("Хлеб;45.90\nМолоко;89.50\nСыр;349.00")
with open("prices.txt", encoding="utf-8", errors="replace") as f:
print(f.read())
����;45.90
������;89.50
���;349.00
Картинка сразу отвечает на главный вопрос: сломаны только буквы, а цифры, точки и точки с запятой целы. Значит, файл не битый — он просто не в UTF-8, и структура в нём та, которую ты ждал. Если бы «поплыл» весь файл целиком, включая цифры, это был бы не текст, а, например, xlsx или архив. Оставлять errors="replace" в рабочем коде нельзя: замещённые символы уже не восстановить. Как работают кодеки и ручной bytes.decode() — в карточке кодирование и декодирование текста.
Чем encoding='utf-8-sig' отличается от 'utf-8'?
utf-8-sig — это тот же UTF-8, только он умеет отбросить BOM: три служебных байта EF BB BF в самом начале файла. Их дописывает Excel, когда сохраняет «CSV UTF-8», и часть редакторов на Windows. Обычный кодек utf-8 такой файл прочитает без ошибки, но первым символом окажется невидимый \ufeff, и сломается всё, что зависит от точного совпадения строк.
with open("prices.txt", "w", encoding="utf-8-sig") as f:
f.write("45.90\n89.50")
with open("prices.txt", encoding="utf-8") as f:
first = f.readline().strip()
print(repr(first))
try:
print(float(first))
except ValueError as e:
print("ValueError:", e)
'\ufeff45.90'
ValueError: could not convert string to float: '\ufeff45.90'
Так BOM превращается в загадочную ошибку ровно в первой строке файла и ни в одной другой; остальные причины той же ошибки разобраны в статье про could not convert string to float. Лечение — encoding="utf-8-sig": он снимает BOM, если тот есть, и ничего не портит, если его нет. Для чтения чужих CSV это разумный вариант по умолчанию.
Как отдать CSV, чтобы Excel не показал кракозябры?
Здесь всё зеркально. Excel на Windows открывает CSV в системной кодировке и показывает честный UTF-8 как кракозябры до тех пор, пока не увидит BOM. Поэтому для файла, который пойдёт в Excel, нужна связка encoding="utf-8-sig" при записи, а не encoding="utf-8" — иначе в таблице появится та же «РҐР»РµР±».
import csv
rows = [["товар", "цена"], ["Хлеб", "45.90"], ["Молоко", "89.50"]]
with open("report.csv", "w", encoding="utf-8-sig", newline="") as f:
csv.writer(f, delimiter=";").writerows(rows)
print(open("report.csv", "rb").read(3))
b'\xef\xbb\xbf'
Три байта в начале — тот самый BOM. Аргумент newline="" для модуля csv обязателен: без него на Windows в файле удвоятся переводы строк.
Как определить кодировку файла, если её никто не знает?
Надёжного способа определить кодировку по содержимому не существует: одни и те же байты являются валидным текстом сразу в нескольких однобайтовых кодировках, и файл не хранит никакой пометки о себе. Работающий приём — перебрать короткий список кандидатов и посмотреть на результат глазами. Для русских файлов почти всё покрывают четыре: utf-8-sig, cp1251, koi8-r, cp866.
from pathlib import Path
Path("prices.txt").write_bytes("Хлеб;45.90".encode("cp1251"))
raw = Path("prices.txt").read_bytes()
for codec in ("utf-8-sig", "cp1251", "koi8-r"):
try:
text = raw.decode(codec)
except UnicodeDecodeError:
print(f"{codec:10} падает")
else:
print(f"{codec:10} {text}")
utf-8-sig падает
cp1251 Хлеб;45.90
koi8-r уКЕА;45.90
Обрати внимание на koi8-r: он не упал и выдал «уКЕА». Однобайтовые кодеки почти никогда не падают: в koi8-r и cp866 определены все 256 байтов, в cp1251 не определён ровно один — 0x98. Поэтому «перебирать, пока не перестанет ломаться» — негодный критерий: так отбраковывается практически только UTF-8. Выбор делает человек, который смотрит на строку. Автоматически задачу решают внешние библиотеки charset-normalizer и chardet; в стандартную библиотеку они не входят и дают вероятностный ответ, а не гарантию.
Правило одной строки: всегда указывай encoding явно
Пиши encoding в каждом open(), который открывает текст, — даже когда «и так работает». Это одна строка кода, которая убирает целый класс багов, воспроизводящихся только на чужой машине. Именно так и закрываются в Python кракозябры при чтении файла: не разовым пересохранением в редакторе, а явным аргументом. Для своих файлов это utf-8, для чужих CSV — utf-8-sig, для выгрузок из 1С и старого Excel — cp1251.
Симметричная ручка есть и на записи. Если ты сохраняешь словарь через json.dump() и видишь в файле \u0425\u043b\u0435\u0431 вместо «Хлеб», то кодировка тут ни при чём — за это отвечает ensure_ascii, и разобрано это в статье JSON: русские буквы превратились в коды.
Частые ошибки
- Вызывать
open() без encoding. Самая частая причина всей проблемы. Фикс: open(path, encoding="utf-8") везде, где читается текст.
- Считать, что файл «битый», и просить прислать заново. Пришлют такой же. Фикс: прочитать первые байты в режиме
"rb" и посмотреть, \xd5 там или \xd0\xa5.
- Оставлять
errors="replace" в рабочем коде. Программа перестанет падать, но данные потеряются молча. Фикс: использовать replace только для разведки, а потом поставить правильный encoding.
- Пересохранять файл в редакторе вместо правки кода. Помогает ровно один раз: следующая выгрузка придёт в cp1251 снова. Фикс: чинить в коде.
- Читать «CSV UTF-8» из Excel как
utf-8. Ошибки не будет, но первый заголовок перестанет сравниваться с чем угодно. Фикс: encoding="utf-8-sig".
- Путать чтение и запись.
UnicodeDecodeError возникает при чтении, а UnicodeEncodeError (например, 'charmap' codec can't encode character) — при записи и при печати в консоль Windows.
- Ставить
except Exception вокруг всего чтения. В одну ветку попадут FileNotFoundError, PermissionError и опечатка в пути. Фикс: ловить UnicodeDecodeError отдельно.
Практика: закрепить в браузере
Кодировку саму по себе не потренируешь, но всё, что начинается сразу после f.read(), — это обычный разбор строк: разделить, почистить, собрать словарь. Именно на этом шаге чаще всего и всплывают невидимые символы вроде BOM и неразрывного пробела. Ломают они, кстати, не только данные, но и сам исходник: перемешанные в отступах табы и пробелы дают TabError, и ловится он тем же repr().
Имена файлов из той же выгрузки обычно приходят с расширением, и срезать его тоже надо аккуратно: rstrip(".txt") съедает лишние буквы — это разобрано в статье про удаление расширения.
Мини-резюме
- В Python кракозябры при чтении файла и
UnicodeDecodeError — один баг: интерпретатор предположил не ту кодировку.
- Однобайтовые кодеки (cp1251, koi8-r) почти никогда не падают и выдают мусор молча; UTF-8 падает, и это удобнее.
- У
open() нет фиксированной кодировки по умолчанию: он берёт её из локали, поэтому Windows и Linux ведут себя по-разному.
- В сообщении об ошибке важны все четыре части: кодек, байт, позиция, причина.
errors="replace" — способ увидеть поломку, а не вылечить её.
- BOM из Excel снимается кодировкой
utf-8-sig; она же нужна при записи CSV, который откроют в Excel.
- Определить кодировку по содержимому со стопроцентной точностью нельзя — перебирай кандидатов и смотри на результат.
Если вместо «Хлеб» в консоли появляется «РҐР»РµР±» или скрипт падает с
UnicodeDecodeError, причина одна и та же: файл записан в одной кодировке, а Python читает его в другой. Починка — один аргумент функцииopen():encoding="cp1251"для выгрузок из Excel и 1С,encoding="utf-8"для всего остального.Ниже — почему одна причина даёт то молчаливый мусор, то исключение, как дочитать сообщение об ошибке до конца и что делать, если кодировку файла никто уже не помнит.
Кракозябры и UnicodeDecodeError — это одна и та же ошибка?
Да, корень у них общий: Python предположил не ту кодировку. А симптом зависит только от того, оказались ли байты файла допустимыми в предполагаемом кодеке. Однобайтовые кодировки вроде cp1251 и koi8-r назначают символ почти каждому из 256 возможных байтов, поэтому падают они крайне редко — почти всегда они молча выдают мусор. UTF-8 устроен строже: в нём есть запрещённые сочетания байтов, и на первом же таком Python возбуждает исключение.
Вот файл в UTF-8, прочитанный как cp1251, — ни ошибки, ни предупреждения:
with open("notes.txt", "w", encoding="utf-8") as f: f.write("Хлеб;45.90") with open("notes.txt", encoding="cp1251") as f: print(f.read())А вот обратное направление: файл в cp1251, прочитанный как UTF-8. Этот код сломан намеренно — он падает:
with open("prices.txt", "w", encoding="cp1251") as f: f.write("Хлеб;45.90\nМолоко;89.50\nСыр;349.00\n") with open("prices.txt", encoding="utf-8") as f: text = f.read() print(text)Вывод неочевидный: падение — это удача. Оно останавливает программу сразу. А кракозябры спокойно доезжают до базы, отчёта и клиента.
Откуда берутся в Python кракозябры при чтении файла?
Из строки, в которой ты ничего не написал. Вызов
open("prices.txt")без аргументаencodingне означает «UTF-8» — он означает «возьми кодировку, которую предпочитает система». На русской Windows это cp1251, на Linux и macOS — обычно UTF-8. Поэтому один и тот же скрипт у автора на макбуке работает, а у коллеги на рабочей Windows показывает вместо русских букв символы при чтении txt-файла: код одинаковый, предположение разное.Какую кодировку использует open() по умолчанию?
Фиксированной кодировки по умолчанию у
open()нет. В текстовом режиме он берётlocale.getpreferredencoding(False)— значение, которое зависит от операционной системы и региональных настроек. Именно поэтому фраза «у меня работает» здесь ничего не доказывает: она описывает локаль автора, а не файл.Что предпочитает конкретная машина, видно в интерактивном режиме. На русской Windows это выглядит так:
Снять зависимость от локали можно двумя способами:
encoding="utf-8"в каждомopen()PYTHONUTF8=1или запускpython -X utf8 script.pyПервый способ надёжнее: он едет вместе с кодом, а переменная окружения остаётся на одной машине. Открыть файл в кодировке cp1251 можно тем же способом —
open(path, encoding="cp1251"); базовые формы чтения и записи собраны в справочнике: чтение и запись текстовых файлов.Читаем сообщение об ошибке: байт, позиция, кодек
В строке
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd5 in position 0: invalid continuation byteчетыре факта, и полезны все четыре:'utf-8' codec— кодек, в котором Python пытался читать. Это твоё предположение, а не свойство файла.byte 0xd5— байт, на котором всё сломалось. 0xD5 — это буква «Х» в cp1251.in position 0— смещение от начала прочитанного блока. Позиция 0 значит, что файл «не тот» с самого первого символа, а не в одной случайной строке посередине.invalid continuation byte— в UTF-8 такой байт обязан начинать многобайтовую последовательность и получить продолжение вида10xxxxxx, а получил что-то другое.Те же данные достаются из объекта исключения — это удобно, когда файлов много и падение надо залогировать, а не разглядывать:
with open("prices.txt", "w", encoding="cp1251") as f: f.write("Хлеб;45.90") try: with open("prices.txt", encoding="utf-8") as f: text = f.read() except UnicodeDecodeError as e: print("кодек: ", e.encoding) print("позиция:", e.start) print("байт: ", hex(e.object[e.start])) print("причина:", e.reason)Ловить нужно именно
UnicodeDecodeError, а не голыйexceptбез типа: иначе в ту же ветку попадёт и опечатка в имени файла. Разбор приёма — в карточке перехват конкретного исключения.cp1251, koi8-r и UTF-8: как отличить по симптому
Кракозябры не случайны. Каждой паре «реальная кодировка → предполагаемая» соответствует свой узнаваемый почерк, и по нему кодировку файла часто можно назвать, ничего не запуская. Слово «Хлеб» выглядит так:
Хлебп╔п╩п╣п╠иМЕВUnicodeDecodeErrorХлебЗаглавные «Р» и «С», между которыми стоит по одному постороннему знаку, — это почти всегда UTF-8, прочитанный как cp1251: у кириллицы в UTF-8 первый байт равен 0xD0 или 0xD1, а в cp1251 это ровно «Р» и «С». Псевдографика вида
п╔п╩— UTF-8, прочитанный как koi8-r. Осмысленные, но неправильные русские буквы («иМЕВ» вместо «Хлеб») — koi8-r, прочитанный как cp1251.errors='replace' при чтении файла: диагностика, а не лечение
Аргумент
errors="replace"заставляетopen()не падать, а подставлять символ�вместо каждого куска байтов, который не удалось декодировать (для cp1251-текста, прочитанного как UTF-8, это ровно один символ на букву). Это не починка, а способ увидеть, где именно ломается файл:with open("prices.txt", "w", encoding="cp1251") as f: f.write("Хлеб;45.90\nМолоко;89.50\nСыр;349.00") with open("prices.txt", encoding="utf-8", errors="replace") as f: print(f.read())Картинка сразу отвечает на главный вопрос: сломаны только буквы, а цифры, точки и точки с запятой целы. Значит, файл не битый — он просто не в UTF-8, и структура в нём та, которую ты ждал. Если бы «поплыл» весь файл целиком, включая цифры, это был бы не текст, а, например, xlsx или архив. Оставлять
errors="replace"в рабочем коде нельзя: замещённые символы уже не восстановить. Как работают кодеки и ручнойbytes.decode()— в карточке кодирование и декодирование текста.Чем encoding='utf-8-sig' отличается от 'utf-8'?
utf-8-sig— это тот же UTF-8, только он умеет отбросить BOM: три служебных байтаEF BB BFв самом начале файла. Их дописывает Excel, когда сохраняет «CSV UTF-8», и часть редакторов на Windows. Обычный кодекutf-8такой файл прочитает без ошибки, но первым символом окажется невидимый\ufeff, и сломается всё, что зависит от точного совпадения строк.with open("prices.txt", "w", encoding="utf-8-sig") as f: f.write("45.90\n89.50") with open("prices.txt", encoding="utf-8") as f: first = f.readline().strip() print(repr(first)) try: print(float(first)) except ValueError as e: print("ValueError:", e)Так BOM превращается в загадочную ошибку ровно в первой строке файла и ни в одной другой; остальные причины той же ошибки разобраны в статье про could not convert string to float. Лечение —
encoding="utf-8-sig": он снимает BOM, если тот есть, и ничего не портит, если его нет. Для чтения чужих CSV это разумный вариант по умолчанию.Как отдать CSV, чтобы Excel не показал кракозябры?
Здесь всё зеркально. Excel на Windows открывает CSV в системной кодировке и показывает честный UTF-8 как кракозябры до тех пор, пока не увидит BOM. Поэтому для файла, который пойдёт в Excel, нужна связка
encoding="utf-8-sig"при записи, а неencoding="utf-8"— иначе в таблице появится та же «РҐР»РµР±».import csv rows = [["товар", "цена"], ["Хлеб", "45.90"], ["Молоко", "89.50"]] with open("report.csv", "w", encoding="utf-8-sig", newline="") as f: csv.writer(f, delimiter=";").writerows(rows) print(open("report.csv", "rb").read(3))Три байта в начале — тот самый BOM. Аргумент
newline=""для модуляcsvобязателен: без него на Windows в файле удвоятся переводы строк.Как определить кодировку файла, если её никто не знает?
Надёжного способа определить кодировку по содержимому не существует: одни и те же байты являются валидным текстом сразу в нескольких однобайтовых кодировках, и файл не хранит никакой пометки о себе. Работающий приём — перебрать короткий список кандидатов и посмотреть на результат глазами. Для русских файлов почти всё покрывают четыре:
utf-8-sig,cp1251,koi8-r,cp866.from pathlib import Path Path("prices.txt").write_bytes("Хлеб;45.90".encode("cp1251")) raw = Path("prices.txt").read_bytes() for codec in ("utf-8-sig", "cp1251", "koi8-r"): try: text = raw.decode(codec) except UnicodeDecodeError: print(f"{codec:10} падает") else: print(f"{codec:10} {text}")Обрати внимание на koi8-r: он не упал и выдал «уКЕА». Однобайтовые кодеки почти никогда не падают: в koi8-r и cp866 определены все 256 байтов, в cp1251 не определён ровно один —
0x98. Поэтому «перебирать, пока не перестанет ломаться» — негодный критерий: так отбраковывается практически только UTF-8. Выбор делает человек, который смотрит на строку. Автоматически задачу решают внешние библиотекиcharset-normalizerиchardet; в стандартную библиотеку они не входят и дают вероятностный ответ, а не гарантию.Правило одной строки: всегда указывай encoding явно
Пиши
encodingв каждомopen(), который открывает текст, — даже когда «и так работает». Это одна строка кода, которая убирает целый класс багов, воспроизводящихся только на чужой машине. Именно так и закрываются в Python кракозябры при чтении файла: не разовым пересохранением в редакторе, а явным аргументом. Для своих файлов этоutf-8, для чужих CSV —utf-8-sig, для выгрузок из 1С и старого Excel —cp1251.Симметричная ручка есть и на записи. Если ты сохраняешь словарь через
json.dump()и видишь в файле\u0425\u043b\u0435\u0431вместо «Хлеб», то кодировка тут ни при чём — за это отвечаетensure_ascii, и разобрано это в статье JSON: русские буквы превратились в коды.Частые ошибки
open()безencoding. Самая частая причина всей проблемы. Фикс:open(path, encoding="utf-8")везде, где читается текст."rb"и посмотреть,\xd5там или\xd0\xa5.errors="replace"в рабочем коде. Программа перестанет падать, но данные потеряются молча. Фикс: использоватьreplaceтолько для разведки, а потом поставить правильныйencoding.utf-8. Ошибки не будет, но первый заголовок перестанет сравниваться с чем угодно. Фикс:encoding="utf-8-sig".UnicodeDecodeErrorвозникает при чтении, аUnicodeEncodeError(например,'charmap' codec can't encode character) — при записи и при печати в консоль Windows.except Exceptionвокруг всего чтения. В одну ветку попадутFileNotFoundError,PermissionErrorи опечатка в пути. Фикс: ловитьUnicodeDecodeErrorотдельно.Практика: закрепить в браузере
Кодировку саму по себе не потренируешь, но всё, что начинается сразу после
f.read(), — это обычный разбор строк: разделить, почистить, собрать словарь. Именно на этом шаге чаще всего и всплывают невидимые символы вроде BOM и неразрывного пробела. Ломают они, кстати, не только данные, но и сам исходник: перемешанные в отступах табы и пробелы дают TabError, и ловится он тем жеrepr().ключ=значениев словарь, ровно как при чтении конфига из файла.Имена файлов из той же выгрузки обычно приходят с расширением, и срезать его тоже надо аккуратно:
rstrip(".txt")съедает лишние буквы — это разобрано в статье про удаление расширения.Мини-резюме
UnicodeDecodeError— один баг: интерпретатор предположил не ту кодировку.open()нет фиксированной кодировки по умолчанию: он берёт её из локали, поэтому Windows и Linux ведут себя по-разному.errors="replace"— способ увидеть поломку, а не вылечить её.utf-8-sig; она же нужна при записи CSV, который откроют в Excel.