mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-23 15:18:12 +02:00
Merge pull request #195 from R0m1k3/antigravity
feat: introduce improved search service with persistent browser conne…
This commit is contained in:
2 files changed
+78
No files matched your search
@@ -457,6 +457,74 @@ class ImprovedSearchService:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def search_site_generator(cls, site_key: str, query: str) -> AsyncGenerator[SearchResult, None]:
|
||||||
|
"""Search a single site and yield results as they are scraped"""
|
||||||
|
config = SITE_CONFIGS.get(site_key)
|
||||||
|
if not config:
|
||||||
|
logger.error(f"Unknown site: {site_key}")
|
||||||
|
return
|
||||||
|
|
||||||
|
search_url = config["search_url"].format(query=quote_plus(query))
|
||||||
|
logger.info(f"Searching {config['name']} at {search_url}")
|
||||||
|
|
||||||
|
if not await cls._ensure_browser_connected():
|
||||||
|
logger.error("Failed to connect to browser")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
context = await cls._create_context(cls._browser)
|
||||||
|
|
||||||
|
try:
|
||||||
|
page = await context.new_page()
|
||||||
|
|
||||||
|
# Navigate
|
||||||
|
await page.goto(search_url, wait_until="domcontentloaded", timeout=30000)
|
||||||
|
|
||||||
|
# Wait for selector
|
||||||
|
wait_selector = config.get("wait_selector") or config.get("product_selector")
|
||||||
|
try:
|
||||||
|
if wait_selector:
|
||||||
|
await page.wait_for_selector(wait_selector, timeout=10000)
|
||||||
|
except Exception:
|
||||||
|
logger.warning(f"Timeout waiting for selector {wait_selector} on {site_key}")
|
||||||
|
|
||||||
|
# Get content
|
||||||
|
content = await page.content()
|
||||||
|
await page.close() # Close search page to free resources
|
||||||
|
|
||||||
|
# Parse results (Phase 1)
|
||||||
|
base_url = search_url.split("/search")[0]
|
||||||
|
if "amazon" in site_key:
|
||||||
|
base_url = "https://www.amazon.fr"
|
||||||
|
|
||||||
|
initial_results = cls._parse_results(content, site_key, base_url, query)
|
||||||
|
|
||||||
|
if not initial_results:
|
||||||
|
logger.warning(f"No results found for {site_key}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Phase 2: Scrape details (Streaming)
|
||||||
|
semaphore = asyncio.Semaphore(3)
|
||||||
|
|
||||||
|
async def scrape_wrapper(res):
|
||||||
|
async with semaphore:
|
||||||
|
return await cls._scrape_item_details(res, context)
|
||||||
|
|
||||||
|
tasks = [scrape_wrapper(r) for r in initial_results]
|
||||||
|
|
||||||
|
for future in asyncio.as_completed(tasks):
|
||||||
|
enriched_res = await future
|
||||||
|
if enriched_res:
|
||||||
|
yield enriched_res
|
||||||
|
|
||||||
|
finally:
|
||||||
|
await context.close()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error searching {site_key}: {e}")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def search_all(cls, query: str) -> list[SearchResult]:
|
async def search_all(cls, query: str) -> list[SearchResult]:
|
||||||
"""Search all configured sites"""
|
"""Search all configured sites"""
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import sys
|
||||||
|
from app.services.improved_search_service import ImprovedSearchService
|
||||||
|
|
||||||
|
print("Attributes of ImprovedSearchService:")
|
||||||
|
attrs = dir(ImprovedSearchService)
|
||||||
|
if 'search_site_generator' in attrs:
|
||||||
|
print("SUCCESS: search_site_generator found")
|
||||||
|
else:
|
||||||
|
print("FAILURE: search_site_generator NOT found")
|
||||||
|
print("Available attributes:", [a for a in attrs if not a.startswith('__')])
|
||||||
Reference in new issue
Block a user