模型 / 数据集
karust/openserp avatar
karust/openserp

OpenSERP 自托管实践:用浏览器渲染把六个搜索引擎变成同一个 JSON 接口

Self-hosted SERP API for AI, SEO & automation. Browser-rendered Google, Bing, Yandex, Baidu, DuckDuckGo and Ecosia search with page extraction 🎉

1,390 个 Star156 个 ForkGoMIT

秒懂

它是什么?
OpenSERP 用 Go 写成,通过浏览器渲染抓取 Google、Bing、Yandex、Baidu、DuckDuckGo 与 Ecosia,把结果统一成同一套 JSON schema,并提供 megasearch 合并去重与页面正文提取。它适合想摆脱按次计费、又需要覆盖多引擎的团队,但代价是你要自己承担渲染、代理与反爬的运维成本。
适合谁用?
如果你的场景是多引擎排名对比、LLM 检索增强或 SEO 批量抓取,并且团队里有人愿意维护代理池和浏览器渲染环境,OpenSERP 值得先在测试机上跑通再决定是否接入生产;如果你需要的是稳定的 SLA、合规的数据来源,或者没有精力处理验证码与 IP 封禁,那它就不是合适的工具,按次计费的商业 SERP API 仍然是更省事的选择。动手前先确认三件事:目标引擎在无代理情况下能返回多少条结果、extract 模式抓取的页面是否涉及你不该抓取的内容、以及 Docker 镜像与 Go module 的版本是否与你锁定的 v0.8.12 一致。
能商用吗?
可以。MIT 是宽松许可证:你可以使用、修改并销售基于它的软件,只需保留版权和许可证声明。
还在维护吗?
在维护。仓库最近一次提交在 56 天前。
用什么语言写的?
主要是 Go(依据 GitHub 的语言统计)。

以上回答依据项目的 GitHub 数据(最近同步于 2026年9月15日)和我们的分析,不构成法律意见。

开源项目深度解析

按次计费的 SERP API 贵在哪,OpenSERP 想省掉什么

商业 SERP API 的定价模型是按查询次数收费,这对两类用户都不友好:一类是 LLM 或 agent 需要在一次推理里跑几十次搜索的,另一类是 SEO 团队要按天、按关键词批量拉排名的。OpenSERP 的定位很直接,README 写明它 no API keys, no per-search billing,你把它跑在 localhost,查询次数就只受你自己的机器和网络限制。

它同时覆盖了六个引擎:Google、Yandex、Baidu、Bing、DuckDuckGo 和 Ecosia。README 特别提到 including engines the paid APIs don't cover,这句话指向的是 Yandex、Baidu、Ecosia 这类在英文商业 SERP API 里支持度参差的引擎。如果你的关键词需要同时看百度和 Google 的排名差异,或者需要俄罗斯市场的 Yandex 数据,这个覆盖面本身就是选它的理由。

目标用户写得很清楚:给 LLM 和 agent 当搜索工具,或者给 SEO 排名追踪当后端。这两类用户的共同点是查询量大、对单次延迟不敏感、但对面向上层的稳定性有要求。它不适合的是需要低延迟单次查询的在线服务,因为浏览器渲染本身就要几百毫秒起步。

浏览器渲染 + 统一 schema:一次请求里发生了什么

从 README 的响应示例可以还原出请求的数据流。以 /mega/search 为例,参数 engines=bing,google 指定引擎列表,text 是查询词,extract=1 打开正文提取,mode=any 表示只要有一个引擎返回就结束等待。响应里的 meta 字段记录了 engines_requested、engines_responded 和 engines_failed 三个数组,也就是说部分引擎失败不会让整个请求失败,调用方可以自己判断结果完整性。

结果对象里除了常规的 rank、title、url、snippet,还有几个值得注意的字段。position.absolute 是跨引擎的绝对排名,domain_info 拆出了 tld 和 sld,classification 给出 content_type 和 source_hint(示例里是 article 和 encyclopedia)。当 extract=1 时,每个结果会多出一个 extracted 对象,包含目标页面的 markdown 正文、mode_used 和 fetched_at。这意味着搜索和抓取被合并成一次调用,省掉了你自己写第二段爬虫。

响应顶层的 clusters 数组是 megasearch 的去重产物。每个 cluster 有 canonical_url、domain,以及 occurrences 数组记录它在哪个引擎、哪个排名出现过,engines_count 和 best_rank 用来排序。这个结构对做跨引擎排名对比的人很实用:同一个页面在 Bing 排 1、在 Google 排 7,一眼能看出来。

README 还列出 SERP features 支持 AI summaries、answer boxes、people-also-ask 和 related searches。这些是搜索引擎结果页上的结构化模块,抓取难度比普通蓝色链接高,因为它们的位置和 DOM 结构会随引擎改版变化。

三种启动方式:Docker、go install 与源码构建

README 给了三条部署路径。最快的是 Docker,镜像发布在 Docker Hub 的 karust/openserp 下:

docker run --rm -p 127.0.0.1:7000:7000 karust/openserp:latest serve -a 0.0.0.0 -p 7000

注意这里把宿主端口绑定在 127.0.0.1 上,容器内监听 0.0.0.0,这是一个合理的默认姿势:服务本身不做鉴权,暴露到公网等于把抓取能力开放给别人。仓库里也有 docker compose up 的入口。

如果你有 Go 环境,可以直接装 CLI:

go install github.com/karust/openserp@latest openserp search duckduckgo "open source serp api" --format markdown

第二条命令展示了 CLI 的用法,引擎名作为子命令参数,--format 支持 README 里列出的 JSON、Markdown、Text 和 NdJSON 四种输出。源码构建则是 git clone 之后 go build -o openserp . 再 ./openserp serve。

第一次验证服务是否正常,README 给的 curl 是:

curl "http://127.0.0.1:7000/mega/search?engines=bing,google&text=golang+vs+rust&extract=1&mode=any"

另外 README 的 Features 里提到 Proxies、cache 和 resilient mode,但正文没有给出对应的配置键名。如果你需要挂代理,得去仓库的配置示例里找实际字段,这一点文档是薄的。

SDK 与 MCP:谁在替你做集成

README 列出了四个官方客户端,覆盖 JavaScript/TypeScript、Python、AI agent 的 MCP server,以及 n8n 的社区节点。它们的共同设计是双模式:指向你自托管的服务器时设置 baseUrl,指向官方托管版时设置 apiKey。

JavaScript 侧的用法在 README 片段里能看到 import { OpenSERP } from "@openserp/sdk",安装命令是 npm install @openserp/sdk。Python 侧是 pip install openserp。MCP server 通过 npx @openserp/mcp 启动,这条路径是给 Claude、Cursor 这类支持 MCP 的客户端直接接搜索能力用的。n8n 节点则面向低代码自动化流程。

这里有一个需要留意的地方:SDK 和 MCP 的源码放在 openserpapi 这个组织下,而不是 karust 主仓库里。也就是说主仓库是 Go 服务端,客户端是独立维护的项目。版本对齐要靠你自己确认,尤其是当你锁定 v0.8.12 的时候,SDK 是否跟这个响应 schema 完全一致,README 没有给出兼容性矩阵。

从发布节奏看,v0.8.6 到 v0.8.12 之间隔了大约三周,v0.8.12 与最近一次 push 在同一天。这个频率说明项目处于活跃维护状态,但也意味着 schema 和字段存在变动可能,生产环境建议固定镜像 tag 而不是用 latest。

反爬、验证码与渲染成本:它不解决的部分

浏览器渲染抓取搜索引擎这件事,根本矛盾在于:搜索引擎不希望被自动化抓取,而 OpenSERP 的全部价值就建立在自动化抓取上。README 提到 Proxies 和 resilient mode,但没有说明 resilient mode 具体做了什么,是重试、降级还是切换引擎,从文档看不出来。

可以确定的是几个结构性限制。第一,mode=any 在示例里返回的 meta 中 engines_responded 只有 bing,engines_failed 为空,这说明它拿到第一个响应就返回了,Google 的结果可能根本没等到。如果你需要多引擎的完整对比,就不能用 any 模式,代价是延迟取决于最慢的那个引擎。

第二,extract=1 会真的去抓目标页面。示例里的 extracted.mode_used 是 fast,说明提取有快慢两档,但 README 没有解释 fast 和另一档的区别,也没有说明超时和失败时 extracted 字段是缺失还是报错。

第三,没有代理的情况下,高频查询几乎必然触发验证码或 IP 限流。README 把代理列为特性,等于承认了这一点。这意味着真实成本不只是服务器,还有代理池的采购和维护,以及处理验证码的人力。

如果你的查询量很小、频率很低,这些都不是问题;一旦上量,运维复杂度会迅速超过自己写一个简单爬虫的程度。

和 SerpApi 这类托管服务比,差别在责任边界

最直接的对比对象是 SerpApi。两者的输入输出形态接近,都提供统一 JSON 和多引擎覆盖,但责任划分完全不同。

SerpApi 把代理轮换、验证码处理、引擎改版适配、可用性保障都包在服务里,你付的是这笔钱,换来的是不需要关心为什么今天 Google 的结果是空的。OpenSERP 把这些全部交还给你:代理要自己配,验证码要自己想办法,引擎 DOM 改版导致解析失败要等上游修复或者自己提 PR。README 里 resilient mode 这个词暗示作者意识到了稳定性问题,但具体机制没有展开。

另一条路线是自己写爬虫加解析。OpenSERP 相对这种方案的优势是已经处理了六个引擎的解析差异、SERP features 的提取、结果去重聚类和正文转 markdown。这些工作单独做一遍并不轻松,尤其是 AI summaries 和 people-also-ask 这类动态模块。

所以选择逻辑是:查询量小、预算够,用托管服务;查询量大、有运维能力、需要覆盖托管服务不支持的引擎,用 OpenSERP;只是想抓一两个固定站点的搜索结果,那可能一个几十行的脚本就够了。

MIT 许可证与升级代价

项目采用 MIT 许可证,这是最宽松的一类,允许商用、修改、再分发,义务主要是保留版权声明和许可文本。需要注意 MIT 覆盖的是 OpenSERP 这个软件的代码,不覆盖你抓取到的搜索结果内容,也不覆盖搜索引擎的服务条款。抓取行为本身的合规性取决于你抓什么、抓多少、用来做什么,这跟许可证是两件事,具体判断需要咨询法律意见。

升级成本方面,README 没有提供 changelog 或迁移指南。从 v0.8.3 到 v0.8.12 经历了多次小版本迭代,响应示例里的 version 字段是 2.1,这个版本号和 release tag 不是一套编号,说明 API schema 有自己的版本线。如果你的代码依赖具体字段,升级前应该对比新旧响应,而不是只看 release 号。

自托管还带来一项隐性成本:搜索引擎改版是不定期的,解析逻辑失效通常表现为结果为空或字段缺失,而不是报错。你需要监控 engines_failed 和结果条数,否则可能几天后才发现排名数据已经断了。

什么时候该用,什么时候该换

适合的场景有三个。做跨引擎排名对比的 SEO 工具,因为 clusters 结构直接给出了同一 URL 在不同引擎的名次。给 LLM 或 agent 提供搜索能力,因为 MCP server 和 SDK 已经铺好了接入路径,extract=1 还能省掉单独的正文抓取环节。需要 Yandex 或 Baidu 数据而商业 API 支持不好的项目,这是 README 明确强调的差异点。

不适合的场景同样明确。需要稳定 SLA 的生产搜索功能,因为自托管意味着稳定性由你的代理和机器决定。对单次查询延迟敏感的场景,浏览器渲染的时间成本摆在那里。没有运维资源的小团队,代理池和验证码处理会变成持续负担。

动手前建议按这个顺序验证:先用 Docker 起一个实例,用 README 里那条 curl 确认目标引擎在你的网络环境下能返回结果;然后关掉 mode=any 测试多引擎并发,看 engines_failed 里出现哪些引擎;最后开 extract=1 跑一批真实 URL,确认正文抽取的成功率和格式是否符合你的下游需求。这三步都过了,再考虑把它接进生产流程。

编辑结论

如果你的场景是多引擎排名对比、LLM 检索增强或 SEO 批量抓取,并且团队里有人愿意维护代理池和浏览器渲染环境,OpenSERP 值得先在测试机上跑通再决定是否接入生产;如果你需要的是稳定的 SLA、合规的数据来源,或者没有精力处理验证码与 IP 封禁,那它就不是合适的工具,按次计费的商业 SERP API 仍然是更省事的选择。动手前先确认三件事:目标引擎在无代理情况下能返回多少条结果、extract 模式抓取的页面是否涉及你不该抓取的内容、以及 Docker 镜像与 Go module 的版本是否与你锁定的 v0.8.12 一致。

官方来源

  1. karust/openserp on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
社区笔记

社区笔记