web-search-server
by sanqiuyef
README.md
# web-search-server
面向 AI 的本地 Web 搜索 MCP 服务:DeepSeek 官方 Search/Fetch 为首选通路,
免费引擎聚合与本地渲染回退链作为自动降级,另含站点定向搜索。
- 传输:stdio(在 MCP 客户端里注册本地 MCP);默认端口 8011(HTTP 模式时)
- 运行:`python server.py`(依赖见 `requirements.txt`;可选依赖缺失会自动降级)
- 测试:`python -m pytest tests/ -q`(全部为 mock 测试,不依赖网络)
## 搜索与抓取通路
改造成「官方优先 + 本地降级」两层,避免把可靠性押在不稳定的免费抓取引擎上。
| 环节 | 首选 | 降级(自动) |
|---|---|---|
| 搜索 | DeepSeek 官方 `POST /v1/tools/search` | ddgs(yandex → duckduckgo)→ 本地 HTML 解析 |
| 抓取 | 本地 HTTP 提取(快、免费、能过普通页面) | DeepSeek 官方 `POST /v1/tools/fetch` → Firecrawl → 浏览器渲染 → Crawl4AI |
抓取顺序是「先便宜后兜底」:本地解析普通页面只要 0.5s 且不花钱,只有被拦截或解析
失败才交给官方 Fetch(它还能过 JS 渲染页)。整条回退链有总预算约束(`_FALLBACK_BUDGET`
= 38s),每一级启动前检查剩余预算,不够就跳过并在报错里说明 —— 否则「官方 20s +
Crawl4AI 30s + 浏览器 30s」逐级累加会把工具自身的 45s 超时撞穿(实测表现为抓一个
只需 10s 的页面等到 45s 超时)。
**为什么**:免费引擎这条路要一直打补丁——实测 ddgs 后端会成批失效(2026-10 只剩
yandex/duckduckgo 可用)、Bing 直抓对中文查询返回泛化词结果(「城市洪涝 海绵城市」
返回百度百科「城市」词条)、Google 无代理必然为空、反爬标记还会误伤正常页面。
官方 Search 自带重排与权威性评分,官方 Fetch 直接给 Markdown,把这一整层不确定性
换成了可预期的接口。
**官方通路要点**:
- 鉴权与 DeepSeek 模型 API 同源(Bearer key),但走独立的工具端点,**不消耗模型
轮次**(区别于 DeepSeek Harness 里 `/anthropic/v1/messages` + `web_search_20250305`
服务端工具那条通路——那条每搜一次要花一个模型轮次)。
- 计费:Search / Fetch 各 5 元每 1000 次**成功**请求;失败、无结果、超时不计费,
`count` 只控制条数不影响单次价格。
- 限速:账号级各 5 QPS(本地已先行限流)。
- 搜索语法:`site:`、`-site:`、`-关键词`、`-"短语"`、`"精确短语"`、`lang:`、
`after:`、`before:`,可组合。`time_range=day|week|month|year` 会自动转成 `after:` 日期。
- 结果字段:`rerank_score`(0-3,≥1.5 为优质)、`authority`(0-4,≥3 极权威,
-1 未知)、`snippet`(约 100-200 字)、`summary`(约 600-800 字)、`date`(UTC)。
- 需要正文时用 `need_content=true` 在同一次检索里取回,比逐条抓取便宜 N 倍。
**配置**:把有效的 key 放进环境变量 `DEEPSEEK_API_KEY`(或 MCP 客户端 env)。
key 失效/未配置/余额不足/限流时不会报错中断,而是自动降级到免费通路并在结果末尾
说明原因(例如「DeepSeek 鉴权失败(401):DEEPSEEK_API_KEY 无效或已过期」)。
## 工具清单(18)
| 类别 | 工具 |
|---|---|
| 搜索 | `web_search`(DeepSeek 优先,降级 DuckDuckGo)、`web_search_advanced`(DeepSeek 优先,降级多引擎聚合)、`web_search_with_content`(附正文)、`batch_web_search` |
| 单引擎搜索(免费通路,可单独调用) | `web_search_bing`、`web_search_google`(需代理) |
| 网页抓取 | `web_fetch`(本地 HTTP → DeepSeek Fetch → Firecrawl → 浏览器 → Crawl4AI)、`download_file` |
| 站点搜索·零凭据 | `github_repo_search`、`npm_package_search`、`pypi_package_search`、`gitlab_repo_search`、`crates_package_search`、`maven_package_search`、`nuget_package_search`、`docker_image_search`、`huggingface_model_search` |
| 站点搜索·需登录态 | `twitter_search`(Twitter/X 推文搜索) |
## 站点定向搜索
直接调各平台 API / 复用浏览器会话取结构化结果,比通用网页搜索准确(GitHub 走公开
REST API,无需任何凭据)。
## Twitter(X) 接入(twitter_search)
X 没有免费公开搜索 API,未登录访问 x.com 是空白页(2026-10-04 实测);
公共 Nitter 镜像当天也全部不可用(Cloudflare / Anubis / 429)。所以读 X 必须借登录态,
三条通路按推荐顺序:
### ① 遇到反爬/登录墙,回退用 Playwright MCP 驱动已登录的浏览器(零配置,推荐)
本项目的回退原则:本地抓取/渲染过不去(反爬、登录墙)时,**最终回退是交给
Playwright MCP(`mcp__playwright__*`,browser_* 工具)在用户已登录的浏览器里打开页面完成任务**
—— 不自己造浏览器、不要密码。做法:让 Playwright MCP 以
`--browser msedge --executable-path <系统 Edge> --extension` 的扩展模式桥接你自己的浏览器,
X 登录态就在里面(2026-10-04 实测:x.com 搜索页可直接抽到实时推文)。读 X 时:
`browser_tabs(action="new", url=...)` 开新标签页(别动扩展 connect 页),再用
`browser_evaluate` 抽取 `article[data-testid="tweet"]`。
### ② cookie 会话(静默,本工具自带)
不需弹浏览器时用这条路:
1. 在已登录 x.com 的浏览器(如 Edge)里:F12 → Application → Cookies → `https://x.com`
2. 复制 `auth_token` 与 `ct0` 两个值
3. 配到 MCP 客户端的 env(如 ZCode:`%USERPROFILE%\.zcode\cli\config.json` →
`mcp.servers.web-search-server.env`):
```json
"env": {
"X_AUTH_TOKEN": "<auth_token 的值>",
"X_CT0": "<ct0 的值>"
}
```
4. 重启 MCP 客户端后生效。cookie 等同于账号凭据:只放本机 env,不要写进仓库、
笔记、聊天记录或记忆文件(本仓库代码也不会打印/落盘它们)。
- 登录态过期时工具会提示「cookie 无效或已过期」,重新复制即可。
### ③ 调试端口浏览器(进阶,本工具自带)
Edge/Chrome **≥136 禁止用默认配置目录开 `--remote-debugging-port`**(实测 Edge 154
拒绝),且实测把 Edge 默认配置复制到独立目录启动后 **X 仍是未登录**。所以这条路需要:
```powershell
# 用独立配置目录启动(首次在该窗口里登录一次 X,之后登录态常驻)
& "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" `
--user-data-dir="<独立目录>\edge-agent-profile" --remote-debugging-port=9222
```
保持该实例运行,本工具的浏览器后端会新开标签页搜索、抽取后关闭(用户可见)。
Chrome 以调试端口启动期间不要用它登录敏感账号(调试端口对本机程序开放)。
### 用法
```
twitter_search("audio llm") # 最新推文
twitter_search("audio llm", mode="top") # 热门
twitter_search("from:elonmusk grok") # 读某人推文(X 搜索语法通用)
twitter_search("@elonmusk grok") # 提及
```
返回:@handle、时间、正文、互动数(回复/转推/喜欢/查看)、推文链接。
### 实现说明(改版自愈)
| 环节 | 做法 |
|---|---|
| 接口地址 | `https://x.com/i/api/graphql/{queryId}/SearchTimeline` |
| queryId | 自动从 `abs.twimg.com` 前端 bundle 里发现,结果缓存 24h(`.cache/x_api.json`);失败可手工设 `X_SEARCH_QUERY_ID` |
| features | X 报 `features cannot be null: ...` 时自动补齐同名 feature 重试并缓存 |
| 401/403 | 提示重新复制 cookie;404 自动重发现 queryId 重试 |
| 兜底 | 没有可用登录态时返回配置指引(不返回空结果/假数据) |
## 主要环境变量
| 变量 | 默认 | 说明 |
|---|---|---|
| `DEEPSEEK_API_KEY` | 空 | DeepSeek 官方 Search/Fetch 的 key(与模型 API 同源);留空则整条链路走免费引擎 |
| `WS_DEEPSEEK_ENABLED` | `true` | 官方通路总开关(key 为空时自动关闭) |
| `WS_DEEPSEEK_FETCH` | `true` | web_fetch 是否优先走官方 Fetch |
| `WS_DEEPSEEK_TIMEOUT` / `WS_DEEPSEEK_QPS` | `20` / `5` | 单次请求超时 / 本地 QPS 上限 |
| `WS_DEEPSEEK_BASE_URL` | `https://api.deepseek.com` | 接口地址(自建/代理时改) |
| `WS_SEARXNG_URL` | 空 | 自托管 SearXNG 实例(WSL2 部署于 127.0.0.1:8080) |
| `WS_DDGS_BACKENDS` | `yandex,duckduckgo` | ddgs 后端优先级(须在 ddgs 注册表内,否则会静默退回 auto) |
| `WS_DDGS_TOTAL_BUDGET` | `14` | 逐后端尝试的总预算(秒) |
| `WS_RELEVANCE_FILTER` | `true` | 免费通路的关键词相关性过滤(官方通路不用,它自带重排) |
| `WS_CACHE_TTL` | `300` | 搜索结果内存缓存秒数 |
| `X_AUTH_TOKEN` / `X_CT0` | 空 | X 的 cookie 会话(见上) |
| `X_SEARCH_QUERY_ID` | 空 | 手工覆盖 SearchTimeline queryId |
| `WS_CDP_PORT` | `9222` | CDP 复用的浏览器调试端口(抓取回退链与 twitter_search 共用) |
| `FIRECRAWL_API_KEY` | 空 | 可选云抓取后端 |
## 已知环境依赖
`crawl4ai`(抓取回退链的本地渲染级)依赖 `pyOpenSSL`,而 pyOpenSSL 25.x 与
`cryptography` 46 不兼容——`import OpenSSL.crypto` 会抛
`AttributeError: module 'lib' has no attribute 'GEN_EMAIL'`。此时 crawl4ai 整级失效
(代码已做保护:导入异常只降级为「不可用」,不会打断回退链)。如需恢复该级:
```
D:\anaconda3\python.exe -m pip install "pyOpenSSL>=26.0,<26.3" # 兼容 cryptography 46
```
若装包报 `WinError 5 拒绝访问`,是安全软件拦截删除,用提权安装:
```powershell
Start-Process -Verb RunAs -Wait D:\anaconda3\python.exe -ArgumentList '-m','pip','install','pyOpenSSL==26.2.0'
```
⚠️ 不要直接 `pip install --upgrade pyOpenSSL`:最新版会连带把 cryptography 升到 49+,
而升级过程一旦在删除 `cryptography\hazmat\bindings\_rust.pyd` 时被拦,
会留下 dist-info 已删、文件残缺的半安装状态,导致所有依赖 cryptography 的程序 import 失败。
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues