SEO-аудит-агент с проверкой битых ссылок: Как создать SEO-аудит-агента

Материал основан на разборе freecodecamp.org. Ниже — главное и практические шаги, которые можно быстро применить в работе.


В каждом digital-маркетинговом агентстве есть специалист, который открывает таблицу и по каждому URL клиента проверяет title-тег, мета-описание и H1, а также фиксирует битые ссылки для составления отчёта. Эта процедура повторяется каждую неделю.

Эта работа детерминирована. Агент может делать её вместо человека.

В этом руководстве вы создадите SEO-аудит-агента, который будет взаимодействовать с веб-страницами, используя реальный браузер, извлекая SEO-сигналы через Claude API, асинхронно проверяя битые ссылки и обрабатывая неожиданные ситуации, делая паузы для участия человека и записывая структурированный отчёт, который можно восстановить после прерывания.

В результате у вас будет функционирующий агент, который можно запустить для любого списка URL, а стоимость обработки одного URL составляет менее одного центa.

Что вы создадите

Семимодульный Python-агент, который:

  • Читает список URL из CSV-файла
  • Посещает каждый URL в реальном браузере Chromium (не headless-скрейпер)
  • Извлекает title, мета-описание, H1 и canonical-тег через Claude API
  • Асинхронно проверяет битые ссылки с помощью httpx
  • Обнаруживает граничные случаи (404, страницы входа, редиректы) и делает паузу для ввода человека
  • Инкрементально записывает результаты в report.json — безопасно прерывать и возобновлять
  • Генерирует итоговый отчёт report-summary.txt на обычном языке по завершении работы

Полный код доступен на GitHub по адресу dannwaneri/seo-agent.

По этой теме полезно отдельно посмотреть Составьте еженедельное расписание занятий, чтобы расширить контекст и сравнить подходы.

По этой теме полезно отдельно посмотреть Аннотации типов для декораторов в Python, чтобы расширить контекст и сравнить подходы.

Предварительные требования

  • Python 3.11 или выше
  • Ключ Anthropic API (получить можно на console.anthropic.com)
  • Windows, macOS или Linux
  • Базовое знакомство с Python и командной строкой

Почему Browser Use, а не скрейпер

Традиционный подход к SEO-аудиту включает извлечение HTML-страницы с помощью requests и её разбор с использованием BeautifulSoup. Этот метод хорошо работает с статическими страницами, но не может корректно обрабатывать контент, создаваемый JavaScript, и может упустить динамически добавляемые мета-теги, а также сталкивается с проблемами на аутентифицированных страницах.

Browser Use (уже более 84 000 звёзд на GitHub и лицензия MIT) применяется к задачам иначе: он управляет настоящим браузером Chromium, анализирует DOM после выполнения JavaScript и предоставляет доступ к странице через дерево доступности Playwright, что позволяет агенту видеть контент так же, как и человек.

Практическая разница: скрейпер на основе requests может пропустить мета-описание, внедрённое React-компонентом. Browser Use — нет.

Одно из ключевых преимуществ Browser Use заключается в том, что он читает страницы, принимая во внимание их семантику. Например, если Playwright-скрипт может сломаться из-за изменения CSS-класса кнопки, Browser Use все равно сможет распознать, что это кнопка «Submit», и действовать соответственно. Логика извлечения данных осуществляется в промпте Claude, а не зависит от потенциально изменяющихся CSS-селекторов, что значительно повышает надёжность системы.

Структура проекта

Минимальная структура проекта выглядит так:

  • index.py отвечает за основной цикл аудита и чтение списка URL
  • browser.py открывает страницы в Chromium и снимает SEO-сигналы
  • extractor.py отправляет снимок в Claude и получает структурированный JSON
  • linkchecker.py проверяет внутренние ссылки асинхронно
  • hitl.py решает, когда нужна пауза для человека
  • reporter.py пишет report.json и итоговый summary
  • state.py хранит прогресс и список URL, которые уже обработаны
  • input.csv задаёт входной список страниц клиента

Настройка

Создайте папку проекта и установите зависимости:

Базовая последовательность команд такая: создать папку проекта, установить browser-use, anthropic, playwright, httpx, а затем отдельно выполнить установку Chromium через Playwright. На практике я бы оформил это в requirements.txt, чтобы установка окружения воспроизводилась одной командой.

Создайте input.csv с вашими URL:

В input.csv достаточно одной колонки url и списка адресов, которые нужно проверить. Для первого прогона лучше брать 3-5 страниц, а не весь сайт: так проще проверить логику и не тратить лишние токены API.

Создайте .env.example:

В .env или в переменной окружения хранится только один критичный секрет: ANTHROPIC_API_KEY.

Установите ключ API как переменную окружения перед запуском:

На macOS и Linux ключ удобно экспортировать через export ANTHROPIC_API_KEY=..., а в PowerShell через $env:ANTHROPIC_API_KEY=.... Важно проверить это до старта прогона, иначе агент упадёт на первом же URL.

Модуль 1: Управление состоянием

Агенту необходимо отслеживать, какие URL он уже проверил. В случае прерывания выполнения — будь то отключение питания, прерывание с клавиатуры или сетевая ошибка — он должен возобновить работу с того места, где остановился, вместо того чтобы начинать заново.

state.py обрабатывает это с помощью плоского JSON-файла:

В state.py логика предельно простая:

  1. Если state.json ещё не существует, создаётся файл с тремя массивами: audited, pending, needs_human.
  2. После каждого успешно обработанного URL вызывается mark_audited(url).
  3. Если агент уткнулся в login wall, 403 или другой неоднозначный случай, адрес попадает в needs_human.
  4. Повторный запуск читает state.json и продолжает только с тех URL, которые ещё не были завершены.

Дизайн намеренный: mark_audited() вызывается сразу после обработки URL и записи в отчёт. Если агент аварийно завершится в середине работы, будет потеряна работа максимум по одному URL.

Модуль 2: Интеграция с браузером

browser.py выполняет фактическую навигацию по страницам. Он использует Playwright напрямую (который Browser Use устанавливает как зависимость), чтобы открыть видимое окно Chromium, перейти по URL, захватить HTTP-статус и информацию о редиректах, а также извлечь необработанные SEO-сигналы из DOM.

Ключевые архитектурные решения:

Видимый браузер, не headless. Установите headless=False, чтобы наблюдать за работой агента. Это важно для демонстрации и отладки.

Захват статуса через обработчик ответов. Playwright выбрасывает исключение при ответах 4xx/5xx, но обработчик on("response", ...) срабатывает до исключения. Статус захватывается там.

Задержка 2 секунды между посещениями. Предотвращает срабатывание ограничений частоты запросов или обнаружения ботов на сайтах клиентов агентства.

Вот основная функция навигации:

Базовая функция fetch_page() делает четыре вещи:

  1. Открывает видимый Chromium с headless=False.
  2. Навигирует на URL и отдельно сохраняет первый HTTP-статус через page.on("response", ...).
  3. Забирает из DOM title, meta description, все h1, canonical и ограниченный список ссылок.
  4. При timeout выставляет код 408, при иной ошибке пишет событие в stderr и всё равно возвращает частичный снимок страницы, чтобы основной цикл не ломался.

Несколько вещей, заслуживающих внимания. Ограничение raw_links до 100 сделано намеренно: страницы профилей на DEV.to содержат сотни ссылок — все они не нужны для обнаружения битых ссылок. Параметр wait_until="domcontentloaded" работает быстрее, чем networkidle, и достаточен для извлечения мета-тегов.

Модуль 3: Слой извлечения данных через Claude

extractor.py принимает необработанный снимок страницы из browser.py и вызывает Claude для получения структурированного результата SEO-аудита.

Именно здесь большинство руководств допускают ошибку. Одни пишут сложную логику парсинга на Python (хрупкую), другие просят Claude дать произвольный ответ и пытаются разобрать прозу (ненадёжно). Правильный подход: передать Claude строгую JSON-схему и указать, чтобы он не возвращал ничего, кроме неё.

Промпт-инжиниринг (prompt engineering), который делает это надёжным:

В extractor.py ключевая идея не в размере кода, а в контракте:

  • в Claude отправляется снимок страницы;
  • модель обязана вернуть только JSON по заранее заданной схеме;
  • перед json.loads() ответ очищается от возможных markdown fences;
  • если JSON сломан, агент не падает, а создаёт fallback-результат с human_review=True.

Практически это означает, что правила PASS/FAIL для title, description, h1 и canonical живут в одном месте: в тексте промпта. Меняя эти пороги, вы не переписываете парсер и не трогаете браузерный слой.

Два момента делают это надёжным в продакшене. Во-первых, _strip_fences() обрабатывает случай, когда Claude оборачивает ответ в тройные кавычки `json , несмотря на явный запрет — это иногда происходит с Sonnet и стабильно ломает json.loads(), если не предусмотреть обработку. Во-вторых, резервный вариант _error_result() гарантирует, что агент никогда не упадёт из-за некорректного ответа Claude: он логирует ошибку, помечает URL для проверки человеком и переходит к следующему URL.

Стоимость: Claude Sonnet 4 стоит $3 за миллион входных токенов и $15 за миллион выходных токенов. Типичный снимок страницы занимает около 500 входных токенов; структурированный JSON-ответ — около 300 выходных токенов. Это составляет примерно $0.006 за URL — около $0.12 за аудит 20 URL.

Модуль 4: Асинхронная проверка битых ссылок на сайте

linkchecker.py принимает список raw_links из снимка браузера и проверяет ссылки на том же домене на наличие битых с помощью асинхронных HEAD-запросов.

Принятые проектные решения:

Только тот же домен. Проверка каждой внешней ссылки на странице заняла бы минуты и не нужна клиентам агентства. Фильтрация ограничена ссылками на том же домене, что и проверяемая страница.

HEAD-запросы, а не GET. Быстрее, меньше трафика, достаточно для определения кода статуса.

Ограничение в 50 ссылок. На страницах вроде списков статей DEV.to могут быть сотни внутренних ссылок. Проверка всех них доминировала бы во времени выполнения.

Параллельные запросы через asyncio. Все ссылки проверяются одновременно, а не последовательно.

Модуль 5: Участие человека в процессе

Большинство скриптов либо падают, либо молча пропускают такие случаи. Ни то ни другое неприемлемо в контексте агентства.

hitl.py (модуль участия человека) решает эту задачу с помощью двух функций: одна определяет, нужна ли пауза, другая саму паузу обрабатывает.

В hitl.py (модуле участия человека) достаточно трёх правил:

  • если навигация не дала статус, нужна пауза;
  • если пришёл неожиданный код, кроме ожидаемых redirect-статусов, нужна пауза;
  • если в title или h1 встречаются признаки login wall, URL переводится в режим ручной проверки.

В интерактивном режиме агент предлагает три действия: skip, retry или quit. В --auto-режиме этот выбор не спрашивается, а URL автоматически отправляется в needs_human[].

Функция should_pause() отлавливает четыре случая: сбой навигации, неожиданный HTTP-статус, ключевые слова авторизации в заголовке и ключевые слова авторизации в тегах H1. Проверка ключевых слов авторизации позволяет поймать страницы вида «Пожалуйста, войдите, чтобы продолжить», которые возвращают 200, но фактически недоступны.

В режиме --auto (для запланированных запусков) основной цикл пропускает вызов pause_and_prompt() и автоматически обрабатывает эти случаи, записывая URL в needs_human[] в state и продолжая работу.

Модуль 6: Генератор отчётов

reporter.py записывает результаты инкрементально. Это важно: результаты записываются после аудита каждого URL, а не пакетом в конце. Если выполнение прервётся, завершённая работа не будет потеряна.

reporter.py решает две задачи:

  1. После каждого URL обновляет report.json, заменяя запись по тому же адресу, если это повторная попытка.
  2. По завершении формирует человекочитаемый report-summary.txt, где видно общий PASS/FAIL и поля, которые не прошли проверку.

Это важно для реальной агентской эксплуатации: если оператор сначала пропустил URL, а затем вернулся к нему после ручной авторизации, новая запись не дублирует старую, а аккуратно её заменяет.

Дедупликация в write_result() аккуратно обрабатывает повторные попытки. Если URL повторно обрабатывается после того, как человек проверил страницу с требованием авторизации и прошёл аутентификацию, новый результат заменяет старый, а не создаёт дублирующую запись.

Модуль 7: Основной цикл

index.py связывает всё воедино. Он читает список URL, загружает состояние, пропускает уже проверенные URL и запускает цикл аудита.

В index.py основной цикл выглядит так:

  1. Прочитать input.csv.
  2. Исключить URL, которые уже есть в audited.
  3. Для каждого адреса получить браузерный снимок, затем прогнать извлечение и проверку ссылок.
  4. Если нужен человек, либо спросить действие, либо в --auto-режиме отложить URL в needs_human[].
  5. После каждого успешно завершённого шага записать результат и пометить URL как обработанный.
  6. В конце собрать summary-файл.

Обработчик KeyboardInterrupt — это механизм возобновления. Когда вы нажимаете Ctrl+C, обработчик выводит сообщение и завершает работу корректно. Поскольку mark_audited() вызывается после write_result() для каждого URL, следующий запуск пропускает всё уже обработанное.

Запуск агента

Интерактивный режим (приостанавливается на граничных случаях):

Интерактивный запуск: python index.py.

Автоматический режим (пропускает граничные случаи, добавляет в needs_human[]):

Автоматический запуск по расписанию: python index.py --auto.

При запуске вы увидите, как для каждого URL открывается окно браузера, а в терминале отображается прогресс:

Типичный вывод в терминале показывает, сколько URL осталось, какой адрес сейчас проверяется и чем он закончился: PASS, FAIL или AUTO-SKIPPED. Для оператора этого достаточно, чтобы быстро понять, где именно агент встретил проблему.

Для возобновления после прерывания достаточно повторно запустить python index.py — агент подхватит работу с того URL, на котором остановился.

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

Для регулярных еженедельных аудитов создайте пакетный файл и запланируйте его выполнение с помощью Планировщика задач Windows (Windows Task Scheduler).

Создайте run-audit.bat:

На Windows достаточно короткого run-audit.bat, который выставляет ANTHROPIC_API_KEY, переходит в папку проекта и запускает python index.py --auto.

В Планировщике задач Windows:

  1. Создайте новую базовую задачу
  2. Установите триггер: еженедельно, в понедельник в 7:00
  3. Установите действие: «Запустить программу»
  4. Укажите путь к файлу run-audit.bat

Проверяйте report-summary.txt в понедельник утром. URL-адреса в needs_human[] в файле state.json требуют ручной проверки — страницы с требованием авторизации, платным доступом или вернувшие неожиданные коды состояния.

Для macOS/Linux используйте cron с аналогичной логикой: задача запускается по расписанию, агент отрабатывает в --auto-режиме, результаты ждут вас утром.

Как выглядят результаты

Я запустил этого агента против семи своих опубликованных страниц на Hashnode, freeCodeCamp и DEV.to. Все до единой не прошли проверку.

Пример итогового отчёта выглядит так: часть URL падает по title, часть по description, а часть по h1. Самое полезное здесь не абсолютное число FAIL, а то, что агент сразу раскладывает проблему по полям и даёт команде понятный список исправлений.

Проблемы с описаниями на freeCodeCamp частично обусловлены уровнем платформы — шаблон freeCodeCamp иногда усекает или опускает мета-описания для страниц со списками статей. Проблемы с заголовками на DEV.to — мои собственные. Заголовки статей, которые хорошо работают как заголовки, нередко превышают 60 символов в теге <title>.

Примечание о правиле 60 символов для заголовка: это порог отображения, а не штраф за ранжирование. Google индексирует заголовки любой длины. Рекомендация в 60 символов отражает приблизительное количество символов, умещающихся в результате поиска на десктопе до усечения. Заголовки длиннее 60 символов нередко всё равно ранжируются — они просто обрезаются в результатах поиска, что может снизить показатель кликабельности (CTR, click-through rate). Агент сигнализирует о риске отображения, а не о нарушении правил ранжирования.

Типичные ошибки при настройке агента

Работая с подобными инструментами, я регулярно вижу несколько одних и тех же проблем, которые легко предотвратить заранее.

Не установлена переменная окружения ANTHROPIC_API_KEY. Агент завершится с ошибкой на первом же URL. Проверьте переменную командой echo $ANTHROPIC_API_KEY (macOS/Linux) или echo %ANTHROPIC_API_KEY% (Windows) перед запуском.

Chromium не установлен. Browser Use устанавливает Playwright как зависимость, но сам браузер нужно установить отдельно командой playwright install chromium. Без этого шага агент не запустится.

Слишком большой input.csv при первом запуске. Начните с 5–10 URL, убедитесь, что всё работает, и только потом добавляйте полный список клиента. Это сэкономит и время, и деньги на API.

Игнорирование needs_human[] в state.json. Эти URL не исчезли — они требуют ручной проверки. Если не обрабатывать их регулярно, список будет накапливаться и отчёт станет неполным.

Запуск без --auto в cron. В автоматическом режиме без флага --auto агент зависнет, ожидая ввода с клавиатуры, которого никогда не будет. Для любых запланированных задач всегда используйте python index.py --auto.

Следующие шаги

Агент в текущем виде охватывает основной рабочий процесс SEO-аудита. Очевидные расширения:

Метрики производительности — добавить вызов Lighthouse или PageSpeed Insights API для каждого URL

Валидация структурированных данных — проверить наличие разметки JSON-LD schema и валидировать её

Доставка по email — отправить итоговый отчёт report-summary.txt через SMTP после завершения работы

Поддержка нескольких клиентов — отдельные файлы input.csv для каждого клиента, отдельные директории отчётов

Полный код, включая все семь модулей, находится по адресу dannwaneri/seo-agent. Склонируйте репозиторий, добавьте свои URL и запустите агент.

Ответы на эти вопросы могут быть для вас полезными

Можно ли использовать агента для аудита сайтов, требующих авторизации? Да, но с оговоркой. Агент обнаружит стену авторизации и в интерактивном режиме предложит выбор: пропустить, повторить или выйти. Если вы вручную войдёте в браузер и выберете «retry», агент продолжит работу с уже аутентифицированной сессией. В автоматическом режиме такие URL попадают в needs_human[] и требуют ручной обработки.

Почему агент использует Claude Sonnet 4, а не более дешёвую модель? Sonnet 4 стабильно возвращает валидный JSON без лишних обёрток. Более дешёвые модели чаще оборачивают ответ в markdown-блоки или добавляют пояснительный текст, что ломает json.loads(). Если бюджет критичен, можно попробовать claude-haiku, но потребуется более агрессивная обработка в _strip_fences().

Что делать, если агент прервался на середине списка? Просто запустите python index.py повторно. Агент загрузит state.json, определит уже проверенные URL и продолжит с первого необработанного. Работа, записанная в report.json, не потеряется.

Насколько точны результаты проверки битых ссылок? Модуль использует HEAD-запросы, которые большинство серверов обрабатывают корректно. Однако некоторые серверы блокируют HEAD и возвращают 405, хотя страница доступна. В таких случаях linkchecker может ложно пометить ссылку как битую. Для критичных URL стоит проверить вручную.

Можно ли адаптировать правила PASS/FAIL под требования конкретного клиента? Да. Правила живут в промпте функции extract() в extractor.py. Измените пороговые значения (например, 70 символов вместо 60 для title) прямо в тексте промпта — Claude применит новые правила без изменения остального кода.

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x