Optimize scraper performance and add fallback selectors for robustness

Performance improvements:
- Validation speed: 59.71s → 10.96s (5.5x improvement)
- Removed 50+ console.log statements from JavaScript extraction
- Replaced hardcoded sleeps with WebDriverWait for smart element-based waiting
- Added aggressive memory management (console.clear, GC, image unloading every 20 scrolls)

Scraping improvements:
- Increased idle detection from 6 to 12 consecutive idle scrolls for completeness
- Added real-time progress updates every 5 scrolls with percentage calculation
- Added crash recovery to extract partial reviews if Chrome crashes
- Removed artificial 200-review limit to scrape ALL reviews

Timestamp tracking:
- Added updated_at field separate from started_at for progress tracking
- Frontend now shows both "Started" (fixed) and "Last Update" (dynamic)

Robustness improvements:
- Added 5 fallback CSS selectors to handle different Google Maps page structures
- Now tries: div.jftiEf.fontBodyMedium, div.jftiEf, div[data-review-id], etc.
- Automatic selector detection logs which selector works for debugging

Test results:
- Successfully scraped 550 reviews in 150.53s without crashes
- Memory management prevents Chrome tab crashes during heavy scraping

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-01-18 19:49:24 +00:00
parent bdffb5eaac
commit faa0704737
108 changed files with 23632 additions and 54 deletions

96
test_api_quick.py Normal file
View File

@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Quick test of API interceptor with manual response dumping"""
import json
import logging
import time
from pathlib import Path
from seleniumbase import SB
from modules.api_interceptor import GoogleMapsAPIInterceptor
# Set up logging
logging.basicConfig(level=logging.INFO, format="[%(levelname)s] %(message)s")
url = "https://www.google.com/maps/place/Soho+Club/data=!4m7!3m6!1s0x46dd947294b213bf:0x864c7a232527adb4!8m2!3d54.67869!4d25.2667181!16s%2Fg%2F1thhj5ml!19sChIJvxOylHKU3UYRtK0nJSN6TIY?authuser=0&hl=es&rclk=1"
print("[INFO] Starting browser with UC mode...")
with SB(uc=True, headless=False) as sb:
print("[INFO] Loading Google Maps page...")
sb.open(url)
sb.sleep(3)
# Inject interceptor EARLY
print("[INFO] Injecting API interceptor...")
interceptor = GoogleMapsAPIInterceptor(sb.driver)
interceptor.inject_response_interceptor()
sb.sleep(2)
# Click reviews tab
print("[INFO] Looking for reviews tab...")
try:
sb.click('.LRkQ2', timeout=5)
print("[INFO] Clicked reviews tab")
except Exception as e:
print(f"[WARN] Could not click reviews tab: {e}")
sb.sleep(5)
# Scroll to trigger API calls
print("[INFO] Scrolling to load reviews...")
for i in range(5):
sb.execute_script("window.scrollBy(0, 800)")
sb.sleep(2)
print(f" Scroll {i+1}/5...")
# Wait a bit more
print("[INFO] Waiting for API responses...")
sb.sleep(3)
# Get intercepted responses
responses = interceptor.get_intercepted_responses()
print(f"\n[SUCCESS] Captured {len(responses)} API responses!")
if not responses:
print("[WARN] No responses captured. Exiting.")
exit(0)
# Dump to files
output_dir = Path("debug_api_dump")
output_dir.mkdir(exist_ok=True)
for i, resp in enumerate(responses):
# Full response
resp_file = output_dir / f"response_{i}.json"
with open(resp_file, 'w', encoding='utf-8') as f:
json.dump(resp, f, indent=2, ensure_ascii=False)
# Just body
body_file = output_dir / f"response_{i}_body.txt"
with open(body_file, 'w', encoding='utf-8') as f:
f.write(resp.get('body', ''))
url_str = resp.get('url', 'unknown')
size = resp.get('size', len(resp.get('body', '')))
print(f"\n [{i}] {url_str[:80]}... ({size} bytes)")
print(f" Full: {resp_file}")
print(f" Body: {body_file}")
print(f"\n[SUCCESS] Dumped {len(responses)} responses to: {output_dir}/")
# Try to parse
print("\n[INFO] Attempting to parse reviews from responses...")
try:
parsed_reviews = interceptor.parse_reviews_from_responses(responses)
print(f"[INFO] Parsed {len(parsed_reviews)} reviews")
for i, review in enumerate(parsed_reviews[:5]):
print(f"\n Review {i+1}:")
print(f" ID: {review.review_id[:50] if review.review_id else 'N/A'}")
print(f" Author: {review.author}")
print(f" Rating: {review.rating}")
print(f" Text: {review.text[:80] if review.text else 'N/A'}...")
except Exception as e:
print(f"[ERROR] Failed to parse: {e}")
import traceback
traceback.print_exc()
print("\n[DONE]")