"""Avito probe: 1 anchor × 1 page с настраиваемой задержкой. Минимальный dry-run чтобы проверить bans / detection после anti-bot hardening (PR #487) + global delay setting (PR #485). Setup: ssh -f -N -L 15433:172.20.0.2:5432 gendesign cd tradein-mvp/backend $env:DATABASE_URL = 'postgresql+psycopg://tradein:@localhost:15433/tradein' Usage: uv run python ../scripts/probe-avito-1anchor.py uv run python ../scripts/probe-avito-1anchor.py --delay 5 --radius 1000 """ from __future__ import annotations import argparse import asyncio import sys import time import types from pathlib import Path _BACKEND_DIR = Path(__file__).resolve().parent.parent / "backend" if str(_BACKEND_DIR) not in sys.path: sys.path.insert(0, str(_BACKEND_DIR)) for _m in ( "weasyprint", "weasyprint.text", "weasyprint.text.ffi", "weasyprint.css", "weasyprint.css.targets", ): sys.modules[_m] = types.ModuleType(_m) sys.modules["weasyprint"].HTML = type("HTML", (), {"__init__": lambda self, *a, **kw: None}) sys.modules["weasyprint"].CSS = type("CSS", (), {"__init__": lambda self, *a, **kw: None}) from app.core.db import SessionLocal # noqa: E402 from app.services.scrapers.avito import AvitoScraper # noqa: E402 from app.services.scrapers.base import save_listings # noqa: E402 ANCHOR_NAME = "Центр ЕКБ" ANCHOR_LAT = 56.8400 ANCHOR_LON = 60.6050 async def main() -> None: p = argparse.ArgumentParser() p.add_argument("--delay", type=float, default=3.0, help="sec между HTTP requests") p.add_argument("--radius", type=int, default=1500, help="метры") p.add_argument("--pages", type=int, default=1) args = p.parse_args() print( f"=== Avito probe ===\n" f" anchor: {ANCHOR_NAME} ({ANCHOR_LAT}, {ANCHOR_LON})\n" f" radius: {args.radius}m\n" f" pages: {args.pages}\n" f" delay: {args.delay}s\n", flush=True, ) t0 = time.perf_counter() try: async with AvitoScraper() as s: lots = await s.fetch_around( ANCHOR_LAT, ANCHOR_LON, args.radius, pages=args.pages, delay_override_sec=args.delay, ) except Exception as e: # noqa: BLE001 print(f"FETCH FAIL: {type(e).__name__}: {e}", flush=True) return fetched = len(lots) print(f"fetched: {fetched} lots in {time.perf_counter() - t0:.1f}s", flush=True) if fetched == 0: print("⚠️ 0 lots — вероятно 429/ban. Смотри backend logs.", flush=True) return db = SessionLocal() try: ins, upd = save_listings(db, lots) print(f"saved: ins={ins} upd={upd}", flush=True) except Exception as e: # noqa: BLE001 print(f"SAVE FAIL: {type(e).__name__}: {e}", flush=True) finally: db.close() sample = lots[:3] print("\nsample listings:", flush=True) for lot in sample: addr = (lot.address or "")[:60] print( f" source_id={lot.source_id} price={lot.price_rub}₽ " f"rooms={lot.rooms} area={lot.area_m2}м² floor={lot.floor}/{lot.total_floors} " f"addr={addr!r}", flush=True, ) if __name__ == "__main__": asyncio.run(main())