Заметки с практики · Сергей Несмачный и команда
Делаем сайты и веб-приложения для бизнеса в России и записываем, что на деле работает.

Аналитика и 152-ФЗ · 02.10.2026
В ответе ИИ три названия. Как узнать, есть ли там вы
Видимость в ответах Алисы, ChatGPT, Perplexity и нейро-блоках Яндекса можно измерить, а не угадывать. Семь показателей, правило трёх повторов и причина, по которой цифра такая.
Ещё новое
CMS и хостинг · 29.09.2026
Cloudflare выпустила EmDash, и это начало конца эпохи WordPress

Кейсы · 11.09.2026
Как новый сайт попал в ответы ChatGPT за 17 дней

Аналитика и 152-ФЗ · 06.09.2026
7 альтернатив Google Analytics с учётом 152-ФЗ (2026)

CMS и хостинг · 11.08.2026
Появилась ли наконец настоящая альтернатива WordPress? (2026)

Статьи по темам

AI-разработка · 26.06.2026
Создайте RAG-чатбот на основе контента WordPress (2026)
# Build a RAG Chatbot on WordPress Content: Complete Step-by-Step Guide ## What You'll Build A RAG (Retrieval-Augmented Generation) chatbot that: - Fetches WordPress posts via REST API - Converts posts into embeddings - Stores embeddings in Qdrant vector database - Retrieves relevant posts for user queries - Generates answers with proper citations ## Prerequisites - WordPress site with REST API enabled (WordPress 4.7+) - Python 3.10+ - Qdrant instance (local or cloud) - OpenAI API key (or alternative LLM) - Basic knowledge of Python and APIs ## Architecture Overview ``` WordPress REST API ↓ Python Script ↓ Embeddings (OpenAI) ↓ Qdrant Storage ↓ RAG Pipeline ↓ LLM Response + Citations ``` ## Step 1: Set Up Your Environment ### Install Required Dependencies ```bash pip install requests python-dotenv qdrant-client openai langchain python-dotenv ``` ### Create Environment File Create `.env`: ``` WORDPRESS_URL=https://your-wordpress-site.com WORDPRESS_USERNAME=your_username WORDPRESS_PASSWORD=your_password OPENAI_API_KEY=your_openai_key QDRANT_URL=http://localhost:6333 QDRANT_API_KEY=your_qdrant_key_or_leave_empty ``` ## Step 2: Fetch WordPress Posts via REST API ### Create WordPress Data Fetcher Create `fetch_wordpress.py`: ```python import requests import json from typing import List, Dict import os from dotenv import load_dotenv load_dotenv() class WordPressFetcher: def __init__(self, base_url: str): self.base_url = base_url.rstrip('/') self.rest_endpoint = f"{self.base_url}/wp-json/wp/v2" def fetch_all_posts(self, per_page: int = 100) -> List[Dict]: """ Fetch all posts from WordPress using pagination """ posts = [] page = 1 while True: url = f"{self.rest_endpoint}/posts" params = { 'per_page': per_page, 'page': page, 'status': 'publish' } response = requests.get(url, params=params) if response.status_code != 200: print(f"Error fetching page {page}: {response.status_code}") break page_posts = response.json() if not page_posts: break posts.extend(page_posts) page += 1 print(f"Fetched page {page - 1}, total posts: {len(posts)}") return posts def extract_post_content(self, post: Dict) -> Dict: """ Extract relevant content from WordPress post """ return { 'id': str(post['id']), 'title': post['title']['rendered'], 'content': post['content']['rendered'], 'excerpt': post['excerpt']['rendered'], 'url': post['link'], 'date': post['date'], 'author': post['author'], 'categories': post.get('categories', []) } def clean_html(self, html_content: str) -> str: """ Remove HTML tags from content """ from html.parser import HTMLParser class MLStripper(HTMLParser): def __init__(self): super().__init__() self.reset() self.strict = False self.convert_charrefs = True self.text = [] def handle_data(self, d): self.text.append(d) def get_data(self): return ''.join(self.text) stripper = MLStripper() stripper.feed(html_content) return stripper.get_data() # Usage if __name__ == "__main__": wordpress_url = os.getenv('WORDPRESS_URL') fetcher = WordPressFetcher(wordpress_url) posts = fetcher.fetch_all_posts() print(f"Total posts fetched: {len(posts)}") # Save to file for inspection with open('wordpress_posts.json', 'w') as f: json.dump([fetcher.extract_post_content(p) for p in posts], f, indent=2) ``` ### Run the Fetcher ```bash python fetch_wordpress.py ``` This creates `wordpress_posts.json` with all your posts. ## Step 3: Create Embeddings ### Set Up Embedding Generator Create `embeddings.py`: ```python from openai import OpenAI import os from dotenv import load_dotenv load_dotenv() class EmbeddingGenerator: def __init__(self, api_key: str = None): self.client = OpenAI(api_key=api_key or os.getenv('OPENAI_API_KEY')) self.model = "text-embedding-3-small" def generate_embedding(self, text: str) -> list: """ Generate embedding for a single text """ response = self.client.embeddings.create( input=text, model=self.model ) return response.data[0].embedding def generate_batch_embeddings(self, texts: list) -> list: """ Generate embeddings for multiple texts (batch is more efficient) """ response = self.client.embeddings.create( input=texts, model=self.model ) return [item.embedding for item in response.data] # Test if __name__ == "__main__": generator = EmbeddingGenerator() test_embedding = generator.generate_embedding("Test content for embedding") print(f"Embedding dimension: {len(test_embedding)}") print(f"First 5 values: {test_embedding[:5]}") ``` ## Step 4: Set Up Qdrant Vector Database ### Start Qdrant Locally (Docker) ```bash docker run -p 6333:6333 -p 6334:6334 \ -v /path/to/qdrant/storage:/qdrant/storage:z \ qdrant/qdrant:latest ``` Access Qdrant dashboard at `http://localhost:6333/dashboard` ### Create Qdrant Manager Create `qdrant_manager.py`: ```python from qdrant_client import QdrantClient from qdrant_client.models import Distance, VectorParams, PointStruct from typing import List, Dict, Tuple import os from dotenv import load_dotenv load_dotenv() class QdrantManager: def __init__(self, url: str = None, api_key: str = None): self.url = url or os.getenv('QDRANT_URL', 'http://localhost:6333') self.api_key = api_key or os.getenv('QDRANT_API_KEY') self.client = QdrantClient( url=self.url, api_key=self.api_key if self.api_key else None ) self.collection_name = "wordpress_posts" def create_collection(self, vector_size: int = 1536): """ Create collection for storing embeddings 1536 is the dimension for text-embedding-3-small """ try: self.client.recreate_collection( collection_name=self.collection_name, vectors_config=VectorParams(size=vector_size, distance=Distance.COSINE), ) print(f"Collection '{self.collection_name}' created") except Exception as e: print(f"Error creating collection: {e}") def add_posts(self, posts: List[Dict], embeddings: List[List[float]]): """ Add posts with their embeddings to Qdrant """ points = [] for idx, (post, embedding) in enumerate(zip(posts, embeddings)): point = PointStruct( id=int(post['id']), vector=embedding, payload={ 'title': post['title'], 'content': post['content'], 'excerpt': post['excerpt'], 'url': post['url'], 'date': post['date'], } ) points.append(point) self.client.upsert( collection_name=self.collection_name, points=points, ) print(f"Added {len(points)} posts to Qdrant") def search(self, query_embedding: List[float], limit: int = 5) -> List[Dict]: """ Search for similar posts """ results = self.client.search( collection_name=self.collection_name, query_vector=query_embedding, limit=limit, ) retrieved_posts = [] for result in results: retrieved_posts.append({ 'id': result.id, 'score': result.score, 'title': result.payload['title'], 'content': result.payload['content'], 'url': result.payload['url'], 'date': result.payload['date'], }) return retrieved_posts def get_collection_info(self): """ Get collection statistics """ info = self.client.get_collection(self.collection_name) return info # Test if __name__ == "__main__": manager = QdrantManager() info = manager.get_collection_info() print(f"Collection info: {info}") ``` ## Step 5: Index WordPress Posts into Qdrant ### Create Indexing Script Create `index_posts.py`: ```python import json from fetch_wordpress import WordPressFetcher from embeddings import EmbeddingGenerator from qdrant_manager import QdrantManager import os from dotenv import load_dotenv from html.parser import HTMLParser load_dotenv() class HTMLStripper(HTMLParser): def __init__(self): super().__init__() self.reset() self.strict = False self.convert_charrefs = True self.text = [] def handle_data(self, d): self.text.append(d) def get_data(self): return ''.join(self.text) def strip_html(html): stripper = HTMLStripper() stripper.feed(html) return stripper.get_data() def index_wordpress_posts(): """ Main indexing pipeline """ print("Step 1: Fetching WordPress posts...") wordpress_url = os.getenv('WORDPRESS_URL') fetcher = WordPressFetcher(wordpress_url) raw_posts = fetcher.fetch_all_posts() # Extract and clean content posts = [] for raw_post in raw_posts: post = fetcher.extract_post_content(raw_post) post['content'] = strip_html(post['content']) post['excerpt'] = strip_html(post['excerpt']) posts.append(post) print(f"Extracted {len(posts)} posts") print("\nStep 2: Generating embeddings...") generator = EmbeddingGenerator() # Combine title and content for embedding texts_to_embed = [ f"{post['title']}\n{post['content'][:500]}" # Use first 500 chars for post in posts ] embeddings = generator.generate_batch_embeddings(texts_to_embed) print(f"Generated {len(embeddings)} embeddings") print("\nStep 3: Indexing into Qdrant...") manager = QdrantManager() manager.create_collection(vector_size=1536) manager.add_posts(posts, embeddings) print("\nStep 4: Verifying...") info = manager.get_collection_info() print(f"Collection has {info.points_count} points") print("✓ Indexing complete!") if __name__ == "__main__": index_wordpress_posts() ``` ### Run the Indexer ```bash python index_posts.py ``` This will: - Fetch all posts - Generate embeddings - Store in Qdrant - Display progress ## Step 6: Build the RAG Chatbot ### Create RAG Pipeline Create `rag_chatbot.py`: ```python from embeddings import EmbeddingGenerator from qdrant_manager import QdrantManager from openai import OpenAI from typing import List, Dict, Tuple import os from dotenv import load_dotenv load_dotenv() class RAGChatbot: def __init__(self): self.embedding_generator = EmbeddingGenerator() self.qdrant_manager = QdrantManager() self.llm_client = OpenAI(api_key=os.getenv('OPENAI_API_KEY')) self.model = "gpt-4o-mini" def retrieve_relevant_posts(self, query: str, top_k: int = 5) -> List[Dict]: """ Retrieve relevant posts for a query """ # Generate embedding for the query query_embedding = self.embedding_generator.generate_embedding(query) # Search in Qdrant results = self.qdrant_manager.search(query_embedding, limit=top_k) return results def generate_answer_with_citations( self, query: str, retrieved_posts: List[Dict] ) -> Tuple[str, List[Dict]]: """ Generate answer using LLM with citations """ # Format context from retrieved posts context = self._format_context(retrieved_posts) # Create prompt system_prompt = """You are a helpful assistant answering questions about WordPress blog posts. Use the provided post content to answer questions accurately. Always cite the source posts by title and URL in your response. If you can't find relevant information, say so explicitly.""" user_prompt = f"""Based on the following posts, answer the question: "{query}" POSTS: {context} Please provide a comprehensive answer with citations to the source posts.""" # Call LLM response = self.llm_client.chat.completions.create( model=self.model, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], temperature=0.7, max_tokens=1000 ) answer = response.choices[0].message.content return answer, retrieved_posts def _format_context(self, posts: List[Dict]) -> str: """ Format retrieved posts into a context string """ context_parts = [] for i, post in enumerate(posts, 1): context_parts.append(f""" Post {i}: {post['title']} URL: {post['url']} Relevance Score: {post['score']:.3f} Content: {post['content'][:800]}... ---""") return "\n".join(context_parts) def chat(self, query: str, top_k: int = 5) ->

ИИ-поиск · 15.06.2026
Почему ваш сайт невидим для ChatGPT (и как это исправить за 14 дней)
ИИ-ассистенты теперь решают, кого рекомендовать. Большинство сайтов для них нечитаемы. Вот 5 бесплатных проверок и как работает разовое техническое исправление.
AI-разработка · 02.05.2026
WordPress как бэкенд для RAG: Как мы это сделали и почему это работает
Как мы сделали из WordPress базу знаний для ИИ-ассистента: публичные и приватные страницы, вебхук, Qdrant и гибридный поиск. Что сработало на практике.
CMS и хостинг · 17.03.2026
Как я настроил бесплатную профессиональную почту для своего стартапа с помощью Cloudflare (и вы тоже можете)
Каждый стартап нуждается в профессиональном адресе электронной почты. Никто не воспринимает support@gmail.com серьезно. Но платить $6–12 за пользователя в месяц за Google Workspace или Microsoft 365 только для того, чтобы получать письма на собственном домене, кажется ранней налогом на стартап, который вам не следует…

Аналитика и 152-ФЗ · 26.02.2026
Почему мы создаём свою альтернативу Google Analytics?
Коротко: Google Analytics отправляет данные россиян за рубеж — это риск по 152-ФЗ, плюс часть визитов теряется из-за баннеров согласия. Поэтому мы сделали Web-Metrics.ru: аналитику, которая хранит данные в России, работает без cookies и без баннера. Проблема: российский бизнес работает на зарубежной аналитике…

AI-разработка · 10.02.2026
EasyTalk: как я создал плагин для WordPress, чтобы превращать статьи в аудио с помощью ElevenLabs
Кратко: Я хотел, чтобы мои блог-посты можно было не только читать, но и слушать. Я создал плагин для WordPress, который преобразует посты в естественно звучащее аудио с использованием API ElevenLabs. Он работает с многоязычными сайтами и позволяет посетителям прослушивать посты одним нажатием. Проблема: Люди не всегда…
Следующая заметка на почту
Делаем сайты и SaaS и пишем про стек, SEO, аналитику и ИИ. Без спама, только то, что проверили сами.
9 живых сайтов
клиентские и наши продукты
До 7 языков
на одном сайте, одна CMS
152-ФЗ по умолчанию
хостинг в России, аналитика без cookies
20+ лет в IT
работаем с клиентами по всей России
Сайты, из которых выросли заметки
Все работыЖивые сайты финтеха, голосового ИИ-ассистента, фармгруппы, B2B-дистрибьютора и личного бренда. Все открываются, можно проверить.

WiFly
Платформа Wi-Fi-маркетинга · Россия

ClientPulse
Платформа клиентских данных · Россия

CallEmily
AI-голосовой ассистент · Россия

Royal Ride
Премиум-трансферы · Санкт-Петербург

TutWiFi
Бесплатный публичный Wi-Fi · Россия

WifiSpeed
Замер скорости Wi-Fi · Россия

Marbella34
Недвижимость · Марбелья

Sveta Vasilyeva
Персональный стилист · Лиссабон
Что мы делаем
Услуги и ценыСайты и лендинги для бизнеса
Понятная структура, сильные тексты, форма, которая приходит в почту или CRM.
Многоязычные сайты на headless CMS
До семи языков на одном бэкенде: EmDash на Cloudflare или headless WordPress.
E-commerce и каталоги
Интернет-магазины, B2B-каталоги с запросом КП, платежи и остатки.
Веб-приложения, SaaS и AI-интеграции
Дашборды, калькуляторы, личные кабинеты, чат- и голосовые ассистенты.
Как мы работаем
- 01
Разбор и предложение
Пишете пару строк. За 24 часа получаете вопросы или предложение с объёмом, оценкой и сроками.
- 02
Дизайн и разработка
Демо на staging-адресе каждую неделю с первого дня. Дизайн, контент, интеграции и переводы идут параллельно.
- 03
Запуск и рост
К запуску готовы аналитика, разметка и готовность к ИИ-поиску, дальше улучшаем по реальным цифрам.
Хотите такой же результат на своём сайте?
Напишите пару строк о задаче. Ответим в течение 24 часов со следующими шагами.






