Перед вами файл, в котором первый столбец как будто называется id, но обращение к строке по ключу id не срабатывает. Одна из возможных причин: начальная подпись UTF-8, которая после обычного декодирования осталась символом U+FEFF в первом имени. Задача здесь узкая: проверить эту гипотезу на известном учебном файле, сохранить две записи и не вычищать похожие символы по всему тексту.

В вымышленном CSV есть столбцы id и score, записи R01 и R02 и условные значения 7 и 9. Это учебные числа, а не наблюдения реальных участников. Байты собраны прямо в коде, поэтому пример воспроизводится без внешнего файла. Код выполнен в Python 3.12.14 без дополнительных пакетов; реальный интерфейс табличной программы не тестировался.

Что делает utf-8-sig

По документации кодека utf_8_sig, при декодировании необязательная подпись UTF-8 в начале данных пропускается. В нашем примере первые три байта — EF BB BF. Обычный utf-8 декодирует их в U+FEFF; вариант utf-8-sig обрабатывает начальную подпись. Для UTF-8 она не выбирает порядок байтов, как можно подумать по названию BOM.

utf-8-sig не угадывает неизвестную кодировку. Отсутствие подписи не доказывает, что перед вами UTF-8; здесь кодировка известна по условию. По умолчанию некорректные последовательности UTF-8 вызывают ошибку, а не исчезают молча. Не добавляйте errors="ignore" только ради успешного чтения, если данные важно сохранить.

Путь чтения   Что получено в учебном примере
Декодирование utf-8   Первый ключ содержит U+FEFF перед id
Декодирование utf-8-sig   Имена ровно id и score
Число записей после csv.DictReader   2 записи, ID R01 и R02 сохранены
Сумма score для этих строк   7 + 9 = 16
Авторская схема на учебных данных, не интерфейс. BOM перед первым заголовком. Начало UTF-8 файла EF BB BF + id,score Далее две учебные записи: R01 и R02. Обычный utf-8 Первый ключ: \ufeffid Невидимый символ остаётся в заголовке. Кодек utf-8-sig Первый ключ: id 2 записи; score: 7 + 9 = 16. Убирается начальная подпись. Символ внутри текста остаётся.
Авторская схема на учебных данных, не интерфейс. BOM перед первым заголовком. Начало UTF-8 файла EF BB BF + id,score Далее две учебные записи: R01 и R02. Обычный utf-8 Первый ключ: \ufeffid Невидимый символ остаётся в заголовке. Кодек utf-8-sig Первый ключ: id 2 записи; score: 7 + 9 = 16. Убирается начальная подпись. Символ внутри текста остаётся.

Законченный пример: байты, имена и две записи

Код csv.DictReader использует первую строку как имена полей, если они не переданы отдельно. Поэтому проверять нужно не только картинку таблицы, но и фактическое содержимое fieldnames. Обозначения plain_keys и sig_keys в выводе — имена после двух способов декодирования; rows — прочитанные строки, sum_score — сумма учебных значений.

Ниже полный код учебного примера и фактически полученный вывод. Обозначения в выводе объяснены рядом с примером.

import csv
from io import StringIO

raw = b"\xef\xbb\xbfid,score\nR01,7\nR02,9\n"
plain_text = raw.decode("utf-8")
sig_text = raw.decode("utf-8-sig")
plain_reader = csv.DictReader(StringIO(plain_text, newline=""))
sig_reader = csv.DictReader(StringIO(sig_text, newline=""))
print("plain_keys =", repr(plain_reader.fieldnames))
print("sig_keys =", repr(sig_reader.fieldnames))
rows = list(sig_reader)
print("rows =", rows)
print("sum_score =", sum(int(row["score"]) for row in rows))
interior = "A\ufeffB"
print("interior_kept =", interior.encode("utf-8").decode("utf-8-sig") == interior)
assert plain_reader.fieldnames == ["\ufeffid", "score"]
assert sig_reader.fieldnames == ["id", "score"]
assert [row["id"] for row in rows] == ["R01", "R02"]
assert len(rows) == 2
plain_keys = ['\ufeffid', 'score']
sig_keys = ['id', 'score']
rows = [{'id': 'R01', 'score': '7'}, {'id': 'R02', 'score': '9'}]
sum_score = 16
interior_kept = True

Запись с обратной косой чертой в repr помогает заметить невидимый символ: имя с U+FEFF перед id отличается от обычного id. Assert подтверждает точные имена, два ID и число записей. Преобразование score в int подходит только потому, что в этом маленьком наборе заранее заданы корректные целые значения; полноценная проверка произвольного столбца здесь не реализована.

Почему нельзя просто удалить U+FEFF везде

Начальная подпись и символ внутри текстового поля находятся в разных местах. Проверка interior_kept означает «внутренний символ сохранён»: строка A с U+FEFF между A и B после выбранного декодирования остаётся той же. Это не рекомендация оставлять любой невидимый символ без анализа, а демонстрация границ действия кодека. Решение о содержимом поля требует отдельного правила.

  • Сначала сохраните исходные байты и сведения об ожидаемой кодировке. Работайте с копией, а не перезаписывайте источник ради первого удачного чтения.
  • Затем посмотрите repr первого имени и список fieldnames. Различайте начальную подпись, пробелы, повторяющиеся заголовки и действительно пустое имя.
  • Для самого чтения применяйте utf-8-sig к известному UTF-8 с возможной начальной подписью. Если читаете CSV из файла, указывайте подходящую кодировку и newline=""; в примере используется строковый поток.
  • После чтения проверьте имена, количество записей и сохранность ID. Устранение подписи не проверяет схему, диапазоны значений и научный смысл данных.

Контрольная запись для своего импорта

Что проверить   Что сохранить в журнале
Кодировка и начало файла   Ожидаемая кодировка, наличие или отсутствие начальной подписи
Заголовки   Фактические имена и согласованный ожидаемый набор
Записи   Количество прочитанных строк и контрольные ID
Граница проверки   Другие невидимые символы и ошибки значений не исправлялись автоматически

Если имена действительно повторяются или пусты, нужен другой контроль — проверка заголовков до DictReader. Более общий сценарий с ведущими нулями в идентификаторах разобран в статье об импорте CSV. Подпись UTF-8 не объясняет автоматически любой сбой этих этапов.

Если после проверки импорта нужна консультация по дальнейшим расчётам, можно обсудить статистический анализ вашего набора данных. Передайте исходный файл, описание переменных и вопрос исследования. Успешно прочитанный CSV сам по себе не доказывает пригодность выборки или корректность статистического вывода.

Источники