Crawl4AI: сбор страницы и карта raw/fit Markdown — это асинхронная Python-библиотека, которая забирает HTML, конвертирует его в Markdown и при подключённом фильтре отдаёт две версии текста: полную raw_markdown и очищенную fit_markdown. Ниже — полный учебный стенд, код и проверка обеих версий.
Архитектура Crawl4AI и границы версии
Crawl4AI устроен как набор объектов, каждый из которых отвечает за свою часть работы. В документации проекта (docs.crawl4ai.com/core/quickstart) перечислены ключевые элементы: асинхронный краулер AsyncWebCrawler, настройки браузера BrowserConfig, настройки запуска CrawlerRunConfig, автоматическая конвертация HTML в Markdown через DefaultMarkdownGenerator и стратегии извлечения данных. Это одна продуктовая архитектура, и её достаточно, чтобы собрать страницу и получить текст.
Разделение обязанностей выглядит так:
| Объект | За что отвечает | Где задаётся |
|---|---|---|
AsyncWebCrawler |
запускает браузер и выполняет запрос | контекстный менеджер |
BrowserConfig |
поведение браузера: headless, user agent, JavaScript | передаётся в краулер |
CrawlerRunConfig |
параметры отдельного запуска: кэш, таймауты, генератор | передаётся в arun |
DefaultMarkdownGenerator |
конвертация HTML в Markdown | внутри CrawlerRunConfig |
PruningContentFilterLXML |
фильтрация шума перед выдачей fit-версии | внутри генератора |
Документация проекта описывает AsyncWebCrawler как асинхронный краулер, который по умолчанию запускает headless-браузер Chromium. BrowserConfig управляет поведением браузера, CrawlerRunConfig — тем, как проходит конкретный запуск. Такое разделение позволяет держать один браузер и менять параметры под каждый запрос.
Границы версии важны для воспроизводимости. Официальная документация помечена как v0.9.x. Внутри неё есть отдельное указание: старый PruningContentFilter объявлен устаревшим и выдаёт DeprecationWarning, а его место занимает PruningContentFilterLXML — движок на базе lxml. Документация проекта сообщает, что новый движок работает примерно в десять раз быстрее на больших страницах при идентичном выводе, а в одном из будущих релизов PruningContentFilter станет алиасом lxml-движка. Для нового кода берётся PruningContentFilterLXML.
Ещё одна граница — режим кэша. Документация проекта указывает, что по умолчанию режим кэша выставлен в CacheMode.BYPASS, то есть каждый запуск забирает свежий контент; включение кэша делается явно через CacheMode.ENABLED. Для учебного примера это означает, что повторные запуски не подменяют результат старой копией.
Отдельно стоит назвать то, чего архитектура не обещает. Crawl4AI — библиотека для сбора и преобразования страниц. Обход защит, гарантированный результат на любом сайте и одинаковое поведение на всех версиях в неё не входят. Документация проекта прямо относит часть тем — прокси, PDF-вывод, многотабличные сессии — к более поздним руководствам.
Учебный вход и ручной эталон
Для проверки нужен полностью заданный вход. В этом материале используется полностью заданная учебная страница. Её HTML целиком:
<nav>Menu</nav><main><h1>Limits</h1><p>12 requests per day.</p><p>No weekend support.</p></main>
Читатель сохраняет этот HTML как case.html и открывает его либо через собственный локальный HTTP-сервер, либо размещает по своему публичному URL. Данные не являются собранной страницей реального сайта.
Ручной эталон задаёт, что именно должно оказаться в результате. В учебной странице три смысловые единицы: заголовок Limits, число 12 и оговорка No weekend support. Контракт результата требует сохранить raw_markdown и fit_markdown отдельно и проверить все три единицы в каждом непустом варианте.
Почему оговорка вынесена в эталон отдельно. Короткие фразы вроде No weekend support легко теряются при фильтрации: они невелики по объёму, не содержат ключевых слов заголовка и могут не набрать нужный вес. Если fit-версия теряет эту оговорку, для задачи используется raw-версия либо меняются настройки фильтра с повторной проверкой. Признак «короче raw» не является обязательным признаком успеха: fit-версия может оказаться пустой, равной raw по смыслу или потерять короткую оговорку.
Наличие Menu в raw-версии само по себе не ошибка. Навигационный блок попал в исходный HTML, и его присутствие в полной версии ожидаемо. Основной контент должен быть отделён при подготовке источника — в учебном случае он уже лежит внутри <main>.
Отрицательные случаи, при которых результат не принимается как полный источник:
| Случай | Что видно в результате | Почему не принимается |
|---|---|---|
| HTTP 200 с формой входа | вместо Limits — поля логина |
страница не отдала контент |
| Пустой результат | ни одной из трёх единиц | проверять нечего |
| Подмена числа | 12 заменено на 20 |
факт искажён |
Ни один из этих случаев не считается полным источником. Это ручные эталоны учебного примера.
Документированный маршрут и файлы
Маршрут собирается из объектов, описанных в документации проекта. Порядок такой: создать генератор Markdown с фильтром, положить его в конфигурацию запуска, открыть асинхронный краулер с настройками браузера, выполнить запрос, разложить результат на две версии.
Перед запуском установите пакет командой pip install -U crawl4ai и выполните crawl4ai-setup для установки браузера. Затем подготовьте два файла и запустите локальный HTTP-сервер из каталога с HTML.
Первый файл — case.html с HTML из предыдущего раздела. Второй — Python-скрипт. Команда запуска локального сервера выполняется из каталога с case.html:
python -m http.server 8765 --bind 127.0.0.1
Это локальный стенд и действие читателя. Адрес http://127.0.0.1:8765/case.html соответствует файлу в текущем каталоге.
Полный блок для читателя:
import asyncio
from pathlib import Path
from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig
from crawl4ai.content_filter_strategy import PruningContentFilterLXML
from crawl4ai.markdown_generation_strategy import DefaultMarkdownGenerator
async def main():
generator = DefaultMarkdownGenerator(
content_filter=PruningContentFilterLXML(threshold=0.4, threshold_type="fixed")
)
config = CrawlerRunConfig(markdown_generator=generator)
async with AsyncWebCrawler(config=BrowserConfig(headless=True)) as crawler:
result = await crawler.arun(url="http://127.0.0.1:8765/case.html", config=config)
raw = result.markdown.raw_markdown
fit = result.markdown.fit_markdown
Path("raw.md").write_text(raw or "", encoding="utf-8")
Path("fit.md").write_text(fit or "", encoding="utf-8")
print("success:", result.success, "status:", result.status_code)
for name, text in [("raw", raw), ("fit", fit)]:
print(name, {term: term in text for term in ["Limits", "12", "No weekend support"]})
print(text)
asyncio.run(main())
Код редакцией не запускался. Он собран по текущей официальной документации и приведён как учебный пример.
Что делает каждая часть. Импорты берут краулер, конфигурации, фильтр и генератор. DefaultMarkdownGenerator получает фильтр PruningContentFilterLXML с порогом 0.4 и типом порога "fixed". CrawlerRunConfig получает генератор. AsyncWebCrawler открывается с BrowserConfig(headless=True). Запрос идёт через arun по адресу локального стенда. Результат разбирается на raw_markdown и fit_markdown, печатается статус и словарь проверки трёх единиц.
Документация проекта описывает threshold как границу оценки: блоки ниже порога удаляются. Тип "fixed" означает прямое сравнение, тип "dynamic" — подстройку порога по данным. Параметр min_word_threshold отбрасывает блоки короче заданного числа слов. В учебном примере порог 0.4 выбран как отправная точка; при потере оговорки его меняют и проверяют заново.
Проверка полей и отрицательные случаи
Проверка идёт по трём единицам в двух версиях. Словарь в коде печатает для каждой версии, найдены ли Limits, 12 и No weekend support. Это даёт шесть отметок: три для raw и три для fit.
Разберём возможные исходы.
Первый: обе версии содержат все три единицы. Результат принимается, для дальнейшей работы подходит любая версия.
Второй: raw содержит все три, fit теряет No weekend support. Для этой задачи используется raw-версия. Альтернатива — изменить настройки фильтра и повторить проверку.
Третий: fit пустой. Учебная страница мала, и фильтр может удалить её целиком. Это предусмотренный исход фильтрации. Для задачи берётся raw-версия.
Четвёртый: обе версии пусты. Причина обычно в том, что краулер не получил HTML. Документация проекта в разделе о типичных ошибках советует убедиться, что HTML действительно получен, и проверить, не слишком ли агрессивен фильтр.
Пятый: success равен False. Тогда смотрим status_code и сообщение об ошибке. В учебном стенде такой исход означает проблему с локальным сервером или адресом.
Отдельно проверяется подмена числа. Если вместо 12 в тексте стоит 20, результат не принимается: факт искажён, и источником такой текст быть не может.
Документация проекта описывает объект MarkdownGenerationResult с несколькими полями. Кроме raw_markdown и fit_markdown там есть markdown_with_citations — версия со ссылками в виде сносок, references_markdown — отдельный блок собранных ссылок и fit_html — HTML-фрагмент, из которого собрана fit-версия. Последнее поле полезно при отладке: по нему видно, что именно фильтр оставил.
Проверка точных полей — это не формальность. Документация проекта в разделе о типичных ошибках предупреждает: у некоторых сайтов важные данные лежат в подвале или боковой панели, поэтому текстовое качество fit-версии проверяют отдельно. Учебный пример показывает этот риск в миниатюре: короткая оговорка легко выпадает из отфильтрованного текста.
Журнал приёмки на учебной странице
До запроса заведите короткую карточку: адрес страницы, её назначение и обязательные сведения. Для нашего стенда назначение — передать в базу знаний правило о количестве запросов вместе с условием работы поддержки. Слова Limits, 12 и No weekend support связываются с одним исходным документом. Дата, пользователь и тариф в HTML не заданы; добавлять их при подготовке карточки нельзя. Такой список помогает заметить потерю условия, даже когда полученный Markdown выглядит аккуратным.
После выполнения скрипта сначала прочитайте статус запроса. Успешное соединение и непустой текст ещё не подтверждают содержание. Откройте сохранённые raw.md и fit.md рядом с исходным case.html. Для каждого обязательного сведения укажите, где оно осталось. Если в одном файле сохранилась цифра, а в другом — оговорка, объединять их наугад не следует: принимается вариант, в котором правило можно прочитать целиком и сопоставить с исходником. При ручной подготовке итогового фрагмента сохраните ссылку на страницу и оставьте исходные файлы рядом.
Ниже приведён отдельный контроль приёмки. Он работает только с заданными строками и не обращается к Crawl4AI, браузеру или сети. Все значения в наборе условные. Названия случаев описывают специально подготовленные входы для проверяющего кода. Они не являются ответами библиотеки и не показывают качество фильтра на реальных сайтах.
required = ["Limits", "12 requests per day.", "No weekend support."]
cases = {
"complete": "# Limits\n12 requests per day.\nNo weekend support.",
"lost_note": "# Limits\n12 requests per day.",
"changed_number": "# Limits\n20 requests per day.\nNo weekend support.",
"login_page": "Sign in\nEmail\nPassword",
"empty": "",
}
expected = {
"complete": True,
"lost_note": False,
"changed_number": False,
"login_page": False,
"empty": False,
}
for name, text in cases.items():
fields = {term: term in text for term in required}
accepted = bool(text.strip()) and all(fields.values())
assert accepted == expected[name], (name, fields)
print(name, "ACCEPT" if accepted else "REJECT", fields)
Ручной эталон для complete — принять: правило и условие сохранены. Для lost_note — отклонить: читатель увидит число запросов, но потеряет условие о выходных. Для changed_number — отклонить: заголовок и оговорка присутствуют, однако число искажено. Для login_page — отклонить: получен другой по назначению текст. Для empty — отклонить: содержательной основы для ответа нет. Эти решения заданы до запуска проверяющего кода, поэтому их можно сверить независимо от результата преобразования.
Проверка целой фразы 12 requests per day. строже, чем поиск отдельной цифры. Цифра может встретиться в меню, в ссылке или рядом с другим условием. Например, присутствие 12 где-то в навигации не доказывает сохранность правила о дневных запросах. В маленьком учебном стенде точные строки удобны, потому что вход заранее известен. На произвольной странице потребуется содержательная проверка расположения и смысла: ограничения исходного правила должны сохраниться вместе с числом.
У такого контроля есть понятные границы. Перенос строки внутри фразы, изменение пробелов или допустимое оформление Markdown способны дать отрицательную отметку при сохранённом смысле. Поэтому отметка направляет проверку к конкретному полю. Откройте оба файла и найдите фразу глазами. Если изменилось только оформление, зафиксируйте это в карточке. Если исчезло условие, верните результат на сбор. Расширять проверку до безусловного принятия любого похожего текста нельзя: это скроет именно ту потерю, ради которой создан эталон.
Сравнение raw и fit удобно вести отдельными строками журнала. В строке raw запишите статус сбора, наличие заголовка, наличие правила и наличие оговорки. В строке fit повторите те же поля. Решение для дальнейшей работы выносится после заполнения обеих строк. Успешная raw-версия позволяет продолжить работу, когда fit оказался пустым. При этом пустой fit остаётся зафиксированным результатом конкретной настройки, и при следующем изменении фильтра его проверяют заново.
Если fit потерял короткую оговорку, изменяйте одну настройку за раз. Сохраните предыдущий файл перед повторным запуском и подпишите, какая настройка изменилась. Затем повторите тот же контроль на том же HTML. Такой порядок позволяет связать изменение результата с действием читателя. Одновременная замена страницы, фильтра и ожидаемых фраз затруднит разбор: станет непонятно, какое изменение вернуло оговорку. Учебный стенд полезен именно своей неизменностью.
Для передачи результата следующему участнику достаточно понятного набора файлов: исходный case.html, скрипт сбора, raw.md, fit.md и карточка приёмки. В карточке укажите, какой вариант выбран и почему. Формулировка причины должна называть сохранённые сведения: заголовок, дневное правило и условие поддержки. Оценки вроде «выглядит чище» не объясняют решение. Если выбран raw из-за потери оговорки в fit, сохраните это основание, чтобы следующий этап случайно не подменил принятый файл.
Перед загрузкой в базу знаний ещё раз сопоставьте итоговый фрагмент с исходным HTML. Заголовок помогает определить тему, число задаёт значение, оговорка ограничивает применение правила. Все части нужны вместе. Навигационное Menu можно исключить из подготовленного фрагмента, сохранив неизменный raw-файл для разбора. Сам факт удаления меню не подтверждает качество fit: решающим остаётся сохранение обязательного содержания.
При переходе со стенда на свою страницу измените адрес и составьте новый эталон из её действительного содержания. Учебные 12 и условие про выходные туда не переносятся. Для страницы с другим правилом будут другие обязательные фразы, но последовательность действий сохранится: зафиксировать источник, получить обе версии, проверить сведения, выбрать полный вариант и сохранить основание решения. Если исходник изменился, обновление эталона следует отдельно объяснить по новой странице.
Наконец, отделяйте результат сбора от последующего ответа языковой модели. Даже принятый Markdown требует правильной передачи контекста. Если вопрос касается часов работы поддержки, учебная страница позволяет сообщить только про выходные. Часов в ней нет. В ответе нужно обозначить отсутствие этих данных. Это та же граница содержания, которая действует при проверке raw и fit: последующий этап работает с тем, что осталось в принятом источнике.
Если один и тот же HTML дал разные результаты при повторных запусках, сначала сохраните оба набора файлов. Проверьте, одинаковы ли адрес, содержимое страницы и конфигурация. В карточке оставьте различия наблюдаемыми: какой фрагмент появился или исчез. До выяснения причины используйте только вариант, прошедший содержательную приёмку. Когда ни один вариант её не прошёл, передача источника останавливается. Восстанавливать пропавшее условие из старого результата допустимо лишь как отдельную ручную редакторскую работу с повторной сверкой исходника; такой текст уже требует собственного объяснения происхождения.
Этот порядок позволяет повторить решение другому человеку. Он открывает тот же HTML, видит обязательные сведения, читает два сохранённых варианта и проверяет указанную причину выбора. Для учебной задачи достаточно этого прозрачного следа без заявления о скорости, полноте обхода или качестве модели.
Перед следующим запуском сохраните исходный HTML неизменным. Иначе сравниваться будут разные страницы, и причину расхождения raw и fit установить по этим файлам уже не получится.
Чужие замеры и качество результата
Внешние материалы по Crawl4AI содержат собственные примеры и проверки. Их нельзя переносить на учебный пример этого материала.
Обзор на Zenrows описывает сбор страниц для RAG: установку, AsyncWebCrawler, работу с fit Markdown, BM25-фильтр, разбиение на части и подачу сырого HTML. Издание называет собственные шаги и собственные сценарии. Эти шаги относятся к их статье и их данным.
Обзор на ScrapingBee — полное руководство с указанным автором: установка, Markdown, извлечение через CSS и через модель, динамические страницы, глубокий обход и вывод для RAG. Издание приводит свои примеры. Они не описывают учебную страницу с заголовком Limits.
Материал на Bright Data описывает сбор с подключением модели DeepSeek. Это отдельный сценарий с отдельным провайдером.
Отсюда правило: числа, тарифы и выводы о качестве из чужих материалов остаются в этих материалах. Учебный пример проверяется по своему эталону — три единицы, две версии, шесть отметок. Ни один внешний замер не доказывает, что фильтр сохранит No weekend support на конкретной странице читателя.
То же касается производительности. Документация проекта упоминает, что PruningContentFilterLXML добавляет около 50 миллисекунд времени обработки. Это ориентир из документации. На больших страницах с несколькими фильтрами обработка идёт медленнее; документация проекта советует учитывать режим кэша, чтобы не скачивать страницу повторно.
Отдельная тема — выбор фильтра. Документация проекта описывает BM25ContentFilter для случая, когда есть поисковый запрос: фильтр старается оставить блоки, релевантные запросу. Параметр bm25_threshold повышают, чтобы оставить меньше блоков, и понижают, чтобы оставить больше. PruningContentFilterLXML применяют, когда запроса нет и нужна общая очистка от повторов, подвалов и боковых панелей. Документация проекта также показывает двухпроходную схему: сначала очистка через PruningContentFilterLXML, затем отбор по запросу через BM25ContentFilter, причём без повторного сетевого запроса — второй проход работает с уже полученным HTML.
Неизвестные значения и условия отказа
Часть значений в примере задана условно, и это нужно назвать прямо.
Порог 0.4 — учебный вход. Документация проекта приводит другие значения в своих примерах: 0.5 и 0.6. Конкретное число подбирается под страницу: при слишком высоком пороге fit-версия пустеет, при слишком низком в неё проходит шум. Проверка после смены порога обязательна.
Адрес http://127.0.0.1:8765/case.html — адрес локального стенда читателя. Порт 8765 выбран для примера; при занятом порте берётся другой, и адрес в коде меняется соответственно.
headless=True означает запуск браузера без видимого окна. Документация проекта упоминает и режим с видимым интерфейсом; для учебного стенда он не нужен.
Условия, при которых результат нельзя принять:
- краулер не получил HTML, и обе версии пусты;
- фильтр удалил учебную страницу целиком, и fit-версия пуста;
- fit-версия потеряла
No weekend support; - число
12заменено другим; successравенFalse, аstatus_codeуказывает на ошибку.
В каждом из этих случаев полный контент-контракт сохраняется: три единицы должны быть проверены, и решение о выборе версии принимается по результату проверки. Если fit-версия не проходит, для задачи берётся raw-версия.
Отдельно про доступность. Этот материал описывает библиотеку и локальный учебный стенд. Вопросы доступности внешних сервисов, платных провайдеров и моделей здесь не рассматриваются: они относятся к другим продуктам и другим условиям.
Частые вопросы
Что такое fit Markdown в Crawl4AI?
Это версия текста после применения фильтра содержимого. Документация проекта описывает её как очищенный вариант, в котором фильтр удалил или сократил шумные фрагменты. Она доступна как result.markdown.fit_markdown. Без подключённого фильтра или генератора обычно видна только raw-версия.
Чем raw_markdown отличается от fit_markdown?
raw_markdown — прямая конвертация HTML в Markdown без фильтрации. fit_markdown — та же страница после фильтра. Документация проекта называет первое поле полной нефильтрованной версией, второе — очищенной. Обе версии лежат внутри result.markdown.
Почему fit Markdown может быть пустым?
Фильтр удаляет блоки ниже порога оценки. Учебная страница мала, и при неудачном пороге она может быть удалена целиком. Документация проекта в разделе о типичных ошибках советует понизить пороги или отключить фильтр, чтобы проверить, вернётся ли содержимое. Пустой fit-результат требует проверки условий фильтрации.
Какой фильтр выбрать для очистки страницы?
Для общей очистки без поискового запроса документация проекта предлагает PruningContentFilterLXML. Он анализирует плотность текста, плотность ссылок, структуру HTML и известные шаблоны вроде nav и footer. Для отбора по запросу применяется BM25ContentFilter. Два фильтра можно объединить в два прохода без повторного сетевого запроса.
Что делать, если fit-версия потеряла важную оговорку?
Для задачи берётся raw-версия: она содержит полный текст страницы. Альтернатива — изменить настройки фильтра, например порог или минимальное число слов в блоке, и повторить проверку. Документация проекта отдельно предупреждает, что у части сайтов важные данные лежат в подвале или боковой панели, поэтому качество fit-версии проверяют вручную.
