mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-24 15:48:11 +02:00
Merge pull request #137 from R0m1k3/claude/add-product-monitoring-01DyNDGnKBX4fHNSjAkwVckd
fix: Add concurrency limits to prevent Browserless timeout saturation
This commit is contained in:
1 file changed
+21
-15
@@ -302,7 +302,8 @@ class NewSearchService:
|
|||||||
|
|
||||||
# Phase 2: Scrape details for each result (Parallel)
|
# Phase 2: Scrape details for each result (Parallel)
|
||||||
# We want to yield results as they complete, not wait for all
|
# We want to yield results as they complete, not wait for all
|
||||||
semaphore = asyncio.Semaphore(3) # Limit concurrency per site
|
# Reduced from 3 to 2 to avoid saturating Browserless
|
||||||
|
semaphore = asyncio.Semaphore(2) # Limit concurrency per site
|
||||||
|
|
||||||
async def scrape_wrapper(res):
|
async def scrape_wrapper(res):
|
||||||
async with semaphore:
|
async with semaphore:
|
||||||
@@ -455,32 +456,37 @@ async def search_products(
|
|||||||
|
|
||||||
# 3. Execute searches and stream results
|
# 3. Execute searches and stream results
|
||||||
# We create a task for each site generator
|
# We create a task for each site generator
|
||||||
|
|
||||||
generators = [NewSearchService.search_site_generator(key, query) for key in site_keys]
|
generators = [NewSearchService.search_site_generator(key, query) for key in site_keys]
|
||||||
|
|
||||||
# We need to iterate over multiple async generators concurrently
|
# We need to iterate over multiple async generators concurrently
|
||||||
# This is a bit complex, so we'll use a queue or similar
|
# This is a bit complex, so we'll use a queue or similar
|
||||||
# Simpler approach: Use aiostream if available, or just interleave manually
|
# Simpler approach: Use aiostream if available, or just interleave manually
|
||||||
# For now, let's just run them and yield as we get them.
|
# For now, let's just run them and yield as we get them.
|
||||||
# Since we want to show results ASAP, we can use asyncio.as_completed on the *next* item of each generator?
|
# Since we want to show results ASAP, we can use asyncio.as_completed on the *next* item of each generator?
|
||||||
# No, generators are stateful.
|
# No, generators are stateful.
|
||||||
|
|
||||||
# Simplest robust approach without extra libs:
|
# Simplest robust approach without extra libs:
|
||||||
# Create a wrapper task for each generator that puts items into a shared Queue
|
# Create a wrapper task for each generator that puts items into a shared Queue
|
||||||
|
|
||||||
queue = asyncio.Queue()
|
queue = asyncio.Queue()
|
||||||
active_producers = len(generators)
|
active_producers = len(generators)
|
||||||
|
|
||||||
async def producer(gen):
|
|
||||||
try:
|
|
||||||
async for item in gen:
|
|
||||||
await queue.put(item)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error in search producer: {e}")
|
|
||||||
finally:
|
|
||||||
await queue.put(None) # Sentinel
|
|
||||||
|
|
||||||
# Start producers
|
# IMPORTANT: Limit concurrent sites to avoid saturating Browserless
|
||||||
|
# Max 2 sites can search in parallel, others wait
|
||||||
|
site_semaphore = asyncio.Semaphore(2)
|
||||||
|
|
||||||
|
async def producer(gen):
|
||||||
|
async with site_semaphore: # Wait for slot before starting search
|
||||||
|
try:
|
||||||
|
async for item in gen:
|
||||||
|
await queue.put(item)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in search producer: {e}")
|
||||||
|
finally:
|
||||||
|
await queue.put(None) # Sentinel
|
||||||
|
|
||||||
|
# Start producers (limited by semaphore)
|
||||||
for gen in generators:
|
for gen in generators:
|
||||||
asyncio.create_task(producer(gen))
|
asyncio.create_task(producer(gen))
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user