Spaces:
Sleeping
Sleeping
Fix DuckDuckGo backend silently returning empty results (#2)
Browse filesquery_ddg_search() didn't raise when the duckduckgo_search library got
blocked/rate-limited and parsed zero results instead of throwing. This made
/serp/search_duck report {"error": null, "results": []} on failure, and
worse, made the /serp/search fallback chain (_search_one) treat DDG's empty
return as a success and never try Brave or Bing — the reason /serp/search
was returning "All backends are rate-limited." even when Bing worked fine
on its own. Now an empty DDG result raises, so both endpoints behave
correctly.
Claude-Session: https://claude.ai/code/session_012sXQyRk3EZHbYHas8fQsiS
Co-authored-by: Claude <noreply@anthropic.com>
serp.py
CHANGED
|
@@ -50,6 +50,20 @@ class BrowserUnavailableError(Exception):
|
|
| 50 |
super().__init__("Playwright browser is not available (it failed to start at startup).")
|
| 51 |
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
_PLAYWRIGHT_CONCURRENCY_SEMAPHORE = Semaphore(PLAYWRIGHT_CONCURRENCY_LIMIT)
|
| 54 |
|
| 55 |
|
|
@@ -260,6 +274,9 @@ async def query_ddg_search(q: str, n_results: int = 10):
|
|
| 260 |
results.append(
|
| 261 |
{"title": result["title"], "body": result["body"], "href": result["href"]})
|
| 262 |
|
|
|
|
|
|
|
|
|
|
| 263 |
return results
|
| 264 |
|
| 265 |
|
|
|
|
| 50 |
super().__init__("Playwright browser is not available (it failed to start at startup).")
|
| 51 |
|
| 52 |
|
| 53 |
+
class DuckDuckGoBlockedException(Exception):
|
| 54 |
+
"""Raised when DuckDuckGo returns zero results.
|
| 55 |
+
|
| 56 |
+
The duckduckgo_search library doesn't always raise when its request is
|
| 57 |
+
blocked/rate-limited — it can also just parse zero results from the
|
| 58 |
+
anti-bot response page and return an empty list. Since callers (the
|
| 59 |
+
`/serp/search` fallback chain in particular) rely on an exception to know
|
| 60 |
+
a backend failed, treat an empty result as a failure too.
|
| 61 |
+
"""
|
| 62 |
+
|
| 63 |
+
def __init__(self, *args):
|
| 64 |
+
super().__init__("DuckDuckGo returned no results, likely blocked or rate-limited")
|
| 65 |
+
|
| 66 |
+
|
| 67 |
_PLAYWRIGHT_CONCURRENCY_SEMAPHORE = Semaphore(PLAYWRIGHT_CONCURRENCY_LIMIT)
|
| 68 |
|
| 69 |
|
|
|
|
| 274 |
results.append(
|
| 275 |
{"title": result["title"], "body": result["body"], "href": result["href"]})
|
| 276 |
|
| 277 |
+
if not results:
|
| 278 |
+
raise DuckDuckGoBlockedException()
|
| 279 |
+
|
| 280 |
return results
|
| 281 |
|
| 282 |
|