Coverage for web_search / providers / google.py: 47%
36 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 os
2import time
3from typing import List
4from googleapiclient.discovery import build
5from qrclaw.web_search.types import WebSearchProvider, SearchResult, WebSearchResponse
6from qrclaw.logger import get_logger
8# 环境变量:GOOGLE_API_KEY 和 GOOGLE_CSE_ID
9GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
10GOOGLE_CSE_ID = os.getenv("GOOGLE_CSE_ID")
12logger = get_logger("qrclaw.web_search.providers.google")
14class GoogleSearchProvider(WebSearchProvider):
15 @property
16 def id(self) -> str:
17 return "google"
19 @property
20 def name(self) -> str:
21 return "Google Search"
23 def is_available(self) -> bool:
24 return bool(GOOGLE_API_KEY and GOOGLE_CSE_ID)
26 def search(self, query: str, max_results: int = 5, **kwargs) -> WebSearchResponse:
27 if not self.is_available():
28 raise RuntimeError("GOOGLE_API_KEY or GOOGLE_CSE_ID not configured")
30 start_time = time.time()
31 logger.debug(f"Google searching: {query}")
33 try:
34 service = build("customsearch", "v1", developerKey=GOOGLE_API_KEY)
35 # max results per page is 10
36 num = max(1, min(10, int(max_results)))
38 res = service.cse().list(
39 q=query,
40 cx=GOOGLE_CSE_ID,
41 num=num
42 ).execute()
44 items = res.get("items", [])
45 results: List[SearchResult] = []
47 for item in items:
48 results.append(SearchResult(
49 title=item.get("title", ""),
50 url=item.get("link", ""),
51 snippet=item.get("snippet", "")
52 ))
54 except Exception as e:
55 logger.error(f"Google search failed: {e}")
56 raise RuntimeError(f"Google API error: {e}")
58 took_ms = (time.time() - start_time) * 1000
59 return WebSearchResponse(
60 query=query,
61 provider=self.id,
62 count=len(results),
63 took_ms=took_ms,
64 results=results
65 )