Конвейер данных для ИИ хорош ровно настолько, насколько хорош текст, который вы в него подаёте, а открытый веб остаётся самым богатым источником свежих, предметно-ориентированных знаний, на которые можно опереть модель. Сложность в том, чтобы получить этот текст в пригодном виде: большинство страниц представляют собой клубок навигации, рекламы и контента, отрисованного через JavaScript, который обычный HTTP-запрос попросту не видит. Это руководство показывает, как построить конвейер данных для ИИ на LangChain и Crawlbase, используя Crawling API в качестве источника документов, чтобы страницы приходили в виде чистого markdown, а затем разбивая их на части, встраивая в векторное хранилище и опрашивая с помощью генерации, дополненной поиском (RAG).
Устройство конвейера простое, и он работает от начала до конца на Python: Crawlbase загружает и очищает страницу, LangChain разбивает её на фрагменты и встраивает их в векторное хранилище, а на этапе запроса вы извлекаете наиболее релевантные фрагменты и передаёте их LLM в качестве контекста. Crawlbase берёт на себя ротацию прокси, блокировки и отрисовку, поэтому код вашего конвейера остаётся сосредоточен на данных, а не на борьбе с антибот-защитой. Всё, что приведено ниже, можно запустить: подставьте свои URL и токены, и у вас есть рабочая RAG-система поверх живого веб-контента.
Почему Crawlbase как источник документов для LangChain
LangChain поставляется с загрузчиками документов для файлов, баз данных и нескольких веб-источников, но именно загрузка реальных веб-страниц в масштабе и ставит в тупик большинство конвейеров. Голый запрос к современному сайту возвращает либо JavaScript-оболочку без контента, либо страницу с блокировкой, и даже когда вы получаете HTML, он полон шаблонной разметки, которая загрязняет ваши эмбеддинги. Мусорные фрагменты означают мусорный поиск, а это означает LLM, которая уверенно цитирует баннер о cookie.
Crawling API чисто решает уровень получения данных. Вы отправляете ему URL, он отрисовывает страницу за доверенным резидентным IP и может вернуть содержимое в виде чистого markdown вместо сырого HTML. Этот markdown это ровно то, что вы хотите видеть как документ LangChain: читаемая проза с сохранёнными заголовками, из которой вырезаны навигация, скрипты и рекламная разметка. Подача предварительно очищенного markdown в ваш разделитель это самый мощный рычаг качества в RAG-конвейере, опирающемся на веб, и это та же идея, что разбирается в материале о веб-скрапинге в markdown, готовый для LLM.
Именно такое разделение ответственности и делает конвейер поддерживаемым. Crawlbase владеет веб-доступом: ротацией IP, решением CAPTCHA, отрисовкой JavaScript и возвратом структурированного вывода. LangChain владеет оркестрацией: разбиением на фрагменты, встраиванием, поиском и промптом, который формирует ответ. Модель владеет рассуждением. Вы можете изменить то, как разбиваете на фрагменты или какую модель опрашиваете, не трогая то, как загружаются данные, и наоборот.
Crawling API принимает параметр format=markdown (а в официальном клиенте есть помощник get_markdown), который возвращает страницу в виде чистого markdown, а не HTML. Для RAG это важно: markdown сохраняет заголовки и списки как структуру, которую ваш разделитель может уважать, отбрасывая при этом шаблон, который иначе превратился бы в шумные, малоценные фрагменты в вашем векторном хранилище.
Архитектура: от URL до обоснованного ответа
В конвейере четыре этапа, у каждого одна задача. Получить: Crawling API загружает каждый URL и возвращает чистый markdown. Разбить: текстовый разделитель LangChain дробит каждый документ на перекрывающиеся фрагменты, достаточно мелкие, чтобы их можно было точно встроить и извлечь. Встроить и сохранить: каждый фрагмент превращается в вектор и записывается в векторное хранилище (мы используем Chroma локально). Извлечь и сгенерировать: на этапе запроса вы встраиваете вопрос, вытягиваете ближайшие фрагменты и передаёте их LLM как обосновывающий контекст.
Первые три этапа это офлайн-задача загрузки, которую вы запускаете, когда меняются ваши источники. Четвёртый запускается каждый раз, когда пользователь задаёт вопрос. Именно разделение загрузки и запросов позволяет конвейеру масштабироваться: вы обходите и встраиваете один раз, а затем дёшево отвечаете на множество вопросов поверх сохранённых векторов. Более широкий паттерн, включая то, почему очистка важна ещё до встраивания, разобран в материале о том, как структурировать и очищать собранные из веба данные для ИИ и ML.
Настройка проекта
Вам понадобится Python 3.10 или новее. Создайте виртуальное окружение и установите библиотеки: официальный клиент Crawlbase для получения данных, пакеты LangChain для оркестрации, Chroma для векторного хранилища и интеграцию с OpenAI для эмбеддингов и чат-модели.
python -m venv .venv source .venv/bin/activate pip install crawlbase langchain langchain-community langchain-openai langchain-chroma
Вам также нужны две учётные записи: токен Crawlbase из вашей панели управления и ключ провайдера эмбеддингов/LLM (здесь ключ OpenAI). Пакет crawlbase даёт вам клиент CrawlingAPI; langchain-chroma оборачивает локальное хранилище Chroma; langchain-openai поставляет и эмбеддинги, и чат-модель. Экспортируйте ключи как переменные окружения, чтобы ничего чувствительного не жило в коде.
export CRAWLBASE_TOKEN="your_crawlbase_token" export OPENAI_API_KEY="your_openai_key"
Шаг 1: Получите чистый markdown с помощью Crawling API
Начните с получения данных. Официальный клиент предоставляет метод get, который принимает URL и опции; передача format=markdown возвращает страницу в виде чистого markdown в теле ответа. Оберните это в небольшую функцию, которая превращает каждую загруженную страницу в Document LangChain, неся исходный URL в метаданных, чтобы потом можно было его процитировать.
import os from crawlbase import CrawlingAPI from langchain_core.documents import Document api = CrawlingAPI({"token": os.environ["CRAWLBASE_TOKEN"]}) def load_page(url): # format=markdown returns clean markdown, not raw HTML response = api.get(url, {"format": "markdown"}) if response["status_code"] != 200: raise RuntimeError(f"Fetch failed for {url}: {response['status_code']}") body = response["body"] text = body.decode("utf-8") if isinstance(body, bytes) else body return Document(page_content=text, metadata={"source": url}) urls = [ "https://example.com/docs/getting-started", "https://example.com/docs/pricing", ] docs = [load_page(u) for u in urls] print(f"Loaded {len(docs)} documents")
Для страниц с обилием JavaScript добавьте в словарь опций "ajax_wait": "true" и "page_wait" в миллисекундах и используйте JavaScript-токен. Поскольку получение данных изолировано в load_page, подстановка этих опций не затрагивает ни один последующий этап. Если сайт отвечает статусом, отличным от 200, функция выбрасывает исключение с кодом, поэтому плохой источник всплывает громко, а не отравляет ваше хранилище страницей ошибки.
Ваш RAG-конвейер хорош ровно настолько, насколько хорош входящий текст. Crawling API отрисовывает страницу за вращающимся резидентным IP и возвращает чистый markdown за один вызов, поэтому ваши фрагменты это реальный контент, а не навигационные панели и страницы блокировки. Подключите его как источник документов LangChain и для начала направьте на несколько публичных URL на бесплатном тарифе.
Шаг 2: Разбейте документы на фрагменты
Целые страницы слишком велики, чтобы встраивать их с пользой: единственный вектор для длинного документа смазывает отдельные темы вместе и вредит точности поиска. Вместо этого разбейте каждый документ на перекрывающиеся фрагменты. RecursiveCharacterTextSplitter от LangChain сначала пытается дробить по границам абзацев и предложений, поэтому фрагменты остаются связными, а поскольку markdown от Crawlbase сохраняет заголовки и списки, эти разбиения ложатся на естественные швы.
from langchain_text_splitters import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=150, ) chunks = splitter.split_documents(docs) print(f"Split into {len(chunks)} chunks")
chunk_size около 1000 символов с перекрытием в 150 символов это разумное значение по умолчанию для прозы. Перекрытие переносит немного контекста через границы, чтобы факт, разорванный между двумя фрагментами, не терялся. Настраивайте оба под ваш контент: более плотные, более технические страницы часто лучше извлекаются с меньшими фрагментами, тогда как длинные статьи терпят большие. Метаданные из load_page копируются на каждый фрагмент автоматически, поэтому каждый из них по-прежнему знает свой исходный URL.
Шаг 3: Встройте и сохраните в векторной базе данных
Теперь превратите каждый фрагмент в вектор и сохраните его. Модель эмбеддингов отображает текст в точку многомерного пространства, где семантически похожие отрывки лежат рядом, а это и делает возможным поиск по смыслу. Chroma хранит эти векторы локально и берёт на себя поиск по сходству; передача persist_directory записывает индекс на диск, поэтому стоимость встраивания вы платите лишь однажды.
from langchain_openai import OpenAIEmbeddings from langchain_chroma import Chroma embeddings = OpenAIEmbeddings(model="text-embedding-3-small") vector_store = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory="./chroma_db", ) print(f"Stored {len(chunks)} vectors")
Этот блок завершает загрузку. Запускайте его один раз, когда меняются ваши источники, а не на каждый запрос. Чтобы переиспользовать хранилище позже, снова откройте его через Chroma(persist_directory="./chroma_db", embedding_function=embeddings), вместо того чтобы перестраивать из документов. Chroma удобна для локальной разработки; тот же интерфейс LangChain служит фасадом для размещённых хранилищ вроде Pinecone или pgvector, когда вы перерастёте одну машину, поэтому остальной ваш код не меняется.
Шаг 4: Извлеките данные и сгенерируйте ответ
Когда векторы на месте, путь запроса короток. Превратите хранилище в ретривер, встройте вопрос пользователя, вытяните ближайшие фрагменты и передайте их чат-модели с промптом, который велит ей отвечать только из предоставленного контекста. Язык выражений LangChain связывает всё это в одну цепочку.
from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.runnables import RunnablePassthrough from langchain_core.output_parsers import StrOutputParser retriever = vector_store.as_retriever(search_kwargs={"k": 4}) llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) prompt = ChatPromptTemplate.from_template( "Answer using only the context below.\n\n" "Context:\n{context}\n\nQuestion: {question}" ) def format_docs(docs): return "\n\n".join(d.page_content for d in docs) chain = ( {"context": retriever | format_docs, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser() ) answer = chain.invoke("What does the getting-started guide say about setup?") print(answer)
Установка k=4 извлекает четыре наиболее релевантных фрагмента; повышайте её для широких вопросов, понижайте для узких. temperature равная 0 держит модель привязанной к извлечённому тексту, а не импровизирующей. Поскольку промпт ограничивает ответ предоставленным контекстом, ответы остаются обоснованными тем, что вы фактически обошли, а так как каждый фрагмент несёт свои метаданные source, вы можете показывать цитаты, напрямую инспектируя извлечённые документы через retriever.invoke(question).
Запуск всего конвейера
Расставьте четыре шага по порядку в одном скрипте, и у вас есть полный конвейер: load_page по вашим URL, разбиение, встраивание в Chroma, затем сборка цепочки и её вызов. Первый запуск обходит и встраивает, что занимает мгновение; последующие запуски, которые переоткрывают сохранённое хранилище, отвечают значительно меньше чем за секунду, потому что дорогая работа уже сделана. Добавьте больше URL в список и перезапустите загрузку, чтобы расширить то, что система знает.
Отсюда та же структура расширяется естественно. Запланируйте задачу загрузки, чтобы обновлять источники с заданной периодичностью, направьте её на карты сайта, чтобы обходить целые разделы, или замените Chroma на размещённое векторное хранилище по мере роста вашего корпуса. Для высоконагруженного обхода вы можете перенести получение данных на асинхронный Crawling API или управлять им из агента через Web MCP, а также маршрутизировать всё через Smart AI Proxy, когда вам нужна ротация IP перед вашим собственным загрузчиком. Контракт конвейера не меняется: чистый текст на входе, обоснованные ответы на выходе. Подробнее о стороне извлечения смотрите в материале о том, как работает извлечение данных с помощью ИИ.
Ключевые выводы
- Получение данных это рычаг качества. Чистый markdown из Crawling API превосходит сырой HTML, потому что шаблонная разметка превращается в шумные фрагменты, разрушающие поиск.
- Четыре этапа, чёткие границы. Получить, разбить, встроить, извлечь-и-сгенерировать, чтобы можно было менять любой из них, не трогая остальные.
-
Дробите с перекрытием.
RecursiveCharacterTextSplitterна ~1000 символов с перекрытием 150 держит фрагменты связными, а факты целыми через границы. - Загружайте однажды, запрашивайте многократно. Сохраняйте векторное хранилище, чтобы дорогая работа по встраиванию происходила только при изменении источников.
- Обосновывайте модель. Ограничьте промпт извлечённым контекстом и держите temperature низкой, чтобы ответы оставались привязанными к тому, что вы обошли.
- Несите метаданные источника. Помечайте каждый документ его URL, чтобы можно было ссылаться на точные страницы, стоящие за каждым ответом.
Часто задаваемые вопросы
Почему стоит использовать Crawlbase вместо встроенного веб-загрузчика LangChain?
Встроенные загрузчики предполагают, что страница возвращает пригодный HTML на обычный запрос, чего современные сайты делают редко: они отрисовывают контент в браузере и блокируют автоматический трафик. Crawling API отрисовывает страницу за вращающимся резидентным IP и возвращает чистый markdown, поэтому ваши документы это реальный контент, а не пустые оболочки или страницы блокировки. Эта чистота напрямую улучшает качество фрагментов и точность поиска.
Что запрашивать для RAG-конвейера: HTML или markdown?
Markdown. Передайте format=markdown, чтобы страница вернулась как читаемая проза с сохранёнными заголовками и списками и вырезанными навигацией, скриптами и рекламной разметкой. Эти структурные подсказки помогают разделителю дробить по естественным границам, а удаление шаблона держит малоценный текст вне вашего векторного хранилища. Запрашивайте HTML, только когда вам нужно разбирать конкретные элементы селекторами, а не встраивать страницу.
Как обрабатывать страницы с обилием JavaScript?
Используйте JavaScript-токен и добавьте ajax_wait и page_wait к опциям, которые вы передаёте в api.get. Crawling API тогда отрисовывает страницу в настоящем браузере, ждёт асинхронный контент и возвращает готовый markdown. Поскольку получение данных изолировано в функции load_page, включение отрисовки не влияет на разбиение, встраивание или поиск дальше по цепочке.
Какой размер фрагмента и перекрытие использовать?
Начните примерно с 1000 символов на фрагмент с перекрытием в 150 символов для общей прозы. Меньшие фрагменты повышают точность на плотном техническом контенте; большие фрагменты подходят для длинных статей, где контекст растягивается на абзацы. Перекрытие переносит немного контекста через границы, чтобы факт, разорванный между двумя фрагментами, всё же оставался извлекаемым. Считайте это значениями по умолчанию и настраивайте по собственным результатам поиска.
Обязательно ли использовать OpenAI для эмбеддингов и LLM?
Нет. Конвейер по своей сути не зависит от провайдера. Замените OpenAIEmbeddings и ChatOpenAI на любую поддерживаемую LangChain модель эмбеддингов и чат-модель, в том числе локальные, и код разбиения, хранения и поиска останется тем же. Crawlbase находится целиком на стороне получения данных, поэтому ваш выбор модели никогда не влияет на то, как загружаются данные.
Как поддерживать базу знаний свежей?
Перезапускайте этапы загрузки (получить, разбить, встроить) по расписанию для URL, которые меняются, и переоткрывайте сохранённое хранилище для запросов в промежутках. Для больших или часто обновляемых корпусов направьте обход на карты сайта и перенесите получение данных на асинхронный Scraper API, чтобы можно было загружать множество страниц, не блокируя ваше приложение.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.

