Соберите простого RAG-бота на Python, который читает любую веб-страницу и отвечает на вопросы только по её тексту. Гайд с кодом и объяснениями.— Читать дальше «RAG-бот для любого сайта на Python за 60 строк кода»
Если попросить языковую модель ответить по свежей документации или конкретному сайту, она с блеском выдумает то, чего там нет. Стандартное решение — Retrieval-Augmented Generation (RAG): сначала достать реальный текст, найти в нём нужные куски и только потом передать их модели в качестве контекста.
В этой статье соберём работающего RAG-бота, который отвечает на вопросы о любой веб-странице, примерно на 60 строках Python. Никаких парсеров HTML, headless-браузеров и сложных фреймворков — только requests, локальная Ollama и чистый Markdown.
RAG (retrieval-augmented generation) — это подход, при котором языковая модель не отвечает по своей памяти, а сначала ищет релевантные фрагменты во внешнем тексте, а потом генерирует ответ на их основе. Это резко снижает галлюцинации и позволяет работать с данными, которых не было в обучающей выборке.
Ключевые выводы
nomic-embed-text и llama3 через Ollama не требуют иностранных карт и VPN.Архитектура бота простая и универсальная:
Такая схема легко масштабируется: можно заменить локальную Ollama на OpenAI, Anthropic или российские модели, а векторы перенести в Qdrant или Chroma.
Что понадобитсяPython 3.9+requestsOllama с моделями nomic-embed-text и llama3Сохраните скрипт как 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 снижает галлюцинации, опирая ответы модели на извлечённые документы...
Если скормить эмбеддинг-модели сырой HTML, в векторах окажутся теги <div>, меню навигации и копирайты из футера. В результате поиск похожести выдаст «Copyright © 2026» вместо полезного ответа. Чистый Markdown — заголовки, абзацы, списки — улучшает качество ретривла почти бесплатно.
Если не хочется зависеть от внешнего API, можно заменить fetch_markdown на один из альтернативных вариантов:
Для российских разработчиков локальная Ollama особенно удобна: модели качаются бесплатно, не нужны иностранные карты, а инференс идёт на своём железе.
Куда развиватьanswer меняется в двух строках.Chroma, Qdrant или FAISS, чтобы индексировать сразу много страниц.json вместо Markdown, если нужна структура: параграфы, заголовки, ссылки — отдельно.Часто задаваемые вопросы
1
Что такое RAG простыми словами?
RAG — это подход, при котором языковая модель перед ответом ищет релевантные фрагменты во внешнем документе. Это помогает ей отвечать по свежим или специфическим данным, а не по обучающей выборке.
2
Можно ли обойтись без внешнего API для извлечения текста?
Да. Вместо Web to Markdown/JSON API можно использовать библиотеку trafilatura, сервис r.jina.ai или написать собственный парсер на BeautifulSoup. Главное — получить чистый текст без навигации и рекламы.
3
Почему для эмбеддингов и ответов используется Ollama?
Ollama запускает модели локально, бесплатно и без облачных API-ключей. Для учебных и pet-проектов этого достаточно, а при необходимости легко перейти на коммерческий провайдер.
4
Как повысить качество ответов?
Три очевидных рычага: улучшить очистку текста, подобрать размер чанков и увеличить число релевантных фрагментов в промпте. Также помогает явная инструкция модели отвечать только по контексту.
5
Какие сайты подходят для такого бота?
Подходят любые текстовые страницы: статьи, документация, справочники, блоги. Сложности возникают со страницами, где весь контент загружается JavaScript, или с капчами — там нужен рендеринг и прокси.
RAG — не магия, а последовательность простых шагов: получить чистый текст, разрезать его на фрагменты, найти ближайшие к вопросу и отдать их модели. Весь минимальный пайплайн укладывается в короткий Python-скрипт, который можно запустить на своём ноутбуке.
RAG работает ровно так хорошо, каков текст, который вы ему скармливаете. Уберите самую утомительную часть — очистку HTML, — и останется интересное: поиск и генерация.
bao001 xiaoавтор туториала на DEV Community
Исходник идеи — статья «Chat With Any Website: Build a RAG Bot in ~60 Lines of Python». Попробуйте собрать бота на своей странице и посмотрите, где он справляется, а где начинает фантазировать.
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | Ваши open source-контрибьюторы теперь ИИ-first. Как не утонуть в потоке агентских пулреквестов | 0 | 11.9 | 19-08-2026 |
| 2 | Как написать отчёт по практике с помощью ИИ и готовых промптов? | 5 | 7 | 02-07-2026 |
| 3 | ИИ для автоматизации РКО. Как финансовые организации сокращают расходы и убирают ручной ввод документов | 5 | 7 | 01-07-2026 |
| 4 | Fine-tuning LLM в 2026: гид по LoRA, QLoRA и полному дообучению | 0 | 7 | 25-06-2026 |
| 5 | Распознавание документов в браузере с оценкой качества изображения: пошаговая интеграция | 0 | 5.5 | 26-08-2026 |
| 6 | Как мы детектили фрод, а получили Джонни и Джулию: история одной браузерной разминки | 0 | 4.75 | 17-08-2026 |
| 7 | Как мы создаём аналог Vercel, Render, Railway, Netlify и PythonAnywhere | 0 | 4.16 | 27-08-2026 |
| 8 | Собери программиста в отпуск: игра-квест от Тproger | 0 | 9.84 | 21-08-2026 |
| 9 | 7 стратегий модернизации легаси: что рефакторить, переносить, заменять или переписывать | 0 | 4.4 | 25-08-2026 |
| 10 | Пошаговые диалоги в Python без боли: описываем визарды в JSON, а не в if-ах | 0 | 10.65 | 21-04-2026 |