Гайд · TNWS AI

Как проверить hreflang в XML sitemap после AI-локализации

7 мин—

Разбор XML sitemap с xhtml:link: проверка пары ru/en и self-аннотаций без смешения пространств имён.

Практическая задача

Не потерять альтернативные языковые URL при экспорте локализованных страниц в XML sitemap. Google документирует hreflang через sitemap как один из равноправных способов указания локализованных страниц. В XML имена элементов имеют пространства имён: <loc> sitemap и <xhtml:link rel="alternate" hreflang="..."> принадлежат разным пространствам. Для каждого URL в этом проекте ожидаем полный набор альтернатив. Пример опирается на корректно сформированный XML, не выполняет сетевых запросов и не подменяет полноценную проверку качества перевода. Этот сценарий применим, когда у команды есть контроль над входом или выходом AI-приложения и требуется конкретный сигнал о нарушении правила, а не общее впечатление от нескольких ответов. Все входы ниже вымышлены; числовые результаты относятся только к этим примерам.

Документация методов Python и синтаксиса workflow сверена 26.09.2026. Если вы используете другое окружение, проверьте совместимость до копирования команд. Статья не утверждает, что показанный код измеряет качество конкретной модели или заменяет ручную проверку.

Подготовка и проверяемый контракт

Перед запуском запишите, какие данные считаются входом, что является ожидаемым результатом, что именно означает отказ и кому предстоит разбирать расхождение. Для такой проверки храните тестовый вход, ожидаемый результат и фактический исход раздельно: если они смешаны, тест может подтверждать сам себя. Используйте стабильные синтетические ID, чтобы видеть ошибку в журнале, не раскрывая содержимое клиентских обращений.

Сначала прогоните тест на маленьком входе, где правильное поведение известно вручную. Затем внесите одну контролируемую ошибку и убедитесь, что код её замечает. После этого верните корректный набор и только тогда расширяйте проверку до реальных данных. Не переносите демонстрационные численные значения или пороги в производство без отдельного обоснования: они выбраны для наглядности, а не рассчитаны по вашей аудитории.

Вход и ожидаемый результат

XML содержит ru и en loc, но у en-элемента только en-аннотация; требуется также ru. Печатает неполный элемент https://example.org/en/a; ru-элемент проходит. Перед тем как применять правило к закрытым данным, обсудите пограничные случаи, которые могут поменять интерпретацию входа. Запись в документации о границах теста не менее важна, чем зелёный check.

Пошаговые действия

  1. Создайте scripts/check.py в корне репозитория и вставьте код из блока ниже. Он использует названия функций стандартной библиотеки, указанные в официальных ссылках в конце статьи.
  2. Выполните python scripts/check.py локально. Не продолжайте, если уже демонстрационная фикстура даёт иной результат: сначала проверьте копирование кода и кодировку файла.
  3. Соберите опубликованный XML в файл и передайте его через ET.parse вместо учебной строки. Явно задайте URI обоих пространств имён, иначе стандартный findall может молча вернуть пустоту. Запустите пример и обнаружьте неполный набор на en loc; затем исправьте генератор так, чтобы оба элемента содержали одинаковые ru/en-ссылки. Сверьте loc и href с фактически доступными URL. Если используете hreflang в HTML вместо sitemap, не дублируйте механизм без причины: Google считает способы эквивалентными.
  4. Создайте .github/workflows/ai-corpus-check.yml из YAML-шаблона. Событие pull_request полезно для проверки данных до слияния; чтение репозитория ограничено permissions: contents: read. Скрипт не должен получать ключ внешней модели или реальные секреты.
  5. В разделе Actions откройте job check, сопоставьте его код завершения с локальным прогоном и зафиксируйте ссылку на проверенный коммит в PR. Не принимайте зелёный результат на старой ревизии за проверку последнего изменения.
# scripts/check.py
import xml.etree.ElementTree as ET
S='http://www.sitemaps.org/schemas/sitemap/0.9'; X='http://www.w3.org/1999/xhtml'
ru='https://example.org/ru/a';en='https://example.org/en/a'
xml=f'''<urlset xmlns="{S}" xmlns:xhtml="{X}"><url><loc>{ru}</loc><xhtml:link rel="alternate" hreflang="ru" href="{ru}"/><xhtml:link rel="alternate" hreflang="en" href="{en}"/></url><url><loc>{en}</loc><xhtml:link rel="alternate" hreflang="en" href="{en}"/></url></urlset>'''
root=ET.fromstring(xml)
expected={'ru':ru,'en':en}
missing=[]
for entry in root.findall(f'{{{S}}}url'):
    loc=entry.findtext(f'{{{S}}}loc')
    alternates={el.get('hreflang'):el.get('href') for el in entry.findall(f'{{{X}}}link') if el.get('rel')=='alternate'}
    if alternates!=expected: missing.append(loc)
assert missing==[en]
print('incomplete sitemap entries:',missing)
name: AI corpus checks
on:
  pull_request:
permissions:
  contents: read
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-python@v7
        with:
          python-version: '3.13'
      - run: python scripts/check.py

Как превратить фикстуру в блокирующий контроль

Приведённый Python намеренно содержит локальную демонстрацию с заранее известным ответом. Успешное завершение команды означает лишь то, что правило распознало показанный пример; оно не означает, что реальные опубликованные страницы прошли аудит. Перед использованием предложенного workflow замените учебные словари и XML на снимок данных вашего генератора страниц. Для задачи «Как проверить hreflang в XML sitemap после AI-локализации» сохраните эталон отдельно от проверяемого вывода, посчитайте найденные нарушения и завершайте программу ненулевым кодом, если список ошибок непустой: после диагностического вывода добавьте проверку вида if errors: raise SystemExit(1), где errors — результат именно вашего контроля. Проверьте этот переход двумя отдельными прогонами: сначала подайте намеренно испорченный экспорт и убедитесь, что job красный, затем исправьте экспорт и дождитесь зелёного результата. Не меняйте ожидаемый результат вместе с данными лишь ради прохождения CI. Отдельно проверяйте доступность URL и полноту выгрузки, иначе пустой снимок даст ложное спокойствие.

Ошибки интерпретации и границы

Код не выявляет дублирующиеся xhtml:link с одинаковым hreflang: словарь их перезапишет; для этого добавьте отдельную проверку количества. Если вы расширяете набор случаев, добавляйте не только новые примеры, но и отрицательные случаи, на которых тест обязан упасть. Проверяйте, что изменение expected прошло ревью отдельно от изменения кода: иначе можно незаметно изменить правило под текущий ответ системы. Избегайте вывода полного приватного набора в CI и не сохраняйте в артефактах ответ закрытой модели без решения о доступе и хранении.

Ошибка выполнения самого Python, нераспознанный формат входа и обнаруженный бизнес-дефект — разные ситуации. В сообщении теста указывайте тип проблемы и безопасный ID. Если тест требует внешний сервис, вынесите его из PR с недоверенным кодом и явно ограничьте доступ к токенам; этот пример намеренно работает локально. Перед публикацией каких-либо результатов опишите методику, область применения и происхождение примеров, иначе читатель воспримет демонстрационную арифметику как характеристику продукта.

Практический разбор именно этого контроля

Не потерять альтернативные языковые URL при экспорте локализованных страниц в XML sitemap. Для самостоятельной проверки сначала выпишите три наблюдаемых состояния: допустимый вход, намеренно недопустимый вход и видимый сигнал отказа. В данной фикстуре это выглядит так: XML содержит ru и en loc, но у en-элемента только en-аннотация; требуется также ru. При успешном тесте ожидается: Печатает неполный элемент https://example.org/en/a; ru-элемент проходит. Не превращайте это в числовой KPI: здесь проверяется контракт на конкретных примерах, а не поведение модели на пользовательском трафике.

Код не выявляет дублирующиеся xhtml:link с одинаковым hreflang: словарь их перезапишет; для этого добавьте отдельную проверку количества. Подключая проверку к продукту, расположите её до той операции, ради которой она написана: парсинга, сохранения, отображения или экспорта. Для каждого нового формата добавляйте отдельный негативный пример и письменное решение, почему он отклоняется. Отдельно просмотрите обработку ошибок: система должна отличать невалидный ответ AI от сбоя тестовой инфраструктуры, а пользователь не должен видеть сырой стек или приватное содержимое.

Чек-лист финальной проверки

  • Синтетический вход и ожидаемый исход согласованы предметным владельцем.
  • Команда python scripts/check.py работает локально и в CI на одной версии файлов.
  • Негативный случай обнаруживается, а не только успешный случай проходит.
  • Имена ID и меток уникальны там, где этого требует контракт.
  • В репозитории, логах и артефактах нет реальных секретов и приватных текстов.
  • Понятно, какой дефект тест находит и каких выводов о качестве AI он не даёт.
  • Ссылки на применённые функции и синтаксис workflow перепроверены при переносе.

FAQ

В XML имя включает URI пространства имён xhtml.

Нужно ли использовать одновременно HTML и sitemap?

Google не обещает выигрыша от одновременного применения нескольких способов.

Почему у en нужны обе ссылки?

Каждая версия перечисляет саму себя и другие версии кластера.

Что делать с недоступным переводом?

Уберите его из утверждённого кластера до публикации и обновите sitemap.

Что делать после обнаружения сбоя

Сохраните конкретный ID, воспроизведите его на текущей ревизии и отделите ошибку входных данных от ошибки функции. Если неверен контракт, исправьте её после предметного ревью; если неверен код, сначала оставьте проваливающийся тест, затем вносите исправление. После зелёного запуска просмотрите соседние случаи с похожими признаками, чтобы локальная правка не изменила другое согласованное поведение. Сохраните версии кода, набора и параметров запуска рядом с выводом: без них сравнивать результаты в будущем нельзя. Не приводите цифру из синтетического примера как измеренный показатель продукта.

Официальные первоисточники

Проверено 26.09.2026: 1, 2, 3, 4, 5, 6. Описание переписано своими словами; изменяемые версии и поля нужно повторно сверять перед дальнейшим использованием.

Читайте также

Комментарии

Пока тихо. Скажите первое слово