Crawl4AI: открытый веб-краулер, который отдаёт страницы в виде Markdown для LLM

· 4 мин чтения
web-scraping rag browser-automation python open-source
📂 Исходный код на GitHub

Открытый веб-краулер и скрапер для LLM и ИИ-агентов: превращает любой сайт в чистый, готовый для LLM Markdown. Лицензия Apache 2.0, язык — Python, более 84 тысяч звёзд на GitHub.

Crawl4AI: открытый веб-краулер, который отдаёт страницы в виде Markdown для LLM

Crawl4AI: открытый веб-краулер, который отдаёт страницы в виде Markdown для LLM

Crawl4AI — Python-библиотека, Docker-сервер и облачный сервис, которые решают одну задачу: превращать произвольную веб-страницу в чистый Markdown, который удобно скормить LLM. Проект заточен под RAG, пайплайны данных и агентов, которым нужен доступ к вебу без боли с HTML-разметкой, рекламными вставками и навигационным мусором. Репозиторий на Python, лицензия Apache 2.0, на GitHub — больше 84 тысяч звёзд.

Смысл простой: если агент должен что-то прочитать в интернете, обычно приходится сначала писать свой скрапер, а потом переписывать его вывод в текст, пригодный для модели. Crawl4AI закрывает обе задачи сразу — это и браузерный движок, и генератор Markdown, и плагин для MCP.

Два способа использовать

Разработчик предлагает два пути, и выбор между ними — это выбор между «своё железо» и «свои деньги».

🐍 Библиотека 🐳 Свой сервер ☁️ Crawl4AI Cloud
Кто крутит браузеры вы, в своём Python-процессе вы, в Docker на своей машине авторы проекта
Тяжёлые JS-страницы и антиботы ваши настройки и прокси ваши настройки и прокси решается автоматически
Поиск по вебу — — /search и /answer
Цена бесплатно, навсегда бесплатно (ваш хостинг) pay-as-you-go

Локальный вариант ставится в две команды:

pip install -U crawl4ai
crawl4ai-setup        # one-time browser install

Дальше — обычный asyncio-код:

import asyncio
from crawl4ai import AsyncWebCrawler

async def main():
    async with AsyncWebCrawler() as crawler:
        result = await crawler.arun(url="https://news.ycombinator.com")
        print(result.markdown)

asyncio.run(main())

Облачный вариант — один ключ и REST-вызов без единого браузера на вашей стороне:

curl -s https://api.crawl4ai.com/scrape \
  -H "Authorization: Bearer $CRAWL4AI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://news.ycombinator.com"}' | jq -r .markdown

Тот же ключ работает для /search, /answer, /extract и для пачки URL разом (/scrape/batch, /scrape/jobs).

Генерация Markdown

Главная заявленная ценность — «умный» Markdown: заголовки, списки, таблицы и блоки кода в структуре, которую LLM читает спокойно. Поверх этого идут три механизма отсева мусора:

  • PruningContentFilterLXML — выкидывает меню, футеры и шаблонные блоки;
  • BM25ContentFilter — то же, но применительно к конкретному запросу;
  • LLMContentFilter — фильтрация средствами самой модели.

Ссылки на странице превращаются в нумерованный список цитат, а генератор Markdown можно заменить на свой: подключается кастомная стратегия, если дефолтная не устраивает.

Разница между сырым и отфильтрованным выводом видна на примере:

import asyncio
from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode
from crawl4ai.content_filter_strategy import PruningContentFilterLXML
from crawl4ai.markdown_generation_strategy import DefaultMarkdownGenerator

async def main():
    run_config = CrawlerRunConfig(
        cache_mode=CacheMode.BYPASS,
        markdown_generator=DefaultMarkdownGenerator(
            content_filter=PruningContentFilterLXML(threshold=0.48, threshold_type="fixed", min_word_threshold=0)
        ),
    )
    async with AsyncWebCrawler(config=BrowserConfig(headless=True)) as crawler:
        result = await crawler.arun(url="https://en.wikipedia.org/wiki/Web_crawler", config=run_config)
        print(len(result.markdown.raw_markdown), "characters of raw Markdown")
        print(len(result.markdown.fit_markdown), "characters after the filter")

asyncio.run(main())

Извлечение структурированных данных

Здесь у проекта четыре разных маршрута, и это важное различие — можно обойтись вообще без LLM:

  • JsonCssExtractionStrategy, JsonXPathExtractionStrategy, RegexExtractionStrategy — быстрый разбор по схеме, без участия модели;
  • generate_schema — генерирует переиспользуемую схему по вашему описанию желаемого;
  • LLMExtractionStrategy — извлечение в типизированную JSON-схему через любой провайдер, поддерживаемый LiteLLM (в том числе локальный ollama/llama3.3);
  • CosineStrategy — находит среди чанков те, что соответствуют запросу.

Для больших страниц предусмотрено разбиение на чанки: по темам, по регулярке и по предложениям.

Схема на CSS выглядит так — никакого LLM не требуется:

import asyncio, json
from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode, JsonCssExtractionStrategy

schema = {
    "name": "Quotes",
    "baseSelector": "div.quote",
    "fields": [
        {"name": "text", "selector": "span.text", "type": "text"},
        {"name": "author", "selector": "small.author", "type": "text"},
        {"name": "tags", "selector": "a.tag", "type": "list", "fields": [{"name": "tag", "type": "text"}]},
    ],
}

async def main():
    run_config = CrawlerRunConfig(
        extraction_strategy=JsonCssExtractionStrategy(schema),
        scan_full_page=True,   # scroll to the end, so the page loads every quote
        scroll_delay=0.5,
        cache_mode=CacheMode.BYPASS,
    )
    async with AsyncWebCrawler(config=BrowserConfig(headless=True)) as crawler:
        result = await crawler.arun(url="https://quotes.toscrape.com/scroll", config=run_config)
        quotes = json.loads(result.extracted_content)
        print(f"Extracted {len(quotes)} quotes")

asyncio.run(main())

Управление браузером

Краулер умеет работать не в безликом headless-режиме, а как полноценный браузер:

  • свой профиль — persistent-контекст с сохранёнными логинами, куками и настройками;
  • удалённые браузеры — подключение по Chrome DevTools Protocol (CDP);
  • сессии — состояние браузера живёт между шагами многошагового обхода;
  • прокси — с аутентификацией и ротацией;
  • stealth — enable_stealth плюс отдельный адаптер недетектируемого браузера для сайтов, которые палят автоматизацию;
  • полный контроль — заголовки, куки, user-agent, вьюпорт;
  • движки — Chromium, Firefox и WebKit.

Пример с сохранённым профилем — так проходят сайты с авторизацией:

import os
from pathlib import Path
from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode

async def main():
    user_data_dir = os.path.join(Path.home(), ".crawl4ai", "browser_profile")
    os.makedirs(user_data_dir, exist_ok=True)
    browser_config = BrowserConfig(headless=True, user_data_dir=user_data_dir, use_persistent_context=True)
    run_config = CrawlerRunConfig(cache_mode=CacheMode.BYPASS, magic=True)
    async with AsyncWebCrawler(config=browser_config) as crawler:
        result = await crawler.arun(url="ADDRESS_OF_A_CHALLENGING_WEBSITE", config=run_config)
        print(result.success, len(result.markdown))

asyncio.run(main())

Обход и скрапинг

Отдельного внимания стоит AdaptiveCrawler — он останавливается, когда уже научился достаточно, чтобы ответить на запрос. Это экономит и трафик, и деньги, если рядом стоит платная модель.

Остальное:

  • глубокий обход в ширину, в глубину и best-first, с восстановлением после падений через resume_state;
  • обнаружение URL через AsyncUrlSeeder (sitemaps, Common Crawl) и DomainMapper; с prefetch=True поиск URL идёт в 5–10 раз быстрее;
  • динамические страницы: выполнение JavaScript, ожидание элементов, полная прокрутка (scan_full_page) для бесконечной ленты и lazy-изображений;
  • скриншоты и PDF любой страницы;
  • медиа и ссылки: изображения, аудио, видео, srcset, внутренние и внешние ссылки, iframe, метаданные;
  • сырой HTML и локальные файлы через raw: и file://;
  • хуки на каждом шаге обхода;
  • кэширование повторных загрузок;
  • arun_many — много URL разом с адаптивным по памяти диспетчером.

Docker-сервер и REST API

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

export CRAWL4AI_API_TOKEN="$(openssl rand -hex 32)"
docker run -d -p 11235:11235 --name crawl4ai --shm-size=1g \
  -e CRAWL4AI_API_TOKEN="$CRAWL4AI_API_TOKEN" \
  unclecode/crawl4ai:latest
curl -s http://localhost:11235/md \
  -H "Authorization: Bearer $CRAWL4AI_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://news.ycombinator.com"}' | jq -r .markdown

Эндпоинты: /md, /html, /crawl, /crawl/stream, /screenshot, /pdf, /execute_js. Плюс дашборд мониторинга на http://localhost:11235/dashboard, песочница на /playground, пул браузеров с прогретыми страницами и образы под AMD64 и ARM64.

CLI и MCP

Из коробки есть консольная утилита crwl:

# A page as Markdown
crwl https://news.ycombinator.com -o markdown

# Deep crawl, breadth first, at most 10 pages
crwl https://docs.crawl4ai.com --deep-crawl bfs --max-pages 10

# Ask a question about a page (needs an LLM key: crwl config)
crwl https://www.example.com/products -q S0

К серверу или к облаку подключается MCP — одна строка в конфиге агента:

claude mcp add --transport http crawl4ai https://api.crawl4ai.com/mcp \
  --header "Authorization: Bearer $CRAWL4AI_KEY"

Так же подключаются Codex, Cursor и OpenCode. Для облака доступны /search (поиск без браузера, с ранжированием и очисткой), /answer (прямой ответ на вопрос, пока экспериментально) и /extract (извлечение без собственного LLM-ключа).

Лицензия и атрибуция

Apache 2.0. Атрибуция не обязательна по лицензии, но автор просит её указывать — бейджем в README или одной строкой в документации:

This project uses Crawl4AI (https://github.com/unclecode/crawl4ai) for web data extraction.

Документация по библиотеке и API-справочник лежат на docs.crawl4ai.com, примеры — в каталоге docs/examples, планы развития — в ROADMAP.md, текст лицензии — в LICENSE. Актуальная версия на момент публикации — v0.9.4.

Источник: https://github.com/unclecode/crawl4ai