84 звёзд на GitHub и продолжает расти. Yozh Crawler + Scraper — бесплатный, опенсорсный и создаётся публично — поставьте нам звезду, если он заслужил место в вашем стеке.
CyberYozh Data / Yozh Crawler
Софт · Yozh Crawler

Один стартовый URL на входе — весь сайт потоком на выходе

Yozh Crawler обходит сайт с одного URL и передаёт каждую найденную страницу по SSE. Он берёт на себя сложную часть обхода — фронтир, дедуп, скоуп, вежливость, повторы — а загрузка каждой страницы идёт через Yozh Scraper. Никакого дублирования Playwright, никакого SaaS, работает на :8001.

$curl -N :8001/api/v1/crawl -d '{"seed_url":"https://site.com", "scope":{"mode":"same-domain","max_depth":2,"max_pages":500}}'

Обход

Yozh Crawler обходит сайт с одного стартового URL и передаёт каждую найденную страницу по SSE. Он берёт на себя обнаружение — фронтир, дедуп, скоуп, извлечение ссылок, повторы, вежливость — а загрузку каждой страницы делегирует Yozh Scraper по HTTP. Обслуживать нужно ровно один стек Playwright, и все возможности скрейпера (прокси, stealth, сессии, правила извлечения) действуют и для обойдённых страниц.

  • Предикаты скоупаsame-domain / subdomains / all / regex, с include- & exclude-паттернами и жёсткими лимитами max_depth / max_pages.
  • Дедуп по отпечатку — URL канонизируются и хэшируются (SHA1), поэтому переставленные query-параметры и варианты с завершающим слэшем схлопываются в одно посещение.
  • Round-robin фронтир по доменам — один медленный или огромный хост не может забрать всех воркеров у остальных.
  • Адаптивный ограничитель частоты — глобальный token bucket по доменам (с джиттером), общий для всех задач; 429 вдвое снижает RPS на время остывания.
  • Здоровье сессий — скоринг в стиле Crawlee: 401/403/429 выводят сессию из ротации, ошибки повышают её счёт, успех понижает; ротация при счёте ≥ 3 или 50 использованиях.
  • Потоковая передача SSE — каждая задача шлёт события stats, page, page_error и финальные done / cancelled. Реагируйте на находки по мере их поступления.
Базовый URLhttp://localhost:8001
Документация OpenAPIhttp://localhost:8001/docs
Эндпоинт MCPhttp://localhost:8001/mcp
Сопутствующий сервис. Краулер делегирует каждую загрузку в Yozh Scraper, поэтому поднимайте оба вместе — скрейпер на :8000, краулер на :8001.

Быстрый старт

Краулер прописан в корневом docker-compose.yml и поднимается вместе со скрейпером:

bash
docker compose up --build
# scraper  → http://localhost:8000
# crawler  → http://localhost:8001

Или запустите его локально без Docker — укажите в SCRAPER_URL адрес работающего скрейпера:

bash
cd yozh-crawler
pip install -r requirements.txt
cp .env.example .env          # edit SCRAPER_URL if needed
python -m uvicorn src.main:app --reload --port 8001

Базовое использование

Отправьте обход с seed_url и scope. Вызов сразу возвращает job_id; далее обход выполняется в фоне на воркер-задачах.

cURL
curl -X POST http://localhost:8001/api/v1/crawl 
  -H "Content-Type: application/json" 
  -d '{
    "seed_url": "https://example.com",
    "scope": {"mode": "same-domain", "max_depth": 2, "max_pages": 50,
              "per_domain_rps": 1.0, "per_domain_concurrency": 1},
    "scrape_options": {"proxy_type": "none"},
    "enable_scraping": false
  }'

Ответ — это просто дескриптор задачи:

JSON
{ "job_id": "crawl_abc123" }

Дальше вы либо читаете события в реальном времени, либо опрашиваете задачу для получения полной записи.

Обнаружение vs сбор — переключатель enable_scraping

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

enable_scraping: false

Карта обнаружения

Лёгкий проход. Скрейпер по-прежнему рендерит каждую страницу (чтобы находить ссылки, построенные на JS), но краулер сохраняет только каркас — идеально для карт сайта, аудита ссылок и выявления изменений.

  • Сохраняет url · parent_url · depth · status · took_ms
  • Отбрасывает сырой HTML, скриншоты & извлечённые данные
  • Использует более дешёвый crawl_proxy
  • Минимальные объёмы данных, самые быстрые обходы
enable_scraping: true

Полный сбор

Каждая посещённая страница сохраняется с полным ScrapeResponse — сырой HTML, опциональный скриншот и любые поля из ваших правил извлечения. Обход и структурирование за один проход.

  • Сохраняет полный ScrapeResponse для каждой страницы
  • raw_html · screenshot · извлечённые data
  • Использует scrape_options.proxy_*
  • Передайте правила extract, чтобы получать чистый JSON
Выбор прокси. crawl_proxy используется только при enable_scraping=false; scrape_options.proxy_* — при true. Если crawl_proxy равен null, прокси из scrape_options используется независимо от режима.

Скоуп обхода

Скоуп ограничивает обход: где ему разрешено ходить и насколько интенсивно нагружать каждый хост. Это объект scope в запросе.

ПолеТипПо умолчаниюОписание
modesame-domain · subdomains · all · regexsame-domainКакие ссылки входят в скоуп. regex сопоставляется с include_patterns.
include_patternsstring[][]Regex-паттерны, которым URL должен соответствовать, чтобы попасть в очередь (используются режимом regex / как allow-list).
exclude_patternsstring[][]Regex-паттерны, отбрасывающие URL, даже если он иначе входит в скоуп.
max_depthint3Максимальная глубина ссылок от стартовой (стартовая — глубина 0).
max_pagesint500Жёсткий лимит на число посещённых страниц до завершения обхода.
per_domain_rpsfloat1.0Скорость пополнения token bucket по домену (запросов в секунду).
per_domain_concurrencyint1Максимум одновременных запросов к одному домену.
Наивный скоуп subdomains. Он берёт две последние метки как регистрируемый домен, поэтому даёт ложные совпадения на хостах из Public Suffix List, таких как github.io или co.uk. Для них предпочтите same-domain или regex.

Потоковая выдача (SSE)

Каждая задача предоставляет поток Server-Sent Events. Читайте его, чтобы реагировать на страницы в момент их обнаружения, не дожидаясь завершения обхода.

cURL
curl -N http://localhost:8001/api/v1/crawl/crawl_abc123/events

Поток отправляет пять типов событий:

СобытиеКогда
statsПериодический снимок прогресса (visited / queued / failed / dedup_skipped / out_of_scope / retries).
pageПо одному на каждый посещённый URL. Несёт полный ScrapeResponse в режиме сбора и лёгкие метаданные в режиме обнаружения.
page_errorURL, который не удалось загрузить после всех повторов.
doneФинальное — обход завершился штатно.
cancelledФинальное — обход был отменён.

Событие page в режиме обнаружения выглядит так:

JSON
{
  "event": "page",
  "url": "https://example.com/docs/intro",
  "parent_url": "https://example.com/docs",
  "depth": 2,
  "status_code": 200,
  "took_ms": 812,
  "scrape_response": null
}

Статус и результаты

Предпочитаете опрос? Запись задачи доступна в любой момент — в том числе во время обхода, со страницами, найденными к этому моменту. /results — это алиас эндпоинта задачи, оставленный для симметрии со скрейпером.

cURL
curl http://localhost:8001/api/v1/crawl/crawl_abc123
curl http://localhost:8001/api/v1/crawl/crawl_abc123/results
JSON
{
  "job_id": "crawl_abc123",
  "status": "running",
  "stats": {
    "visited": 47, "queued": 12, "failed": 0,
    "dedup_skipped": 9, "out_of_scope": 31, "retries_total": 2
  },
  "pages": [
    { "url": "https://example.com/", "parent_url": null,
      "depth": 0, "status_code": 200, "took_ms": 640 }
  ]
}
status проходит queuedrunningdone (или failed / cancelled).

Отмена обхода

Остановите выполняющуюся задачу через DELETE. Мягкая отмена — вариант по умолчанию и безопасный выбор.

cURL
# soft — stop scheduling, let in-flight requests drain
curl -X DELETE "http://localhost:8001/api/v1/crawl/crawl_abc123?hard=false"

# hard — abort in-flight asyncio tasks immediately
curl -X DELETE "http://localhost:8001/api/v1/crawl/crawl_abc123?hard=true"
Жёсткая отмена оставляет скрейпер осиротевшим. ?hard=true сбрасывает текущий запрос краулера, но скрейпер не может об этом узнать — рендер страницы на его стороне завершится. Предпочитайте мягкую отмену, если только не нужно остановиться прямо сейчас.

MCP

fastapi-mcp смонтирован по адресу /mcp как Streamable HTTP-эндпоинт. Укажите на него Claude или Cursor — и инструменты появятся автоматически:

  • health
  • create_crawl
  • get_crawl
  • get_crawl_results
  • cancel_crawl
~/.claude/settings.json
"open-crawler": {
  "type": "http",
  "url": "http://localhost:8001/mcp"
}
SSE-эндпоинт stream_crawl_events намеренно исключён из MCP — потоковые ответы не ложатся на модель инструмента «запрос/ответ».

Справочник по конфигурации

Запрос — CrawlRequest

ПолеТипПо умолчаниюОписание
seed_urlstring (URL)обязательноЕдинственный URL, с которого начинается обход.
scopeCrawlScopedefaultsГраницы & вежливость — см. Скоуп обхода.
scrape_optionsScrapeOptionsdefaultsПередаётся скрейперу дословно для каждой страницы (url подставляется автоматически).
crawl_proxyScrapeOptions · nullnullДешёвый прокси для режима обнаружения. Читаются только proxy_type / proxy_pool_id / proxy_geo.
enable_scrapingboolfalseСохранять полный ScrapeResponse для страницы (true) или только метаданные обнаружения (false).

Для каждой страницы — ScrapeOptions (выборочно)

ПолеТипПо умолчаниюОписание
proxy_typenone · mobile · res_static · res_rotating · dc_static · …noneПул прокси, через который идёт загрузка страницы.
devicedesktop · mobiledesktopПрофиль viewport / UA.
renderbooltrueРендерить в браузере (нужно для ссылок, построенных на JS).
stealthbooltrueПрименять анти-детект защиту.
wait_untildomcontentloaded · networkidledomcontentloadedКогда страница считается готовой.
screenshotboolfalseДелать скриншот (режим сбора).
extractExtractRule · nullnullПравила полей CSS/XPath → структурированные data для каждой страницы.
session_idstring · nullnullПереиспользовать авторизованную сессию скрейпера — см. Авторизованные обходы.

Переменные окружения

ПеременнаяПо умолчаниюПримечания
SCRAPER_URLhttp://web-scraper:8000Вышестоящий скрейпер (используйте имя docker-сервиса внутри compose-сети).
WORKERS2Число параллельных задач обхода.
QUEUE_MAXSIZE200Глубина очереди ожидающих задач.
JOB_TIMEOUT_MS3_600_000Лимит реального времени на одну задачу обхода.
SCRAPER_JOB_TIMEOUT_MS120_000Таймаут запроса к скрейперу на страницу.
MAX_RETRIES3Повторов на запрос при временном сбое.
RETRY_HTTP_CODES[408,429,500,502,503,504]Коды статуса, вызывающие повтор.
RETRY_BACKOFF_MAX30.0Верхняя граница экспоненциального backoff (секунды).
SESSION_MAX_ERROR_SCORE3.0Порог вывода сессии из ротации.
SESSION_MAX_USAGE50Ротация сессии после N использований.
SESSION_BLOCKED_CODES[401,403,429]Коды, мгновенно выводящие сессию из ротации.

Справочник API

МетодПутьНазначение
POST/api/v1/crawlСоздать задачу. Возвращает {"job_id":"…"}.
GET/api/v1/crawl/{id}Запись задачи — статус, статистика, найденные страницы.
GET/api/v1/crawl/{id}/resultsАлиас записи задачи.
GET/api/v1/crawl/{id}/eventsSSE-поток (stats/page/page_error/done/cancelled).
DELETE/api/v1/crawl/{id}?hard=boolОтменить задачу (мягко или жёстко).
GET/api/v1/healthЗдоровье + доступность скрейпера.

Важные детали

Без аутентификации (v1). Ни один эндпоинт не защищён — сервис рассчитан на внутренние / доверенные сети. Любой, кто до него дотянется, может отправить POST-обход с произвольным seed и превратить его в SSRF-прокси. Держите его внутри VPC или за собственным auth-шлюзом.
robots.txt не учитывается. Краулер обходит каждый входящий в скоуп URL независимо от /robots.txt. Вы сами отвечаете за соблюдение правил robots и условий сайта на чужих ресурсах — используйте exclude_patterns и ограничения частоты, чтобы оставаться вежливыми.
Без персистентности. Задачи живут в памяти и сбрасываются при перезапуске контейнера (симметрично со скрейпером). Для надёжности пишите SSE-поток в собственное хранилище (файл, Postgres, Kafka) по мере поступления страниц. Периодически перезапускайте долгоживущие процессы — завершённые задачи накапливаются в хранилище.
Авторизованные обходы. Создайте сессию на скрейпере (POST /sessions + POST /sessions/{id}/login), затем передайте scrape_options.session_id. Скрейпер прокидывает cookies + storage state в каждом запросе, поэтому каждая обойдённая страница видит авторизованное состояние.
Живой пример

Тот же домен — безопасный вариант по умолчанию

Оставайтесь строго на исходном хосте, ограничьте глубину и число страниц, соблюдайте вежливость. Режим обнаружения (enable_scraping: false) сохраняет только скелет каждой страницы.


            

Нас любят data-команды и AI-разработчики

Что говорят те, кто строит на Yozh

5,0 / 5 · Отзывов: 5
GitHub
Заменили наш собственный кластер Playwright на Yozh за полдня. MCP-эндпоинт встал прямо в нашего Claude-агента — ноль связующего кода, краулер и скрейпер просто заработали.
Marcus Reinhardt Ведущий дата-инженер Northwind Analytics
X
Система пресетов — киллер-фича. Передаём имя источника и получаем чистый JSON; самовосстановление даже поймало два изменения вёрстки Amazon раньше нас.
Priya Nair Основатель ScrapeStack
Reddit
Наконец-то опенсорсный скрейпер, для которого прокси и сессии — первоклассные сущности. Гоняем его за логин-стенами партнёрских порталов — сессии живут, результаты стабильны в разных регионах.
Daniel Osei Backend-инженер Loopfeed
X
Подключил к Cursor через MCP, и теперь мой агент тянет живые веб-данные прямо посреди задачи. Стриминговый обход по SSE — именно то, чего не хватало агентным сценариям.
Elena Kovac ИИ-инженер Vektor Labs
GitHub
Ушли с платного API скрейпинга ради экономии и готовились к откату по качеству — получили обратное. Self-hosted, без оплаты за запрос, а схема вывода чище, чем та, за которую мы платили.
Sofia Almeida Инженерный менеджер Tabbly
Открытый код · Лицензия MIT · 84 ★

Укажите URL — и смотрите, как страницы приходят в реальном времени

Yozh Crawler поставляется в том же репозитории, что и скрейпер — один docker compose up, и у вас есть обнаружение + извлечение, готовое к MCP, на вашей собственной инфраструктуре. Бесплатно. Навсегда. Поставьте звезду, если он заслужил место в вашем стеке.

Yozh Crawler + Scraper распространяются под лицензией MIT. Используйте. Форкайте. Создавайте на их основе.