Это пять тысяч разных вёрсток. Универсального парсера здесь не существует.
К нам пришёл запрос на разработку парсера. Задача была нетривиальной: обработать 5000 URL, определить, есть ли у каждого сайта карьерный раздел, и понять, что именно в нём находится.
Если вакансии открыты — то какие? К каким категориям они относятся? В каких городах? И как устроен процесс отклика: внутренняя форма или ссылка на внешнюю площадку вроде HeadHunter, Avito или SuperJob?
Сложность была не в загрузке страниц, а в анализе. Каждый сайт уникален, и написать один алгоритм, который корректно извлекает данные о вакансиях со всех сразу, — заведомо проигрышная затея.
Как только парсеру требуется исключение под конкретный сайт, вы пишете уже не парсер. Вы пишете 5000 парсеров.
Поэтому мы решили целиком отдать этап анализа искусственному интеллекту.
Чтобы понять, какая модель подходит, мы подключились к OpenRouter. Он позволяет быстро перебрать большое количество моделей и сравнить их по трём параметрам:
После короткой серии экспериментов мы остановились на Haiku от Anthropic. Точности хватало для задачи, а скорости — для работы на объёме.
И вот здесь загвоздка. При оплате за запрос — через OpenRouter или напрямую через Anthropic API — проверка одного сайта обходится примерно в $0,25–0,40. Умножаем на 5000 и получаем $1250–2000 за один проход по списку. Это заметно выходит за разумный бюджет проекта.
Решение — отказаться от оплаты за запрос. Мы перешли на подписку и работали через неё напрямую. Наш тариф стоит $200 в месяц и на практике даёт примерно 1000 проверенных сайтов за 4 часа и около 15 000 сайтов за неделю.
Та же модель, та же работа — и совершенно другая экономика. На объёме способ оплаты решает не меньше, чем выбор модели.
Это устроило нас и по бюджету, и по срокам проекта.
От клиента у нас был только входящий XLS-файл со списком URL. Всё остальное мы построили вокруг него.
Postgres для хранения, Django для управления. Мы подняли Postgres и поставили сверху Django — именно ради админки. Админка Django разворачивается минут за пять и даёт рабочий интерфейс для управления тысячами записей без единой строчки фронтенда.
Импорт одним промптом. Загрузка таблицы в базу заняла один промпт: прочитать Excel-файл, сопоставить колонки, записать строки. Пять минут работы.
Дальше админка превратилась в центр управления. Каждый сайт — это строка. У каждой строки есть статус.
Чтобы запустить обработку, сайты отмечаются чекбоксом — по одному или списком — и переводятся в статус pending.
Как только сайт попадает в pending, отдельный Python-скрипт ловит событие и запускает 10 независимых экземпляров Claude Code. Когда один завершается, сразу стартует следующий. Цикл продолжается, пока все сайты из pending не перейдут в completed или error.
Вот и весь слой оркестрации: колонка со статусом и цикл воркеров. Никакого брокера очередей, никакого планировщика. Очередь — это сама база данных.
Самое интересное находится внутри Claude Code. Инструкции мы написали слоями.
CLAUDE.md содержит общий контекст проекта: как с ним работать, где что лежит, какие приняты соглашения.
Дальше два скилла:
Сначала curl-запрос проверяет, отвечает ли сайт. Любой ответ, кроме 200, — и сайт переходит в статус error.
Здесь есть нюанс. Каждый сайт анализируется через Chrome в headless-режиме, и некоторые сайты определяют headless-браузеры и блокируют их. Такие сайты ошибочно помечаются как error. Мы решаем это повторным прогоном неудачной партии позже — уже в обычном (не headless) режиме.
Некоторые сайты выдают капчу. Некоторые вовсе недоступны в нашем регионе. Мы замерили — это примерно 1% списка. На результат такая доля не влияет, поэтому разбирать эти случаи мы не стали.
Не тратьте время на 1% исключений, пока не работает основной сценарий. Сначала измерьте долю сбоев, потом решайте, стоит ли она кода.
Как только доступность сайта подтверждена, Claude ищет страницу с вакансиями. Начинает с дешёвого варианта: сопоставления типовых URL — /jobs, /career, /vacancies и подобных вариантов.
Если это ничего не даёт, включается дорогой путь: ручной обход главной и нескольких внутренних страниц, чтение контента и определение страницы вакансий по косвенным признакам — не по URL и не по тексту ссылки, а по реальному содержимому страницы.
После того как страница найдена, Claude должен с ней взаимодействовать — отправлять запросы, имитировать клики, скроллить страницу, разворачивать списки. Для этого мы подключили Chrome DevTools MCP.
Первое, что Claude делает на странице вакансий, — проверяет, доступны ли данные о вакансиях через API. Часто это так, и это самый чистый путь из всех.
Возьмём для примера крупный ритейл-сайт. Список вакансий отрисовывается по вызову API, а набор вакансий зависит от выбранного города. Claude отслеживает сетевой трафик, определяет, какой запрос возвращает список городов, а какой — вакансии, вытаскивает ID города из первого ответа, подставляет его во второй и получает чистый список вакансий по каждому городу.
Никакого разбора DOM, никаких хрупких селекторов — только собственный API сайта, реконструированный на лету.
Если API нет, Claude переходит к чтению DOM напрямую. Он смотрит, что есть на странице, и сам решает, что считать вакансией. Если объявления разбиты на страницы — он листает пагинацию. Если есть кнопка «Показать ещё» — нажимает её, пока не загрузится всё. Он имитирует действия человека.
Когда анализ завершён, Claude отправляет результаты обратно через Django REST API. Они пишутся в базу через ORM, а статус сайта меняется на completed.
Всё сразу появляется в админке, и дальше мы работаем с данными уже там.
Простой сайт занимает около 30 секунд. Сложный — 2–3 минуты, максимум 5.
Но сайты обрабатываются не по очереди: одновременно работают 10 копий Claude Code, каждая со своим сайтом. На такой параллельности весь список проходится достаточно быстро, чтобы общее время перестало быть проблемой.
Да — это работает и может быть очень эффективно. Но всё зависит от характера вашего проекта.
5000 URL с 5000 разных структур страниц? Такой подход полностью оправдан. Альтернатива — писать и поддерживать сотни отдельных экстракторов под каждый сайт.
50 000 или 100 000 URL? Здесь я бы рассмотрел гибридный подход: основную массу обрабатывать обычным парсером на Python, а ИИ подключать только там, где парсер не справился. На таком масштабе стоимость ИИ-анализа в пересчёте на сайт становится определяющей, а большинству сайтов он попросту не нужен.
Один сайт с известной структурой? Не привлекайте ИИ вообще. Если вы собираете данные о товарах из одного источника и уже знаете вёрстку и селекторы, обычный бот справится быстрее, дешевле и предсказуемее.
ИИ оправдан, когда каждый сайт свёрстан по-своему. Если вёрстка везде одинаковая, достаточно обычного парсера.
Настоящий вопрос не в том, «умеет ли ИИ парсить сайты». Вопрос в том, достаточно ли высока вариативность ваших источников данных, чтобы платить модели за размышления над каждым из них.
У нас на этом проекте — да, достаточно.
Давайте разберём её, рассмотрим возможные подходы и сориентируем по срокам и бюджету.