Reduce scraper timeouts and raise default concurrency to 6

Timeouts were set for the old proxy 407 round-trip world (14.5s latency
per challenge). Now that credentials are embedded in the proxy URL, pages
load in ~7s — the 90s goto timeout was forcing 5+ minutes of wasted wait
on Cloudflare-blocked dates before rotating sessions.

  homepage warmup goto: 90s → 20s
  page 1 search goto:   90s → 30s  (4× normal load time headroom)
  property-card waits:  30s → 15s  (cards appear in <3s on clean pages)

Default concurrency 3 → 6: each worker uses its own residential proxy IP
so parallelism is safe. ~0.4 GB per Chromium; 6 workers fits in 4 GB LXC.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-09 21:17:19 +00:00
parent b47258d47b
commit ea66025140
2 changed files with 9 additions and 9 deletions

View file

@ -426,16 +426,16 @@ async def scrape_date(
def get_scraper_concurrency(db: Session) -> int: def get_scraper_concurrency(db: Session) -> int:
"""Number of parallel scrape workers (config key, default 3). Each worker """Number of parallel scrape workers (config key, default 6). Each worker
runs its own browser on its own residential proxy IP, so raise this only runs its own browser on its own residential proxy IP (~0.4 GB each), so
with proxy IPs and RAM to spare (~0.4 GB per worker).""" keep this below RAM / 0.4 GB. Requires proxy serial fallback if off."""
row = db.execute( row = db.execute(
text("SELECT config_value FROM system_config WHERE config_key = 'booking_scraper_concurrency'") text("SELECT config_value FROM system_config WHERE config_key = 'booking_scraper_concurrency'")
).fetchone() ).fetchone()
try: try:
return max(1, int(row.config_value)) if row and row.config_value else 3 return max(1, int(row.config_value)) if row and row.config_value else 6
except (ValueError, TypeError): except (ValueError, TypeError):
return 3 return 6
def _effective_concurrency(db: Session, n_jobs: int) -> int: def _effective_concurrency(db: Session, n_jobs: int) -> int:

View file

@ -243,7 +243,7 @@ class PlaywrightLocalBackend(ScraperBackend):
async def _warm_up(self, page: Page): async def _warm_up(self, page: Page):
"""Visit the homepage first so search requests carry session cookies.""" """Visit the homepage first so search requests carry session cookies."""
try: try:
await page.goto(self.HOMEPAGE, wait_until='domcontentloaded', timeout=90000) await page.goto(self.HOMEPAGE, wait_until='domcontentloaded', timeout=20000)
await asyncio.sleep(random.uniform(1.5, 3.0)) await asyncio.sleep(random.uniform(1.5, 3.0))
# Dismiss the cookie-consent dialog if present # Dismiss the cookie-consent dialog if present
for sel in ['#onetrust-accept-btn-handler', for sel in ['#onetrust-accept-btn-handler',
@ -375,12 +375,12 @@ class PlaywrightLocalBackend(ScraperBackend):
# Wait for property cards - booking.com uses data-testid. Retry once # Wait for property cards - booking.com uses data-testid. Retry once
# with a scroll nudge before giving up: cards can lazy-load late. # with a scroll nudge before giving up: cards can lazy-load late.
try: try:
await page.wait_for_selector('[data-testid="property-card"]', timeout=30000) await page.wait_for_selector('[data-testid="property-card"]', timeout=15000)
except Exception as e: except Exception as e:
logger.warning(f"No property cards on first wait, retrying: {e}") logger.warning(f"No property cards on first wait, retrying: {e}")
try: try:
await self._human_like_scroll(page) await self._human_like_scroll(page)
await page.wait_for_selector('[data-testid="property-card"]', timeout=30000) await page.wait_for_selector('[data-testid="property-card"]', timeout=15000)
except Exception as e2: except Exception as e2:
logger.warning(f"No property cards found after retry: {e2}") logger.warning(f"No property cards found after retry: {e2}")
return hotels, rates return hotels, rates
@ -614,7 +614,7 @@ class PlaywrightLocalBackend(ScraperBackend):
# direct hit to searchresults with no Referer causes # direct hit to searchresults with no Referer causes
# Cloudflare to hold the response body open indefinitely. # Cloudflare to hold the response body open indefinitely.
await page.goto(url, wait_until='domcontentloaded', await page.goto(url, wait_until='domcontentloaded',
timeout=90000, referer=self.HOMEPAGE) timeout=30000, referer=self.HOMEPAGE)
except Exception as e: except Exception as e:
logger.warning(f"Page load timeout, continuing: {e}") logger.warning(f"Page load timeout, continuing: {e}")
page_loaded = False page_loaded = False