Unicode и UTF-8: как работают кодировки и откуда «кракозябры»
Практика3 минРедакция Innovialabs
Коротко
Unicode присваивает каждому символу номер, а UTF-8 превращает этот номер в 1–4 байта. «Кракозябры» появляются, когда байты записали в одной кодировке, а прочитали в другой, — поэтому везде явно указывайте UTF-8.
Коротко: откуда берутся «кракозябры»
Компьютер хранит не буквы, а байты. Кодировка — правило, по которому байты превращаются в символы. Если текст сохранили в одной кодировке, а открыли в другой, вместо «Привет» вы увидите «РџСЂРёРІРµС‚».
Решение почти всегда одно: использовать UTF-8 везде и явно указывать кодировку при чтении и записи.
Unicode, кодовые точки и UTF-8
- Unicode — большая таблица, где у каждого символа есть номер, кодовая точка. Кириллическая «А» —
U+0410, латинскаяa—U+0061, узбекский знак ‘ в o‘ —U+2018. - UTF-8 — способ записать кодовую точку байтами. Латиница занимает 1 байт, кириллица — 2, многие знаки препинания и символы — 3, эмодзи — 4.
| Символ | Кодовая точка | Байты UTF-8 |
|---|---|---|
a | U+0061 | 61 |
А (кириллица) | U+0410 | D0 90 |
‘ | U+2018 | E2 80 98 |
Windows-1251 — старая однобайтная кодировка для кириллицы. В ней «А» — это один байт C0. Она до сих пор встречается в выгрузках из старых систем, CSV из Excel и устаревших базах.
Как появляется искажение
Возьмём «П»: в UTF-8 это байты D0 9F. Программа, считающая файл Windows-1251, видит два отдельных символа: D0 — «Р», 9F — «џ». Отсюда «Рџ» в начале испорченного слова.
Обратная ситуация: файл в Windows-1251 открыли как UTF-8. Такие байты не образуют корректных последовательностей, и вместо текста появляются знаки �.
Как исправить на практике
Читайте и пишите файлы с явной кодировкой:
with open("data.csv", encoding="utf-8") as f:
text = f.read()
Конвертируйте старые файлы:
iconv -f WINDOWS-1251 -t UTF-8 old.csv > new.csv
Почините уже испорченную строку, если знаете, как она сломалась:
broken = "Привет"
fixed = broken.encode("cp1251").decode("utf-8") # "Привет"
Проверьте каждый слой:
- HTML:
<meta charset="utf-8">и заголовокContent-Type: text/html; charset=utf-8. - MySQL: кодировка
utf8mb4, а неutf8— последняя хранит максимум 3 байта и не принимает эмодзи. - PostgreSQL: база в кодировке
UTF8. - CSV для Excel: сохраняйте в UTF-8 с BOM (
encoding="utf-8-sig"в Python), иначе Excel может открыть кириллицу неправильно. - Редактор кода и Git: файлы в UTF-8.
Узбекские o‘ и g‘: отдельная ловушка
Апостроф в o‘, g‘ и в словах вроде ma’lumot можно набрать разными символами: ' (U+0027), ` (U+0060), ‘ (U+2018), ’ (U+2019), ʻ (U+02BB). Внешне они похожи, но для компьютера это разные символы. Последствия:
- поиск по «o‘zbek» не находит «o'zbek»;
- дубли в справочниках, клиентах, адресах;
- URL и slug получаются разными.
Что делать: выберите один вариант для проекта и нормализуйте ввод при сохранении — заменяйте остальные варианты на выбранный. Для поиска храните дополнительное нормализованное поле.
Ещё одна деталь: длина строки. "o‘" — это 2 кодовые точки, но 4 байта в UTF-8. Если поле в базе ограничено байтами, а не символами, текст может обрезаться посреди символа.
Нормализация Unicode
Некоторые буквы можно записать двумя способами: «й» — одной кодовой точкой или «и» плюс комбинируемый знак. Выглядит одинаково, а сравнение строк даёт False. Приводите текст к одной форме:
import unicodedata
clean = unicodedata.normalize("NFC", text)