Extract shared proxy module; make it available to direct scraper; remove dead code

Proxy config, DataImpulse sticky-session username syntax, and Playwright/
httpx proxy builders now live in one place (services/proxy.py) instead of
being duplicated across the Booking.com backend and the /config/proxy test
endpoint. Both scrapers consume it.

- services/proxy.py: load_config/normalize (DB-authoritative, env fallback),
  new_session_id, username, playwright_proxy, httpx_proxy_url
- PlaywrightLocalBackend delegates proxy building to the module
- get_scraper_backend factory uses proxy.load_config (one resolution path)
- test_proxy_config endpoint uses the shared URL builder; httpx proxies= ->
  proxy= (forward-compatible, 0.28-safe)
- Direct booking-engine scraper (httpx) can now route through the same proxy,
  gated by the direct_scraper_use_proxy flag (default off, plumbing ready)

Dead code removed: set_scraper_paused (never called — rotate-on-block
replaced pause-on-block), get_competitor_matrix / get_hotels_list /
update_hotel_tier (endpoints have their own SQL), unused PROXY_KEYS tuple.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-05 22:55:24 +00:00
parent 6b7f00b40a
commit 1dc8c0a945
5 changed files with 147 additions and 194 deletions

View file

@ -12,6 +12,7 @@ import logging
from database import get_db, SyncSessionLocal
from auth import get_current_user
from services import proxy as proxy_util
router = APIRouter()
logger = logging.getLogger(__name__)
@ -188,12 +189,6 @@ async def set_system_config(
# ── Booking.com scraper proxy config ─────────────────────────────────────────
PROXY_KEYS = (
'booking_proxy_enabled', 'booking_proxy_host', 'booking_proxy_port',
'booking_proxy_username', 'booking_proxy_password', 'booking_proxy_country',
)
class ProxyConfig(BaseModel):
enabled: bool = False
host: str = ''
@ -265,22 +260,24 @@ async def test_proxy_config(
result = await db.execute(
text("SELECT config_key, config_value FROM system_config WHERE config_key LIKE 'booking_proxy_%'")
)
cfg = {row.config_key: row.config_value for row in result.fetchall()}
host = (cfg.get('booking_proxy_host') or '').strip()
username = (cfg.get('booking_proxy_username') or '').strip()
password = (cfg.get('booking_proxy_password') or '').strip()
if not (host and username and password):
raw = {row.config_key: row.config_value for row in result.fetchall()}
# Test whatever creds are stored, regardless of the enabled toggle, so the
# user can verify before switching the proxy on.
cfg = {
'host': (raw.get('booking_proxy_host') or '').strip(),
'port': (raw.get('booking_proxy_port') or '823').strip(),
'username': (raw.get('booking_proxy_username') or '').strip(),
'password': (raw.get('booking_proxy_password') or '').strip(),
'country': (raw.get('booking_proxy_country') or 'gb').strip(),
}
if not (proxy_util.is_enabled(cfg) and cfg['password']):
raise HTTPException(status_code=400, detail="Proxy host, username and password must be saved first.")
port = (cfg.get('booking_proxy_port') or '823').strip()
country = (cfg.get('booking_proxy_country') or 'gb').strip()
proxy_user = f"{username}__cr.{country};sessid.hnftest"
proxy_url = f"http://{proxy_user}:{password}@{host}:{port}"
proxy_url = proxy_util.httpx_proxy_url(cfg, proxy_util.new_session_id())
import httpx
try:
async with httpx.AsyncClient(proxies=proxy_url, timeout=40.0) as client:
async with httpx.AsyncClient(proxy=proxy_url, timeout=40.0) as client:
resp = await client.get("https://ipinfo.io/json")
resp.raise_for_status()
data = resp.json()