MAATRIX / Блог / Crawl4AI: установка на VPS

Crawl4AI: установка на VPS

Crawl4AI: установка на VPS

MAATRIX

Если вам нужен не разовый скрапинг одной страницы, а регулярный сбор контента для базы знаний или RAG-системы, самописный парсер на requests и BeautifulSoup быстро упирается в JS-рендеринг, меню и рекламу в каждом чанке, а на выходе — сырой HTML, который ещё готовить и готовить перед тем как отдать модели. Crawl4AI закрывает именно этот разрыв: краулит как настоящий браузер и сразу отдаёт чистый markdown, готовый для эмбеддинга. Разберём установку на VPS через pip и Docker, первый рабочий скрипт и то, как вписать всё это в пайплайн RAG.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Crawl4AI против самодельного парсера: когда он оправдан

Crawl4AI — открытый Python-фреймворк для краулинга, спроектированный конкретно под подготовку контента для LLM и RAG. Под капотом — Playwright с headless Chromium, то есть страница реально рендерится в браузере, а не запрашивается как голый HTML. Разница ощущается сразу на сайтах с React, Vue или Next.js: requests.get() там возвращает пустой <div id="root"></div>, а Crawl4AI дожидается отрисовки и забирает готовый DOM.

Второе отличие — формат результата. Обычный парсер отдаёт HTML или в лучшем случае текст, из которого ещё нужно вручную убирать шапку, меню, футер и баннер cookie-согласия. Crawl4AI на выходе сразу даёт markdown двух видов: raw_markdown — честный перевод HTML в markdown без изменений, и fit_markdown — та же страница после эвристической фильтрации шаблонных блоков. Для RAG обычно нужен именно второй вариант: меньше шума — выше точность поиска по эмбеддингам.

Когда самописный парсер всё ещё оправдан: если сайт один, вёрстка простая и статическая, а поля, которые нужно вытащить, известны заранее — пять строк на BeautifulSoup часто быстрее, чем поднимать браузерный движок. Crawl4AI выигрывает там, где источников много, вёрстка у них разная и меняется без предупреждения, а конечная цель — не пять конкретных полей, а связный текст для модели.

Самописный парсер (requests + BeautifulSoup)Crawl4AI
JS-рендерингНе работает без отдельного добавления Selenium/PlaywrightВстроен по умолчанию
Очистка от меню/рекламыВручную, под каждый сайт свои селекторыЭвристика fit_markdown + свои фильтры
Формат выводаHTML или сырой текстMarkdown, готовый под эмбеддинг
Массовый обходПишете сами: очередь, ретраи, параллелизмarun_many() из коробки
Порог входа на одном простом сайтеНижеТребует настройки браузера

Установка на VPS: подготовка и pip

Понадобится чистый VPS с Ubuntu 24.04 или Debian 12 — на них Python уже версии 3.11+, а Crawl4AI требует минимум 3.9. Подключаемся по SSH и ставим виртуальное окружение — на обеих системах системный pip install без него откажет ошибкой externally-managed-environment из-за PEP 668:

sudo apt update && sudo apt install -y python3-venv python3-pip
python3 -m venv ~/crawl4ai
source ~/crawl4ai/bin/activate

Дальше сам пакет и обязательная постустановка — она подтягивает бинарник headless Chromium под Playwright и без неё браузер просто не найдётся при первом запуске:

pip install -U crawl4ai
crawl4ai-setup

Если сервер совсем голый и системных библиотек для Chromium ещё нет, crawl4ai-setup может не докачать всё нужное — тогда playwright install-deps chromium от root добьёт зависимости через apt. Финальную проверку делает crawl4ai-doctor: команда прогоняет тестовый краулинг example.com и явно пишет, что не так — версия Python, браузер или сеть. Подробный разбор конкретных ошибок на этом шаге и их текстов — в отдельной статье про установку и настройку Crawl4AI, если что-то пошло не по плану.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Docker-вариант: Crawl4AI как HTTP-сервис

Пакет в venv удобен, пока краулер вызывает один Python-процесс на этом же сервере. Как только к нему должны обращаться разные клиенты — бэкенд, n8n, другой сервер в сети, — проще поднять готовый Docker-образ с HTTP API:

docker run -d --name crawl4ai \
  -p 127.0.0.1:11235:11235 \
  --shm-size=1g \
  -e CRAWL4AI_API_TOKEN=$(openssl rand -hex 24) \
  --restart unless-stopped \
  unclecode/crawl4ai:latest

Два момента здесь не для галочки. --shm-size=1g — Docker по умолчанию выделяет контейнеру 64 МБ разделяемой памяти, а Chromium требует больше; без флага браузер молча падает на первой тяжёлой странице. И публикация порта именно на 127.0.0.1, а не 0.0.0.0, — сервис без токена слушает интернет без единой проверки, кто стучится; с заданным CRAWL4AI_API_TOKEN наружу его пускают уже через Nginx с TLS.

Проверка и запуск краулинга — обычные HTTP-запросы:

curl http://127.0.0.1:11235/health

curl -X POST http://127.0.0.1:11235/crawl \
  -H "Authorization: Bearer <ваш-токен>" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.com"], "crawler_config": {"cache_mode": "bypass"}}'

Актуальный список параметров и полную схему запроса удобнее смотреть в /playground самого контейнера — образ обновляется чаще, чем успевают переиздаваться сторонние мануалы.

Первый скрипт: краулинг страницы и markdown на выходе

Минимальный рабочий пример на чистом Python, без Docker — асинхронная функция вокруг AsyncWebCrawler:

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

async def main():
    browser_conf = BrowserConfig(headless=True)
    run_conf = CrawlerRunConfig(cache_mode=CacheMode.BYPASS)

    async with AsyncWebCrawler(config=browser_conf) as crawler:
        result = await crawler.arun(url="https://example.com/docs", config=run_conf)
        print(result.markdown.fit_markdown[:1000])

asyncio.run(main())

Два нюанса, о которые почти все спотыкаются в первый раз. result.markdown — не строка, а объект с полями .raw_markdown и .fit_markdown, обращаться нужно именно так. И CacheMode.BYPASS — не опция для галочки: по умолчанию Crawl4AI кеширует результат по URL на диск, и при повторном запуске во время отладки вы получите старую версию страницы вместо свежего запроса.

Для обхода не одной страницы, а целого раздела сайта есть arun_many() — принимает список URL и параметр max_session_permit, ограничивающий число параллельных браузерных контекстов:

async def crawl_section(urls: list[str]):
    run_conf = CrawlerRunConfig(cache_mode=CacheMode.BYPASS)
    async with AsyncWebCrawler() as crawler:
        results = await crawler.arun_many(urls, config=run_conf, max_session_permit=3)
        return [r.markdown.fit_markdown for r in results if r.success]

На VPS с 2 ГБ RAM max_session_permit больше 2 почти гарантированно уводит процесс в OOM — каждый браузерный контекст держит свои сотни мегабайт, независимо от того, насколько простая страница.

Интеграция в RAG-пайплайн: от краулинга до векторной базы

Место Crawl4AI в пайплайне RAG — самый первый шаг, сбор и очистка сырых данных, тот самый, который в статье про сбор данных для RAG парсингом сайтов закрывают связкой из sitemap-обхода и trafilatura. Crawl4AI решает ту же задачу иначе: рендерит JS-страницы из коробки и сразу возвращает готовый markdown, так что отдельный шаг «извлечь текст из HTML» из пайплайна выпадает целиком.

Дальше пайплайн одинаковый вне зависимости от того, чем вы получили текст:

  1. Краулинг — Crawl4AI обходит список URL (из sitemap.xml или руками составленного списка страниц) и отдаёт markdown по каждой.
  2. Чанкинг — делите текст на куски по 300–800 токенов с небольшим перекрытием, желательно по границам заголовков и абзацев, а не «по количеству символов вслепую».
  3. Эмбеддинги — каждый чанк превращается в вектор моделью эмбеддинга; какую модель брать под свою задачу и язык — отдельный вопрос, разобран в статье про выбор модели эмбеддингов для RAG.
  4. Векторная база — векторы вместе с исходным текстом и метаданными (URL, заголовок, дата обхода) уходят в индекс, например Qdrant.

Каркас на Python — от результата arun_many() до записи в Qdrant:

from qdrant_client import QdrantClient
from qdrant_client.models import PointStruct
import uuid

client = QdrantClient(url="http://127.0.0.1:6333")

def chunk_text(text: str, size: int = 500, overlap: int = 50) -> list[str]:
    words = text.split()
    chunks = []
    for i in range(0, len(words), size - overlap):
        chunks.append(" ".join(words[i:i + size]))
    return chunks

async def index_page(crawler, url: str, embed_fn):
    result = await crawler.arun(url=url, config=CrawlerRunConfig(cache_mode=CacheMode.BYPASS))
    if not result.success:
        return
    chunks = chunk_text(result.markdown.fit_markdown)
    points = [
        PointStruct(id=str(uuid.uuid4()), vector=embed_fn(c), payload={"url": url, "text": c})
        for c in chunks
    ]
    client.upsert(collection_name="docs", points=points)

embed_fn здесь — заглушка под вашу модель эмбеддинга, локальную через sentence-transformers или облачную по API; сам вызов Qdrant не зависит от того, откуда взялся вектор. Разворачивать Qdrant на том же VPS, где стоит Crawl4AI, или на соседнем сервере — вопрос нагрузки; для старта достаточно одного сервера на обоих, подробности — в статье про установку Qdrant на VPS.

Важный практический момент: сайт меняется, и разовый обход устаревает. Повторный краулинг по расписанию (cron или systemd timer) с перезаписью точек по url в payload — рабочая схема без переусложнения; полноценную инкрементальную синхронизацию с отслеживанием измененных страниц стоит городить, только если объём документов уже реально большой.

Тонкая настройка: JS, фильтрация мусора, извлечение по схеме

BrowserConfig и CrawlerRunConfig — два места, где настраивается почти всё поведение краулинга. Из полезного для подготовки данных под RAG:

  • Ожидание динамического контента. Если страница подгружает данные не сразу, а через пару секунд после рендера (инфинити-скролл, ленивая загрузка), в CrawlerRunConfig есть параметры ожидания по селектору или задержки — без них Crawl4AI может забрать страницу раньше, чем нужный блок появился в DOM.
  • Исключение навигации и рекламы. Помимо автоматической эвристики fit_markdown, можно явно указать CSS-селекторы блоков, которые не нужно включать в результат — шапку, футер, баннеры согласия на cookie, — если эвристика по умолчанию их не убрала.
  • Извлечение по CSS-схеме вместо markdown. Когда нужны не связный текст, а конкретные поля — цена, заголовок, дата, — JsonCssExtractionStrategy описывает схему селекторов и возвращает готовый JSON вместо markdown; полезно, когда часть страниц вашего источника — карточки товаров или структурированные записи, а не статьи.
  • LLM-извлечение. LLMExtractionStrategy прогоняет текст через языковую модель по вашему промпту — годится для более сложной нормализации данных, но требует ключа провайдера (OPENAI_API_KEY или аналог) либо локальной модели через Ollama на том же сервере, и заметно дороже по времени, чем обычный markdown-краулинг.

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

Какой VPS брать под Crawl4AI

Нагрузка здесь браузерная, не вычислительная: Chromium ест RAM на каждый контекст, а CPU почти всегда простаивает. Ориентир по конфигурациям:

ЗадачаvCPURAMДиск
Тесты, единичные страницы, max_session_permit=124 ГБ40 ГБ NVMe
Docker-сервис на несколько клиентов, обход сотен страниц48 ГБ80 ГБ NVMe
Краулинг + локальная LLM-экстракция через Ollama на том же сервере816 ГБ100 ГБ NVMe

Ниже 4 ГБ RAM конфигурацию сложно рекомендовать даже для теста — уже пара параллельных браузерных контекстов на многостраничном сайте способна упереться в OOM. Локацию стоит выбирать по тому, куда вы обходите и куда потом стучится LLM для извлечения: если источники и провайдер эмбеддингов/LLM зарубежные, сервер за пределами России — в Лондоне или США — даёт короче маршрут и снимает вопросы с региональными ограничениями по IP у некоторых облачных LLM-провайдеров. Если обходите только рунет, а модель эмбеддингов локальная, разница обычно не критична.

Готовых сборок Crawl4AI в каталоге приложений нет — сервер приходит чистым, а установка занимает пару команд из раздела выше. Зато Qdrant и Ollama, если пайплайн включает их, разворачиваются одной кнопкой из панели — не придётся настраивать их отдельно.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Частые вопросы

Чем Crawl4AI отличается от Firecrawl?

Firecrawl — облачный сервис с API и лимитами по подписке, Crawl4AI — библиотека с открытым кодом, которую вы разворачиваете сами; нет платы за страницу и внешнего журнала ваших запросов, но и инфраструктуру поддерживаете сами.

Обязательно ли использовать Docker, или venv для одного скрипта достаточно?

Для регулярного вызова из одного Python-процесса на этом же сервере venv полностью достаточно. Docker имеет смысл, когда к краулеру должны обращаться несколько разных сервисов по HTTP.

Можно ли использовать Crawl4AI без LLM вообще, просто для получения текста?

Да, базовый краулинг с выводом raw_markdown/fit_markdown не требует никакого LLM-ключа — LLM нужен только для стратегии LLMExtractionStrategy, отдельного и опционального шага.

Как часто нужно перекраулить сайт для RAG?

Зависит от того, как часто меняется источник — для документации продукта обычно достаточно раз в сутки-неделю по cron, для новостного контента чаще. Отслеживать last-modified в sitemap.xml помогает не перекраулить страницы без изменений.

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.