@@ -0,0 +1,142 @@
|
||||
"""
|
||||
Search service.
|
||||
Integrates Google Custom Search (CSE) and Bing Search API.
|
||||
Configuration is provided via environment variables (see env.example) through config_loader.
|
||||
"""
|
||||
import requests
|
||||
import json
|
||||
from typing import List, Dict, Any, Optional
|
||||
from app.utils.logger import get_logger
|
||||
from app.utils.config_loader import load_addon_config
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class SearchService:
|
||||
"""Search service."""
|
||||
|
||||
def __init__(self):
|
||||
self._config = {}
|
||||
self._load_config()
|
||||
|
||||
def _load_config(self):
|
||||
"""Load config (re-read env-config on each call for local hot-reload)."""
|
||||
config = load_addon_config()
|
||||
self._config = config.get('search', {})
|
||||
self.provider = self._config.get('provider', 'google')
|
||||
self.max_results = int(self._config.get('max_results', 10))
|
||||
|
||||
def search(self, query: str, num_results: int = None, date_restrict: str = None) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Execute a web search.
|
||||
|
||||
Args:
|
||||
query: Search query
|
||||
num_results: Override default max results
|
||||
date_restrict: Time restriction like 'd7' (past 7 days), Google only
|
||||
|
||||
Returns:
|
||||
List of search results
|
||||
"""
|
||||
# 重新加载配置以支持热更新
|
||||
self._load_config()
|
||||
|
||||
limit = num_results if num_results else self.max_results
|
||||
|
||||
if self.provider == 'bing':
|
||||
return self._search_bing(query, limit)
|
||||
else:
|
||||
return self._search_google(query, limit, date_restrict)
|
||||
|
||||
def _search_google(self, query: str, num_results: int, date_restrict: str = None) -> List[Dict[str, Any]]:
|
||||
"""Google Custom Search (CSE)."""
|
||||
api_key = self._config.get('google', {}).get('api_key')
|
||||
cx = self._config.get('google', {}).get('cx')
|
||||
|
||||
if not api_key or not cx:
|
||||
logger.warning("Google Search is not configured (missing api_key or cx).")
|
||||
return []
|
||||
|
||||
url = "https://www.googleapis.com/customsearch/v1"
|
||||
params = {
|
||||
'key': api_key,
|
||||
'cx': cx,
|
||||
'q': query,
|
||||
'num': min(num_results, 10), # Google API 限制每次最多10条
|
||||
'gl': 'cn' if any(c in query for c in ['A股', '利好', '利空', '财报']) else None # 针对中文内容优化地区
|
||||
}
|
||||
|
||||
# 添加时间限制参数
|
||||
if date_restrict:
|
||||
params['dateRestrict'] = date_restrict
|
||||
|
||||
try:
|
||||
# logger.info(f"正在调用 Google Search API: q={query}, params={params}")
|
||||
response = requests.get(url, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
# logger.info(f"Google Search 原始响应: {json.dumps(data, ensure_ascii=False)}") # 打印全部字符
|
||||
# logger.info(f"Google Search 原始响应: {json.dumps(data, ensure_ascii=False)[:500]}...") # 打印前500字符避免日志过大
|
||||
|
||||
results = []
|
||||
if 'items' in data:
|
||||
# logger.info(f"Google Search 返回了 {len(data['items'])} 条结果")
|
||||
for item in data['items']:
|
||||
logger.debug(f"Search Item: {item.get('title')} - {item.get('link')}")
|
||||
results.append({
|
||||
'title': item.get('title'),
|
||||
'link': item.get('link'),
|
||||
'snippet': item.get('snippet'),
|
||||
'source': 'Google',
|
||||
'published': item.get('pagemap', {}).get('metatags', [{}])[0].get('article:published_time', '')
|
||||
})
|
||||
else:
|
||||
logger.warning(f"Google Search returned no 'items'. Full response: {json.dumps(data, ensure_ascii=False)}")
|
||||
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Google search failed: {e}")
|
||||
if 'response' in locals():
|
||||
logger.error(f"Response: {response.text}")
|
||||
return []
|
||||
|
||||
def _search_bing(self, query: str, num_results: int) -> List[Dict[str, Any]]:
|
||||
"""Bing search."""
|
||||
api_key = self._config.get('bing', {}).get('api_key')
|
||||
|
||||
if not api_key:
|
||||
logger.warning("Bing Search is not configured (missing api_key).")
|
||||
return []
|
||||
|
||||
url = "https://api.bing.microsoft.com/v7.0/search"
|
||||
headers = {"Ocp-Apim-Subscription-Key": api_key}
|
||||
params = {
|
||||
"q": query,
|
||||
"count": num_results,
|
||||
"textDecorations": True,
|
||||
"textFormat": "HTML"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
results = []
|
||||
if 'webPages' in data and 'value' in data['webPages']:
|
||||
for item in data['webPages']['value']:
|
||||
results.append({
|
||||
'title': item.get('name'),
|
||||
'link': item.get('url'),
|
||||
'snippet': item.get('snippet'),
|
||||
'source': 'Bing',
|
||||
'published': item.get('datePublished', '')
|
||||
})
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Bing search failed: {e}")
|
||||
return []
|
||||
|
||||
Reference in New Issue
Block a user