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:
parent
6b7f00b40a
commit
1dc8c0a945
5 changed files with 147 additions and 194 deletions
|
|
@ -7,7 +7,6 @@ No proxy - direct connection. Suitable for low-volume scraping.
|
|||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
from datetime import date
|
||||
|
|
@ -15,6 +14,8 @@ from decimal import Decimal, InvalidOperation
|
|||
from typing import List, Optional
|
||||
from urllib.parse import urlencode, urlparse, parse_qs
|
||||
|
||||
from services import proxy as proxy_util
|
||||
|
||||
|
||||
def location_params_from_url(url: str) -> dict:
|
||||
"""Pull the stable destination params (ss, dest_id, dest_type) out of a
|
||||
|
|
@ -109,8 +110,9 @@ class PlaywrightLocalBackend(ScraperBackend):
|
|||
Args:
|
||||
proxy_config: Optional proxy configuration. Falls back to the
|
||||
BOOKING_PROXY_* environment variables when not supplied.
|
||||
See services/proxy.py for the shared config/build helpers.
|
||||
"""
|
||||
self.proxy_config = proxy_config if proxy_config is not None else self._proxy_from_env()
|
||||
self.proxy_config = proxy_config if proxy_config is not None else proxy_util.config_from_env()
|
||||
self._playwright = None
|
||||
self._browser: Optional[Browser] = None
|
||||
self._context: Optional[BrowserContext] = None
|
||||
|
|
@ -122,40 +124,12 @@ class PlaywrightLocalBackend(ScraperBackend):
|
|||
f"(country={self.proxy_config.get('country', 'gb')}, session={self._session_id})"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _proxy_from_env() -> dict:
|
||||
"""Read proxy settings from BOOKING_PROXY_* env vars (empty = disabled)."""
|
||||
host = os.getenv("BOOKING_PROXY_HOST", "").strip()
|
||||
if not host:
|
||||
return {}
|
||||
return {
|
||||
"host": host,
|
||||
"port": os.getenv("BOOKING_PROXY_PORT", "823").strip(),
|
||||
"username": os.getenv("BOOKING_PROXY_USERNAME", "").strip(),
|
||||
"password": os.getenv("BOOKING_PROXY_PASSWORD", "").strip(),
|
||||
"country": os.getenv("BOOKING_PROXY_COUNTRY", "gb").strip(),
|
||||
}
|
||||
|
||||
def _proxy_enabled(self) -> bool:
|
||||
return bool(self.proxy_config.get("host") and self.proxy_config.get("username"))
|
||||
return proxy_util.is_enabled(self.proxy_config)
|
||||
|
||||
def _new_session_id(self):
|
||||
"""Pick a fresh sticky-session id — DataImpulse pins one IP per id."""
|
||||
self._session_id = f"hnf{random.randint(100000, 999999)}"
|
||||
|
||||
def _proxy_launch_arg(self) -> Optional[dict]:
|
||||
"""Build Playwright's proxy dict, encoding country + sticky session
|
||||
into the username per DataImpulse's syntax: LOGIN__cr.gb;sessid.ID."""
|
||||
if not self._proxy_enabled():
|
||||
return None
|
||||
username = f"{self.proxy_config['username']}__cr.{self.proxy_config.get('country', 'gb')}"
|
||||
if self._session_id:
|
||||
username += f";sessid.{self._session_id}"
|
||||
return {
|
||||
"server": f"http://{self.proxy_config['host']}:{self.proxy_config['port']}",
|
||||
"username": username,
|
||||
"password": self.proxy_config["password"],
|
||||
}
|
||||
self._session_id = proxy_util.new_session_id()
|
||||
|
||||
async def rotate_session(self):
|
||||
"""Burn the current proxy IP and warm cache; the next scrape gets a
|
||||
|
|
@ -198,7 +172,7 @@ class PlaywrightLocalBackend(ScraperBackend):
|
|||
# Proxy is set at launch (Chromium binds the sticky session, which
|
||||
# lives in the username, at the network layer). Rotating the IP
|
||||
# therefore relaunches the browser — see rotate_session().
|
||||
proxy = self._proxy_launch_arg()
|
||||
proxy = proxy_util.playwright_proxy(self.proxy_config, self._session_id)
|
||||
if proxy:
|
||||
launch_kwargs['proxy'] = proxy
|
||||
self._browser = await self._playwright.chromium.launch(**launch_kwargs)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue