Как найти страницы со знаками вопроса и повреждённой кодировкой на сайте

На странице вместо русского текста появились вопросительные знаки или «кракозябры». Выглядит одинаково плохо, но причины разные: неверно прочитанные байты иногда можно восстановить, а символы, уже заменённые на вопросительные знаки, могут означать потерю исходного текста.

Сначала сохраните пример страницы и исходные данные. Затем сравните файл, ответ сервера, запись в базе и отображение в браузере. Исправлять кодировку сразу на всём сайте опасно, пока не найден этап, где текст ломается.

Поиск повреждённой кодировки сайта: исходный текст, ошибка, кодировка и массовая проверка
Содержание 11 разделов

Знаки вопроса, символ � и «кракозябры» выглядят похоже, но возникают на разных этапах. Чтобы исправить сайт без новой порчи данных, сначала нужно определить тип повреждения и место, где оно появилось: в исходном файле, базе данных, соединении с БД, импорте, PHP-преобразовании или HTTP-ответе.

Три разных вида повреждения текста

Виды повреждения: Вопросительный знак → Символ замены → Кракозябры
Виды повреждения: Вопросительный знак, Символ замены, Кракозябры.

Буквальные знаки вопроса

Если вместо каждой кириллической буквы сохранён обычный символ ? (U+003F), данные могли пройти через кодировку, в которой нужные символы нельзя представить, а преобразователь заменил их вопросительными знаками. После сохранения такой строки исходные буквы обычно уже невозможно восстановить однозначно: один и тот же ? мог заменить любую букву.

Символ замены �

Знак � — это U+FFFD REPLACEMENT CHARACTER. Стандарт Unicode определяет его как замену неизвестного символа или признак ошибки преобразования некорректной последовательности [1]. Он часто появляется, когда байты не являются допустимым UTF-8, но приложение пытается декодировать их как UTF-8. Если результат с U+FFFD затем записали обратно в базу, часть исходных байтов могла быть потеряна.

Читаемые, но неправильные символы

Строки вида Привет или Привет называются mojibake. Байты одной кодировки были истолкованы как другая, но не обязательно заменены. Unicode приводит пример, когда русский текст в Windows-1251 ошибочно маркируется другой кодировкой и отображается неправильными символами [2]. Иногда такую строку можно обратить, однако перед массовой конвертацией нужно доказать точную последовательность преобразований.

Как браузер выбирает кодировку HTML

Для новой HTML-страницы правильная цель — UTF-8. HTML Standard требует использовать UTF-8 и указывать метку utf-8 [3]. Важно, чтобы согласовались фактические байты и все объявления:

  • HTTP-заголовок Content-Type: text/html; charset=utf-8;
  • <meta charset="utf-8"> в начале <head>;
  • кодировка файла шаблона;
  • строки, полученные из базы и внешних источников.

Декларация meta должна полностью находиться в первых 1024 байтах документа [3][4]. Если HTML начинается с BOM или сервер явно передаёт кодировку в Content-Type, браузер учитывает эти данные раньше позднего meta-элемента. Поэтому простое добавление <meta charset> не исправит ответ, который сервер уже объявил как другую кодировку.

Где ломается цепочка

Где ломается текст: Файл → База данных → Ответ сайта
Где ломается текст: Файл, База данных, Ответ сайта.

Файл сохранён не в той кодировке

Шаблон может быть записан в Windows-1251, а сервер объявляет UTF-8. Статический текст из файла тогда повреждён, в то время как данные из базы выглядят нормально. Обратная ситуация тоже возможна. Сравнение статической подписи шаблона и текста из БД помогает локализовать источник.

HTTP-заголовок спорит с HTML

Веб-сервер, PHP или прокси добавляет один charset, а шаблон — другой. Проверяют именно необработанные заголовки ответа, потому что панель браузера показывает уже выбранную интерпретацию.

Соединение с базой использует другую кодировку

Колонка может быть utf8mb4, но клиентское соединение передаёт или получает строки в другой кодировке. Проверяют кодировку таблицы и колонок, параметры соединения, character_set_client, character_set_connection и character_set_results. Менять их на рабочем сайте без копии нельзя: неверное «исправление» способно заставить приложение заново перекодировать уже корректные данные.

Данные были повреждены до записи

Импорт CSV/XML, обмен с учётной системой, старый API или ручная загрузка могли неверно определить charset. Тогда в БД уже лежит mojibake или знаки замены, и корректный HTTP-заголовок показывает именно сохранённый испорченный текст.

Повторная конвертация

Функция преобразования вызывается дважды: например, импорт уже перевёл Windows-1251 в UTF-8, а обработчик делает это снова. Симптом часто ограничен одним типом сущностей — названиями товаров, свойствами, результатами поиска или письмами.

Кеш хранит старую версию

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

Особенности «1С-Битрикс»

В Bitrix кодировка является параметром сайта: поле CSite.CHARSET описано как кодировка страниц [6]. В публичной части доступны константы SITE_CHARSET и LANG_CHARSET, причём документация отдельно предупреждает, что язык и сайт — разные сущности и значения могут отличаться [7]. Поэтому при многосайтовости нельзя судить о кодировке всех сайтов по одной административной странице.

Проверяют согласованность следующих уровней:

  • поле кодировки каждого сайта;
  • кодировка языковых файлов и шаблона;
  • параметры соединения и реальная кодировка таблиц;
  • пользовательские обработчики в init.php и компонентах;
  • импорт, обмен и выгрузки;
  • файлы кеша и композита.

В API есть методы CMain::ConvertCharset и ConvertCharsetArray [8]. Их наличие не означает, что каждую непонятную строку нужно пропустить через конвертер. Сначала устанавливают исходную и целевую кодировки и проверяют преобразование на копии.

Как найти все повреждённые страницы

Поиск страниц: Сигнатура → Обход сайта → Список ошибок
Поиск страниц: Сигнатура, Обход сайта, Список ошибок.

Шаг 1. Зафиксировать сигнатуры

Для каждого известного примера сохраните:

  • URL и время проверки;
  • необработанные HTTP-заголовки;
  • тело ответа в байтах;
  • HTML после декодирования браузером;
  • значение той же строки в базе или исходном файле.

Ищут не только ?. Полезные сигнатуры: U+FFFD, длинные последовательности вопросительных знаков, частые сочетания Р/С перед кириллическими буквами, Ð/Ñ, а также расхождение объявленного charset с фактическими байтами.

Шаг 2. Обойти сайт краулером

Краулер должен сохранять URL, код ответа, Content-Type, title, H1 и фрагмент основного текста. Проверяют HTML-страницы, AJAX-ответы, XML-фиды и JSON, если они участвуют в публичном интерфейсе. Одновременно фиксируют шаблон страницы: проблема может затрагивать только каталог, поиск или старый раздел.

Шаг 3. Проверить байты, а не только экран

Одинаковая картинка в браузере может иметь разные причины. Если в теле ответа уже содержится байтовая последовательность UTF-8 для U+FFFD, замена произошла раньше браузера. Если байты корректны для Windows-1251, а ответ объявлен UTF-8, проблема находится в метаданных или конфигурации ответа.

Для автоматической проверки UTF-8 можно использовать строгий декодер. В Web API TextDecoder поддерживает режим fatal: при некорректной последовательности он выдаёт ошибку вместо молчаливой подстановки [5]. Это помогает отличить невалидные UTF-8-байты от уже сохранённого mojibake, которое формально может быть валидным UTF-8.

Шаг 4. Сопоставить проблему с источником данных

Сгруппируйте URL по общему признаку:

  • один шаблон;
  • одна таблица или колонка;
  • один импорт;
  • одна дата создания;
  • один сайт в многосайтовости;
  • один слой кеша.

Если заголовок из файла повреждён, а название товара из БД корректно, проверяют файл и HTTP charset. Если статический интерфейс нормальный, а повреждены только импортированные товары за один день, исследуют импорт и оригинальный файл.

Как определить, можно ли восстановить текст

Исходные байты сохранились

Если данные лишь неверно декодируются при выводе, исправляют объявление или точку преобразования. Перед записью в БД ничего массово не перекодируют.

Mojibake сохранён как текст

Восстановление возможно, если известна точная цепочка: какие байты в какой кодировке ошибочно декодировали и как результат затем сохранили. Берут несколько строк с известным оригиналом, строят обратное преобразование и проверяют побайтно. Строки, которые уже корректны, нельзя пропускать через тот же скрипт.

Сохранены ? или U+FFFD

Если заменяющие символы записаны вместо исходных букв, обратное преобразование не знает потерянные значения. Источник восстановления — резервная копия, исходный CSV/XML, внешняя система или редакторская копия. Попытка «угадать» текст регулярными выражениями создаёт новые ошибки.

Безопасный план исправления

Безопасное исправление: Резервная копия → Один пример → Повторная проверка
Безопасное исправление: Резервная копия, Один пример, Повторная проверка.
  1. Остановить источник новых повреждений. Временно приостановить проблемный импорт или запись конкретного поля.
  2. Сделать полную копию. Сохранить файлы, БД и исходные обменные документы.
  3. Воспроизвести на тестовой копии. Зафиксировать входные байты и результат каждого этапа.
  4. Выбрать одну точку исправления. Конфигурация ответа, соединение с БД, импорт или данные — без одновременных изменений всех слоёв.
  5. Подготовить преобразование с условиями. Скрипт должен отличать повреждённые строки от корректных, вести журнал и поддерживать повторный безопасный запуск.
  6. Проверить небольшую выборку. Кириллица, латиница, кавычки, тире, символ рубля, emoji, HTML и JSON.
  7. Выполнить пакетно с журналом. Хранить ID записи, старое и новое значение, результат проверки.
  8. Очистить только относящийся кеш. После подтверждения корректности данных.
  9. Повторно обойти сайт. Сравнить число ошибок и проверить, что новые записи сохраняются правильно.

Переход старого сайта на UTF-8

Для старого проекта на Windows-1251 миграция — это отдельная работа, а не изменение одного meta-тега. Нужно согласованно преобразовать:

  • PHP, HTML, CSS, JavaScript и языковые файлы;
  • базу данных, таблицы и соединение;
  • настройку кодировки сайта в Bitrix;
  • импорт, экспорт, фиды и API;
  • шаблоны писем и генерируемые документы;
  • кеш и поисковый индекс CMS.

Миграцию проводят на копии с контрольными суммами и тестовым набором символов. Старый Windows-1251-сайт может работать корректно, если вся цепочка согласована; смешанная конфигурация опаснее самой по себе старой кодировки. Для новых документов стандартным выбором остаётся UTF-8 [3].

Что не следует делать

  • менять charset только в HTML, не проверив HTTP-заголовок и байты;
  • выполнять ALTER TABLE ... CONVERT на боевой базе без копии и теста;
  • повторно конвертировать все строки по одному визуальному признаку;
  • заменять ? или � предполагаемыми буквами;
  • считать любую последовательность Р и С гарантированным mojibake без контрольного примера;
  • очищать кеш до фиксации исходного повреждённого ответа;
  • смешивать исправление данных и полный переход сайта на UTF-8 в одну непроверяемую операцию.

Как подтвердить результат

Исправление завершено, когда фактические байты соответствуют объявленной кодировке, строгий UTF-8-декодер не находит ошибок, на страницах отсутствуют U+FFFD и характерные последовательности mojibake, а новые записи проходят тот же путь без повреждения. Проверяют не только одну страницу, но все затронутые шаблоны, источники данных и кешированные варианты.

Источники

[1] Unicode Standard 17.0 — U+FFFD REPLACEMENT CHARACTER — https://www.unicode.org/versions/Unicode17.0.0/core-spec/chapter-23/

[2] Unicode Consortium — Display Problems and mojibake — https://www.unicode.org/help/display_problems.html

[3] WHATWG HTML Standard — Specifying the document's character encoding — https://html.spec.whatwg.org/multipage/semantics.html#charset

[4] MDN — HTML meta element and charset — https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta

[5] WHATWG Encoding Standard — decoders, error modes and legacy encodings — https://encoding.spec.whatwg.org/

[6] 1С-Битрикс — CSite: поле CHARSET — https://dev.1c-bitrix.ru/api_help/main/reference/csite/index.php

[7] 1С-Битрикс — SITE_CHARSET и LANG_CHARSET — https://dev.1c-bitrix.ru/api_help/main/general/constants.php

[8] 1С-Битрикс — CMain::ConvertCharset и ConvertCharsetArray — https://dev.1c-bitrix.ru/api_help/main/reference/cmain/index.php

Нужна помощь по этой задаче?
На странице услуги «Перевод старого Битрикс в UTF-8» указаны состав работ, результат и фиксированная цена.