Выпуск новостей завершён 15 сентября 2026. Материалы сохранены на дату публикации. Перейти к инструкциям →

Архив · Полный материал · Tproger

RAG-бот для любого сайта на Python за 60 строк кода

Соберите простого RAG-бота на Python, который читает любую веб-страницу и отвечает на вопросы только по её тексту. Гайд с кодом и объяснениями.

Разработка · TprogerЕвгений Стребков5 минут
Перейти к тексту ↓
RAG-бот для любого сайта на Python за 60 строк кода
Tproger
Коротко о главномРазвернутьСвернуть
  1. Если попросить языковую модель ответить по свежей документации или конкретному сайту, она с блеском выдумает то, чего там нет.
  2. Стандартное решение — Retrieval-Augmented Generation (RAG) : сначала достать реальный текст, найти в нём нужные куски и только потом передать их модели в качестве контекста.
  3. В этой статье соберём работающего RAG-бота, который отвечает на вопросы о любой веб-странице, примерно на 60 строках Python .

Если попросить языковую модель ответить по свежей документации или конкретному сайту, она с блеском выдумает то, чего там нет. Стандартное решение — Retrieval-Augmented Generation (RAG) : сначала достать реальный текст, найти в нём нужные куски и только потом передать их модели в качестве контекста.

В этой статье соберём работающего RAG-бота, который отвечает на вопросы о любой веб-странице, примерно на 60 строках Python . Никаких парсеров HTML, headless-браузеров и сложных фреймворков — только requests , локальная Ollama и чистый Markdown.

Что такое RAG

RAG (retrieval-augmented generation) — это подход, при котором языковая модель не отвечает по своей памяти, а сначала ищет релевантные фрагменты во внешнем тексте, а потом генерирует ответ на их основе. Это резко снижает галлюцинации и позволяет работать с данными, которых не было в обучающей выборке.

Что мы соберём

Архитектура бота простая и универсальная:

  1. Отправляем URL в сервис извлечения текста и получаем чистый Markdown.
  2. Разбиваем Markdown на фрагменты (чанки) по границам абзацев.
  3. Превращаем каждый чанк в вектор — эмбеддинг.
  4. То же самое делаем с вопросом пользователя.
  5. Находим чанки, ближайшие к вопросу, по косинусной близости.
  6. Отдаём найденные фрагменты + вопрос языковой модели с инструкцией отвечать только по контексту.

Такая схема легко масштабируется: можно заменить локальную Ollama на OpenAI, Anthropic или российские модели, а векторы перенести в Qdrant или Chroma.

Что понадобится

  • Библиотека requests
  • Локально запущенная Ollama с моделями nomic-embed-text и llama3
  • Доступ к API извлечения текста (в примере — Web to Markdown/JSON API ; бесплатный тариф даёт 50 запросов в сутки)

Код бота

Сохраните скрипт как rag_bot.py и подставьте свой ключ, если сервис извлечения текста требует авторизации:

import requests

# URL сервиса, который превращает произвольную веб-страницу в Markdown
API_URL = "https://web2md-api-production-d822.up.railway.app/extract"
OLLAMA = "http://localhost:11434" # REST API Ollama

def fetch_markdown(url: str) -> dict:
"""Получить очищенный Markdown по URL."""
r = requests.post(
API_URL,
json={"url": url, "format": "markdown", "max_length": 50000},
# headers={"X-RapidAPI-Key": "ВАШ_КЛЮЧ"}, # если требуется
)
r.raise_for_status()
return r.json()

def chunk_markdown(md: str, max_chars: int = 1200) -> list[str]:
"""Разбить Markdown на чанки по границам абзацев."""
paragraphs = [p.strip() for p in md.split("\n\n") if p.strip()]
chunks, current = [], ""
for p in paragraphs:
if len(current) + len(p) > max_chars and current:
chunks.append(current)
current = p
else:
current = f"{current}\n\n{p}" if current else p
if current:
chunks.append(current)
return chunks

def embed(text: str) -> list[float]:
"""Получить эмбеддинг текста из Ollama."""
r = requests.post(
f"{OLLAMA}/api/embeddings",
json={"model": "nomic-embed-text", "prompt": text},
)
r.raise_for_status()
return r.json()["embedding"]

def cosine(a: list[float], b: list[float]) -> float:
dot = sum(x * y for x, y in zip(a, b))
na = sum(x * x for x in a) ** 0.5
nb = sum(y * y for y in b) ** 0.5
return dot / (na * nb) if na and nb else 0.0

def answer(chunks: list[str], question: str) -> str:
"""Найти релевантные чанки и ответить через LLM."""
q_emb = embed(question)
ranked = sorted(chunks, key=lambda c: cosine(embed(c), q_emb), reverse=True)
context = "\n\n".join(ranked[:3])

prompt = (
"Ответь на вопрос, используя ТОЛЬКО приведённый ниже контекст. "
"Если в контексте нет ответа, так и скажи.\n\n"
f"Контекст:\n{context}\n\n"
f"Вопрос: {question}\nОтвет:"
)
r = requests.post(
f"{OLLAMA}/api/generate",
json={"model": "llama3", "prompt": prompt, "stream": False},
)
r.raise_for_status()
return r.json()["response"]

if __name__ == "__main__":
data = fetch_markdown("https://en.wikipedia.org/wiki/Retrieval-augmented_generation")
print(f"Извлечено: {data['title']} ({data['word_count']} слов)")

chunks = chunk_markdown(data["content"])
question = "Как RAG снижает галлюцинации языковых моделей?"
print("Ответ:", answer(chunks, question))
На что обратить внимание: Если вы используете RapidAPI-версию сервиса, запрос к API_URL обычно требует заголовка X-RapidAPI-Key . Без ключа бесплатный endpoint может вернуть 401.

Разбор по частям

fetch_markdown — единственный внешний вызов. Сервис сам забирает страницу, убирает навигацию, баннеры и футер и возвращает Markdown: заголовки, абзацы, списки. Это освобождает от зависимостей вроде BeautifulSoup или headless Chrome.

chunk_markdown режет текст на фрагменты примерно по 1200 символов, не разрывая абзацы. Мелкие чанки дают более точный ретривл, но увеличивают число эмбеддингов; для начала 1200 символов — хороший баланс.

embed и cosine превращают текст в векторы и считают их близость. Модель nomic-embed-text из Ollama бесплатна, быстрая и неплохо понимает русский и английский.

answer эмбеддит вопрос, выбирает три самых похожих чанка и строит промпт с жёстким ограничением: отвечать только по контексту. Это главная страховка от галлюцинаций.

Запуск

Установите зависимости и скачайте модели в Ollama:

pip install requests
ollama pull nomic-embed-text
ollama pull llama3
python rag_bot.py

Если всё в порядке, в консоли появится примерно такой результат:

Извлечено: Retrieval-augmented generation (1500 слов)
Ответ: RAG снижает галлюцинации, опирая ответы модели на извлечённые документы...

Почему важен чистый Markdown

Если скормить эмбеддинг-модели сырой HTML, в векторах окажутся теги <div> , меню навигации и копирайты из футера. В результате поиск похожести выдаст «Copyright © 2026» вместо полезного ответа. Чистый Markdown — заголовки, абзацы, списки — улучшает качество ретривла почти бесплатно.

Если не хочется зависеть от внешнего API, можно заменить fetch_markdown на один из альтернативных вариантов:

  • trafilatura — библиотека на Python для извлечения главного текста.
  • r.jina.ai/http://URL — бесплатный сервис без ключа.
  • Firecrawl — API с поддержкой сканирования сайтов целиком.
  • ScrapingBee — прокси + рендеринг для сложных страниц.

Для российских разработчиков локальная Ollama особенно удобна: модели качаются бесплатно, не нужны иностранные карты, а инференс идёт на своём железе.

Куда развивать

  • Направьте бота на документацию, чейнджлог или блог конкурента и задавайте вопросы по ним.
  • Замените Ollama на OpenAI, Anthropic, Gemini или российские модели — функция answer меняется в двух строках.
  • Сохраняйте эмбеддинги в векторную БД: Chroma , Qdrant или FAISS , чтобы индексировать сразу много страниц.
  • Используйте формат json вместо Markdown, если нужна структура: параграфы, заголовки, ссылки — отдельно.

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

Да. Вместо Web to Markdown/JSON API можно использовать библиотеку trafilatura, сервис r.jina.ai или написать собственный парсер на BeautifulSoup. Главное — получить чистый текст без навигации и рекламы.

Ollama запускает модели локально, бесплатно и без облачных API-ключей. Для учебных и pet-проектов этого достаточно, а при необходимости легко перейти на коммерческий провайдер.

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

Подходят любые текстовые страницы: статьи, документация, справочники, блоги. Сложности возникают со страницами, где весь контент загружается JavaScript, или с капчами — там нужен рендеринг и прокси.

Выводы

RAG — не магия, а последовательность простых шагов: получить чистый текст, разрезать его на фрагменты, найти ближайшие к вопросу и отдать их модели. Весь минимальный пайплайн укладывается в короткий Python-скрипт, который можно запустить на своём ноутбуке.

RAG работает ровно так хорошо, каков текст, который вы ему скармливаете. Уберите самую утомительную часть — очистку HTML, — и останется интересное: поиск и генерация. bao001 xiao автор туториала на DEV Community

Исходник идеи — статья «Chat With Any Website: Build a RAG Bot in ~60 Lines of Python» . Попробуйте собрать бота на своей странице и посмотрите, где он справляется, а где начинает фантазировать.