Как с точки зрения программирования можно получать...

Creato il: 2 ottobre 2025

Risposto usando GPT-5 da Chat01

Domanda

Как с точки зрения программирования можно получать актуальные процентные ставки по кредитам в разных банках (Допустим что вся эта информация есть на одном сайте)

Pensiero

Outlining programming solution for loan rate retrieval

The user needs a system to retrieve loan rates from a site, involving scraping, API usage, handling pagination, rate limiting, and more. I'll break things down: first, scraping methods using tools like Python's Requests, BeautifulSoup, or Selenium as fallback. I'll include considerations like legal aspects (robots.txt), pagination, dynamic content, change detection, and handling currency nuances (APR vs nominal). I'll also include a cron job, data normalization, and API integration when possible.

Thought for 7s

Risposta

Ниже — практический план «с нуля», как программно получать и поддерживать актуальные ставки по кредитам, если все данные есть на одном сайте.

1) Выяснить «как кормить» данные

Путь A: есть официальный API

  • Ищем REST/GraphQL эндпоинты, OpenAPI, RSS/JSON feeds, application/ld+json.
  • Плюсы: стабильность, меньше блокировок.
  • Минусы: иногда есть лимиты/ключи.

Путь B: нет API → парсинг (scraping)

  • HTML-страницы: requests/httpx + BeautifulSoup/lxml.
  • Динамический контент (React/Vue): Playwright/Selenium для рендеринга JS.
  • Таблицы/фильтры: ловим XHR-запросы в DevTools — часто там «чистый» JSON.

Юридика/этика: прочитайте правила сайта (Terms, robots.txt), ставьте честный User-Agent, соблюдайте rate-limit.


2) Спроектировать модель данных

Минимум — «что за продукт и когда действовало»:

sql
-- PostgreSQL CREATE TABLE loan_rates ( id BIGSERIAL PRIMARY KEY, bank_name TEXT NOT NULL, product TEXT NOT NULL, -- например "Потребительский", "Ипотека", "АВТО" rate_min NUMERIC(5,2), -- мин. ставка, % rate_max NUMERIC(5,2), -- макс. ставка, % apr NUMERIC(5,2), -- эффективная ставка (если есть) currency TEXT NOT NULL, -- "RUB", "USD", … term_min_months INT, term_max_months INT, updated_at TIMESTAMPTZ NOT NULL, -- когда увидели на сайте source_url TEXT NOT NULL, raw_hash TEXT NOT NULL -- контроль изменения ); CREATE UNIQUE INDEX uq_rates_version ON loan_rates (bank_name, product, updated_at);

Нормализуйте: единые единицы (проценты/месяцы), валюта, диапазоны. Если сайт дает только «от N%», кладите в rate_min и оставляйте rate_max = NULL.


3) Экстракция: базовый скрапер (статичный HTML)

python
import hashlib, time from datetime import datetime, timezone import requests from bs4 import BeautifulSoup HEADERS = { "User-Agent": "RatesBot/1.0 ([email protected])" } def fetch(url: str) -> str: r = requests.get(url, headers=HEADERS, timeout=20) r.raise_for_status() return r.text def parse_rates(html: str, base_url: str): """ Пример: разбираем таблицу <tr> с колонками: Банк | Продукт | Ставка | Срок | Валюта Настройте CSS-селекторы под реальную верстку. """ soup = BeautifulSoup(html, "lxml") rows = [] for tr in soup.select("table.rates > tbody > tr"): tds = tr.find_all("td") if len(tds) < 5: continue bank = tds[0].get_text(strip=True) product = tds[1].get_text(strip=True) # Примеры форматов: "от 12.5%", "12.5–18%", "12.5 %" rate_text = tds[2].get_text(" ", strip=True).replace(",", ".") rate_min, rate_max = parse_rate_range(rate_text) term_text = tds[3].get_text(" ", strip=True) term_min, term_max = parse_term(term_text) currency = normalize_currency(tds[4].get_text(strip=True)) row = { "bank_name": bank, "product": product, "rate_min": rate_min, "rate_max": rate_max, "apr": None, # если сайт дает APR — парсите и кладите сюда "currency": currency, "term_min_months": term_min, "term_max_months": term_max, "source_url": base_url, } row["raw_hash"] = make_hash(row) row["updated_at"] = datetime.now(timezone.utc).isoformat() rows.append(row) return rows def parse_rate_range(text: str): # очень простая эвристика, подстройте под реальные форматы import re nums = [float(x) for x in re.findall(r"\d+(?:\.\d+)?", text)] if "–" in text or "-" in text: return (nums[0], nums[1] if len(nums) > 1 else nums[0]) if "от" in text.lower(): return (nums[0], None) return (nums[0] if nums else None, nums[1] if len(nums) > 1 else None) def parse_term(text: str): # Поддержка "12–60 мес", "до 84 мес", "3 года" import re t = text.lower() if "год" in t: # переводим годы в месяцы years = [int(x) for x in re.findall(r"\d+", t)] if "–" in t or "-" in t and len(years) >= 2: return years[0]*12, years[1]*12 if "до" in t and years: return None, years[0]*12 return years[0]*12, None months = [int(x) for x in re.findall(r"\d+", t)] if "–" in t or "-" in t and len(months) >= 2: return months[0], months[1] if "до" in t and months: return None, months[0] return (months[0], None) if months else (None, None) def normalize_currency(cur: str): cur = cur.upper() if "₽" in cur or "RUB" in cur or "RUR" in cur: return "RUB" if "$" in cur or "USD" in cur: return "USD" if "€" in cur or "EUR" in cur: return "EUR" return cur def make_hash(row: dict) -> str: # Хэшируем ключевые поля для отслеживания изменений payload = "|".join([ row["bank_name"], row["product"], str(row["rate_min"]), str(row["rate_max"]), row["currency"], str(row["term_min_months"]), str(row["term_max_months"]) ]) return hashlib.sha256(payload.encode("utf-8")).hexdigest() if __name__ == "__main__": URL = "https://example.com/loans" # замените на реальный html = fetch(URL) data = parse_rates(html, URL) for d in data: print(d) time.sleep(1) # вежливый троттлинг

4) Если страница рендерится JS (SPA)

Playwright надежнее для динамики и XHR:

python
import asyncio from playwright.async_api import async_playwright async def fetch_js(url: str) -> str: async with async_playwright() as p: browser = await p.chromium.launch(headless=True) page = await browser.new_page(user_agent="RatesBot/1.0 ([email protected])") await page.goto(url, wait_until="networkidle") # при необходимости: дождаться появления таблицы await page.wait_for_selector("table.rates") html = await page.content() await browser.close() return html # html = asyncio.run(fetch_js("https://example.com/loans")) # далее используем parse_rates(html, url)

Если в DevTools виден XHR на /api/rates?…, лучше ходить прямо туда (получите JSON и избежите парсинга HTML).


5) Дедупликация и «версионирование»

  • При каждой загрузке считайте raw_hash.
  • Если хэш изменился — вставляете новую запись с новым updated_at.
  • Для «текущей» витрины заведите материализованный вид или таблицу c последними значениями по (bank_name,product).

Пример запроса «актуальных» ставок:

sql
SELECT DISTINCT ON (bank_name, product) bank_name, product, rate_min, rate_max, apr, currency, term_min_months, term_max_months, updated_at, source_url FROM loan_rates ORDER BY bank_name, product, updated_at DESC;

6) Надёжность: троттлинг, ретраи, мониторинг

  • Rate limit: задержки 0.5–2с между запросами, экспоненциальные бэкофы.
  • Ретраи: tenacity/свой декоратор на 5xx/таймауты.
  • Кэш: ETag/Last-Modified (если сервер их отдает).
  • Мониторинг: алерт при невозможности спарсить N+ раз, при резкой смене ставки (например, >2 п.п. за день).
  • Логи: складывайте сырой HTML/JSON с меткой времени (ограниченно), чтобы отлаживать изменения верстки.

7) Планировщик

  • cron / systemd timer / Airflow — запуск раз в N часов.
  • Для чувствительных продуктов — чаще (например, каждый час), для ипотеки — реже.

8) Проверка качества и тесты

  • Юнит-тесты для парсеров с «замороженными» HTML-фикстурами.
  • Контрольные инварианты: rate_min ≤ rate_max, валюта из белого списка, диапазоны сроков неотрицательны.
  • Автотест на «слом верстки»: если селекторы ничего не нашли — тест падает.

9) Публикация и доступ

  • REST-эндпоинт типа /rates?bank=...&product=... с кэшированием.
  • Экспорт CSV/Parquet для аналитики.
  • Маленькая админка с diff-просмотром изменений (удобно для вручную проверять резкие скачки).

Нюансы домена

  • Различайте номинальную ставку и APR (ПСК/ЭПС), не смешивайте.
  • Фиксируйте дополнительные условия: «зарплатный клиент», «страхование», «первоначальный взнос». Это может требовать отдельной таблицы rate_conditions.
  • Курсы валют: если продукт в USD/EUR, храните дату курса для пересчетов.

Быстрый чек-лист перед стартом

  1. Есть ли официальный JSON/API? (супер!)
  2. Если нет — можно ли вытащить XHR?
  3. Страница статична? (BS4) или нужна автоматизация? (Playwright)
  4. Вы держите «историю изменений» и «текущую витрину»?
  5. Построены алерты и автотесты на лом верстки?

Если дадите пример ссылки/структуры страницы — адаптирую парсер под реальную разметку и покажу конкретные CSS-селекторы/правила извлечения.

Condividi questa Q&A