Coverage for web_search / providers / duckduckgo.py: 48%
29 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-29 02:55 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-29 02:55 +0800
1import time
2from typing import List
3from ddgs import DDGS
4from qrclaw.web_search.types import WebSearchProvider, SearchResult, WebSearchResponse
5from qrclaw.logger import get_logger
7logger = get_logger("qrclaw.web_search.providers.duckduckgo")
9class DuckDuckGoSearchProvider(WebSearchProvider):
10 @property
11 def id(self) -> str:
12 return "duckduckgo"
14 @property
15 def name(self) -> str:
16 return "DuckDuckGo Search"
18 def is_available(self) -> bool:
19 # 始终可用,不需要 Key
20 return True
22 def search(self, query: str, max_results: int = 5, **kwargs) -> WebSearchResponse:
23 start_time = time.time()
24 logger.debug(f"DuckDuckGo searching: {query}")
26 results: List[SearchResult] = []
27 try:
28 with DDGS() as ddgs:
29 # region="wt-wt" 表示全球搜索,timelimit="y" 表示最近一年(可选)
30 ddg_results = ddgs.text(query, max_results=max_results, region="wt-wt")
32 for r in ddg_results:
33 results.append(SearchResult(
34 title=r.get("title", ""),
35 url=r.get("href", ""),
36 snippet=r.get("body", "")
37 ))
38 except Exception as e:
39 logger.error(f"DuckDuckGo search failed: {e}")
40 raise RuntimeError(f"DuckDuckGo API error: {e}")
42 took_ms = (time.time() - start_time) * 1000
43 return WebSearchResponse(
44 query=query,
45 provider=self.id,
46 count=len(results),
47 took_ms=took_ms,
48 results=results
49 )