mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-23 07:08:12 +02:00
feat: implement ScraperService for asynchronous web scraping using Playwright, including browser management, pop-up handling, and configurable scraping options.
This commit is contained in:
1 parent
4489c71432
commit
cd72449a4e
1 file changed
+34
-7
@@ -8,6 +8,8 @@ from datetime import datetime
|
|||||||
from playwright.async_api import Browser, BrowserContext, Page, async_playwright
|
from playwright.async_api import Browser, BrowserContext, Page, async_playwright
|
||||||
from playwright.async_api import TimeoutError as PlaywrightTimeoutError
|
from playwright.async_api import TimeoutError as PlaywrightTimeoutError
|
||||||
|
|
||||||
|
from app.core.search_config import SITE_CONFIGS, get_amazon_proxies, get_random_stealth_config
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
BROWSERLESS_URL = os.getenv("BROWSERLESS_URL", "ws://browserless:3000")
|
BROWSERLESS_URL = os.getenv("BROWSERLESS_URL", "ws://browserless:3000")
|
||||||
@@ -168,16 +170,39 @@ class ScraperService:
|
|||||||
return None, "", url, ""
|
return None, "", url, ""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Determine Proxy Requirement
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
domain = urlparse(url).netloc.replace("www.", "")
|
||||||
|
|
||||||
|
# Check SITE_CONFIGS for proxy requirement
|
||||||
|
requires_proxy = False
|
||||||
|
if domain in SITE_CONFIGS:
|
||||||
|
requires_proxy = SITE_CONFIGS[domain].get("requires_proxy", False)
|
||||||
|
elif f"www.{domain}" in SITE_CONFIGS: # Fallback check
|
||||||
|
requires_proxy = SITE_CONFIGS[f"www.{domain}"].get("requires_proxy", False)
|
||||||
|
|
||||||
|
# Special override: If Action.com, force proxy
|
||||||
|
if "action.com" in domain:
|
||||||
|
requires_proxy = True
|
||||||
|
|
||||||
|
proxy = None
|
||||||
|
if requires_proxy:
|
||||||
|
proxies = get_amazon_proxies() # Reusing Amazon proxies for hard sites
|
||||||
|
if proxies:
|
||||||
|
proxy = random.choice(proxies)
|
||||||
|
# Rotate proxy on retry
|
||||||
|
if attempt > 0:
|
||||||
|
proxy = random.choice(proxies)
|
||||||
|
|
||||||
# SPECIALIZED AMAZON HANDLING
|
# SPECIALIZED AMAZON HANDLING
|
||||||
if "amazon" in url:
|
if "amazon" in url:
|
||||||
return await ScraperService._scrape_amazon_specific(url, item_id, config, return_html)
|
return await ScraperService._scrape_amazon_specific(url, item_id, config, return_html, proxy)
|
||||||
|
|
||||||
context = await ScraperService._create_context(ScraperService._browser, url)
|
context = await ScraperService._create_context(ScraperService._browser, url, proxy=proxy)
|
||||||
page = await context.new_page()
|
page = await context.new_page()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Random delay to simulate human lead-in
|
# Random delay to simulate human lead-in
|
||||||
import random
|
|
||||||
await asyncio.sleep(random.uniform(0.5, 2.0))
|
await asyncio.sleep(random.uniform(0.5, 2.0))
|
||||||
|
|
||||||
await ScraperService._navigate_and_wait(page, url, timeout)
|
await ScraperService._navigate_and_wait(page, url, timeout)
|
||||||
@@ -270,7 +295,7 @@ class ScraperService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _scrape_amazon_specific(
|
async def _scrape_amazon_specific(
|
||||||
url: str, item_id: int | None, config: ScrapeConfig, return_html: bool
|
url: str, item_id: int | None, config: ScrapeConfig, return_html: bool, proxy: dict | None = None
|
||||||
) -> tuple[str | None, str, str, str]:
|
) -> tuple[str | None, str, str, str]:
|
||||||
"""
|
"""
|
||||||
Specialized scraping flow for Amazon to avoid bot detection and ensure good screenshots.
|
Specialized scraping flow for Amazon to avoid bot detection and ensure good screenshots.
|
||||||
@@ -288,7 +313,7 @@ class ScraperService:
|
|||||||
parsed = urlparse(url)
|
parsed = urlparse(url)
|
||||||
base_domain = f"{parsed.scheme}://{parsed.netloc}"
|
base_domain = f"{parsed.scheme}://{parsed.netloc}"
|
||||||
|
|
||||||
context = await ScraperService._create_context(ScraperService._browser, url)
|
context = await ScraperService._create_context(ScraperService._browser, url, proxy=proxy)
|
||||||
page = await context.new_page()
|
page = await context.new_page()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -428,9 +453,8 @@ class ScraperService:
|
|||||||
return await p.chromium.connect_over_cdp(BROWSERLESS_URL, timeout=30000)
|
return await p.chromium.connect_over_cdp(BROWSERLESS_URL, timeout=30000)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _create_context(browser: Browser, url: str) -> BrowserContext:
|
async def _create_context(browser: Browser, url: str, proxy: dict | None = None) -> BrowserContext:
|
||||||
"""Create context with advanced stealth and headers"""
|
"""Create context with advanced stealth and headers"""
|
||||||
from app.core.search_config import get_random_stealth_config
|
|
||||||
|
|
||||||
stealth_config = get_random_stealth_config()
|
stealth_config = get_random_stealth_config()
|
||||||
ua = stealth_config["ua"]
|
ua = stealth_config["ua"]
|
||||||
@@ -438,6 +462,8 @@ class ScraperService:
|
|||||||
platform = stealth_config["platform"]
|
platform = stealth_config["platform"]
|
||||||
|
|
||||||
logger.info(f"🎭 Using stealth profile: {ua[:50]}...")
|
logger.info(f"🎭 Using stealth profile: {ua[:50]}...")
|
||||||
|
if proxy:
|
||||||
|
logger.info(f"🛡️ Using Proxy: {proxy['server']}")
|
||||||
|
|
||||||
# Determine base domain for referer
|
# Determine base domain for referer
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
@@ -454,6 +480,7 @@ class ScraperService:
|
|||||||
timezone_id="Europe/Paris",
|
timezone_id="Europe/Paris",
|
||||||
java_script_enabled=True,
|
java_script_enabled=True,
|
||||||
bypass_csp=True,
|
bypass_csp=True,
|
||||||
|
proxy=proxy,
|
||||||
extra_http_headers={
|
extra_http_headers={
|
||||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||||
"Accept-Language": "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7",
|
"Accept-Language": "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7",
|
||||||
|
|||||||
Reference in new issue
Block a user