OLX → Telegram Бот: Как автоматизировать поиск квартир в Ташкенте и отсеять маклеров
Задача
В Узбекистане снять квартиру через OLX — это лотерея. 80% объявлений размещают маклеры, которые берут комиссию 50–100% от месячной аренды. Каждое объявление — это либо посредник, либо перекуп, либо просто бот, который дублирует чужие фотографии.
Мне нужна была квартира в Ташкенте: Алмазарский район, 2–3 комнаты, цена $400–500 в месяц. Без посредников. И я решил написать бота, который сам бы парсил OLX и отсеивал маклеров.
Как мы строили систему
Бот работает на Python с библиотекой python-telegram-bot. Парсинг OLX через curl_cffi — обычный requests блокируется CloudFront.
Архитектура
text┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Telegram Bot │────▶│ OLX Parser │────▶│ SQLite DB │
│ (user-facing) │ │ (curl_cffi) │ │ (ads, users) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
▼ ▼
JobQueue (час) HTML/API парсинг
broadcast_new_ads (дата, телефон, автор)
Стек
- Python 3.12 +
python-telegram-bot[job-queue] curl_cffi— обход CloudFront- BeautifulSoup4 — парсинг HTML
- SQLite — хранение объявлений и подписчиков
- Systemd — автозапуск как службы
- APScheduler — ежечасная рассылка
Проблема №1: OLX использует CloudFront
Обычный requests выдаёт 403. Классический requests не мог пройти защиту CloudFront. Мы использовали curl_cffi, который эмулирует TLS-отпечаток браузера.
Решение:
pythonfrom curl_cffi import requests
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
resp = requests.get(url, headers=HEADERS, impersonate="chrome", timeout=30)
impersonate="chrome" — ключевой параметр, который обходит CloudFront.
Проблема №2: API OLX не принимает строковые ID
Бот парсил URL ...-ID4qKuF.html, но API /api/v1/offers/4qKuF/ выдавал 404. Оказалось, что API принимает только числовые ID, а на странице они скрыты в HTML.
Решение: Загружаем страницу объявления и ищем числовой ID в data-id или в JSON-LD ("sku"):
pythondef extract_numeric_id(html):
# Ищем в JSON-LD
m = re.search(r'"sku"\s*:\s*"(\d+)"', html)
if m:
return m.group(1)
# Ищем data-id
m = re.search(r'data-id=["\'](\d+)["\']', html)
return m.group(1) if m else None
Теперь бот находит числовой ID и делает запрос к API.
Проблема №3: Телефон скрыт за кнопкой «Показать»
На OLX номер телефона отображается только после клика. Бот не умеет эмулировать клики, но данные всё равно есть в JSON на странице или в API.
Решение: Парсим телефон из JSON __PRERENDERED_STATE__ или из API после получения числового ID:
pythondef fetch_phone(ad_url, numeric_id):
# 1. Через API
data = requests.get(f"https://www.olx.uz/api/v1/offers/{numeric_id}/").json()
for p in data.get("params", []):
if p.get("key") == "phone":
return p.get("value", {}).get("label")
# 2. Через HTML (запасной вариант)
html = requests.get(ad_url).text
m = re.search(r'"label":\s*"([\d\s+]+)"', html)
return m.group(1) if m else None
Проблема №4: Как определить маклера?
На OLX нет тега «маклер». Но есть индикаторы:
- Количество объявлений автора > 3 → почти всегда маклер.
- Один номер телефона повторяется в 3+ объявлениях → 100% маклер.
Решение: Два прохода:
- Собираем все телефоны из найденных объявлений.
- Помечаем маклеров, где телефон встречается чаще 3 раз.
pythonphone_to_ads = {}
for ad in ads:
phone = fetch_phone(ad.url, ad.id)
phone_to_ads.setdefault(phone, []).append(ad.id)
for ad in ads:
if len(phone_to_ads.get(ad.phone, [])) > 3:
ad.is_realtor = True # маклер
Проблема №5: Даты публикации в разном формате
На OLX даты могут быть:
- «Опубликовано сегодня в 16:11»
- «Опубликовано 27 июля 2026 г.»
- «Вчера в 10:30»
Решение: Регулярки с преобразованием в ISO-формат:
pythondef parse_date(text):
patterns = [
(r'Опубликовано\s+сегодня\s+в\s+(\d{1,2}):(\d{2})', 'today'),
(r'Опубликовано\s+(\d{1,2})\s+(\w+)\s+(\d{4})\s+г\.', 'full'),
]
for pattern, ptype in patterns:
m = re.search(pattern, text)
if m:
# преобразуем в ISO
return datetime(...).isoformat()
Проблема №6: Бот зависал при установке команд
При запуске asyncio.run(set_commands(app)) создавался отдельный event loop, который конфликтовал с основным. Бот падал с RuntimeError: Event loop is closed.
Решение: Использовали post_init callback:
pythonasync def post_init(app):
await app.bot.set_my_commands(commands)
app.post_init = post_init
Теперь команды устанавливаются корректно, без конфликтов.
Проблема №7: Автоматическая рассылка без дублей
Каждый час бот проверяет новые объявления и отправляет подписчикам. Чтобы не дублировать, используем seen.json.
Решение: Сохраняем ID отправленных объявлений и фильтруем:
pythonseen = json.load(open('seen.json'))
new_ads = [ad for ad in raw_ads if ad.id not in seen]
# отправляем
seen.update([ad.id for ad in new_ads])
json.dump(list(seen), open('seen.json', 'w'))
Функционал бота
Бот умеет:
text/start — Главное меню с датой последнего сканирования
/search — Поиск новых объявлений за 2 дня с сортировкой по дате
/recent — Показать последние найденные из БД
/subscribe — Подписаться на ежечасную рассылку
/unsubscribe — Отписаться
/status — Проверить подписку
Пример сообщения:
text🏠 OLX Поиск квартир
Я ищу объявления о долгосрочной аренде квартир в Ташкенте
и определяю, маклер это или владелец.
📌 Команды:
/search — найти новые объявления за последние 2 дня
/recent — показать последние найденные объявления
/subscribe — подписаться на ежечасную рассылку
/unsubscribe — отписаться от рассылки
/status — ваш статус подписки
🕒 Последнее сканирование: 2026-07-30T10:09:38.317900
Вывод объявления:
textОбъявление
💰 500 у.е.
🔗 https://www.olx.uz/d/obyavlenie/...
👤 Иван Петров
📦 Объявлений автора: 12
🔴 МАКЛЕР (более 3 объявлений)
📞 998901234567
📅 30.07.2026 16:11
Автозапуск как systemd-служба
Создаём /etc/systemd/system/olx-bot.service:
ini[Unit]
Description=OLX Telegram Bot
After=network-online.target
[Service]
Type=simple
User=root
WorkingDirectory=/bots/olxsearch
ExecStart=/bots/olxsearch/venv/bin/python /bots/olxsearch/bot.py
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Управление:
bashsudo systemctl enable --now olx-bot
sudo systemctl status olx-bot
journalctl -u olx-bot -f
Итоговый результат
- ✅ Бот парсит OLX каждые 2 секунды (с задержками) и не банится.
- ✅ Маклеры помечаются красным.
- ✅ Телефоны и даты извлекаются даже со скрытых полей.
- ✅ Ежечасная рассылка подписчикам.
- ✅ Работает в любой момент по команде
/search. - ✅ Квартиры сортируются по свежести.
За неделю бот отсеял 28 маклеров и показал только 40 объявлений от реальных владельцев.
Примеры команд
Поиск квартиры:
text/search
🔍 Ищу новые объявления... Это может занять некоторое время.
📊 Результаты поиска
Всего: 100
🔴 Маклеры: 28
🟢 Владельцы: 72
⚪ Не определено: 0
Выберите, что показать:
[🔴 Только маклеры] [🟢 Только владельцы] [📋 Все объявления]
Подписка на рассылку:
text/subscribe
✅ Вы подписались на ежечасную рассылку новых объявлений.
Ежечасный отчёт:
text🔄 Ежечасная проверка объявлений
📅 Время: 30.07.2026 10:09
📦 Найдено новых объявлений: 1
👥 Подписчиков: 1