Как с точки зрения программирования можно получать...
생성일: 2025년 10월 2일
생성일: 2025년 10월 2일
Как с точки зрения программирования можно получать актуальные процентные ставки по кредитам в разных банках (Допустим что вся эта информация есть на одном сайте)
Ниже — практический план «с нуля», как программно получать и поддерживать актуальные ставки по кредитам, если все данные есть на одном сайте.
Путь A: есть официальный API
application/ld+json.Путь B: нет API → парсинг (scraping)
requests/httpx + BeautifulSoup/lxml.Юридика/этика: прочитайте правила сайта (Terms, robots.txt), ставьте честный User-Agent, соблюдайте rate-limit.
Минимум — «что за продукт и когда действовало»:
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.
pythonimport 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) # вежливый троттлинг
Playwright надежнее для динамики и XHR:
pythonimport 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).
raw_hash.updated_at.bank_name,product).Пример запроса «актуальных» ставок:
sqlSELECT 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;
tenacity/свой декоратор на 5xx/таймауты.ETag/Last-Modified (если сервер их отдает).rate_min ≤ rate_max, валюта из белого списка, диапазоны сроков неотрицательны./rates?bank=...&product=... с кэшированием.rate_conditions.Если дадите пример ссылки/структуры страницы — адаптирую парсер под реальную разметку и покажу конкретные CSS-селекторы/правила извлечения.