openserp レビュー: ブラウザ描画型セルフホストSERP APIの実用性と境界線
Self-hosted SERP API for AI, SEO & automation. Browser-rendered Google, Bing, Yandex, Baidu, DuckDuckGo and Ecosia search with page extraction 🎉
ひと目でわかる
- これは何?
- Google、Bing、Yandex、Baidu、DuckDuckGo、Ecosia の検索結果を自前のサーバーでJSON化するGo製ツール。APIキーと従量課金を避けたいエンジニアにとっての現実的な選択肢を、リポジトリの記述だけを根拠に検討する。
- 誰に向いている?
- 自前のインフラで検索結果を構造化データとして扱いたい開発者、特にLLMやエージェントの検索ツールとして使いたい人、複数エンジンを横断して重複排除したいSEO担当者には向いている。逆に、検索結果の完全な再現性や安定したレイテンシをSLAで保証したい用途には向かない。
- 商用利用できる?
- できます。MIT は寛容なライセンスで、著作権表示とライセンス表示を残せば、使用・改変・販売が可能です。
- 今もメンテナンスされている?
- されています。最後のコミットは 56 日前です。
- 何の言語で書かれている?
- 主に Go です(GitHub の言語統計による)。
回答はプロジェクトの GitHub データ(最終同期:2026年9月16日)と当サイトの分析に基づくもので、法的助言ではありません。
オープンソース詳細解説
APIキーと従量課金を避けたい人が最初に読むべき理由
商用のSERP APIは検索ごとに課金され、APIキーの管理と利用上限の監視が運用に加わる。openserpはこの構造を反転させる。READMEの冒頭は「No API keys, no per-search billing: one command gives you live, structured search results on localhost」と述べており、検索リクエストは自分のマシンから出て、結果は自分のサーバーが返す。課金単位は存在せず、代わりにインフラとメンテナンスの負担を引き受けることになる。
対象読者は三つに分かれる。第一に、LLMやエージェントに検索ツールを持たせたい開発者。READMEは「Use it as a search tool for LLMs and agents」と用途を明示している。第二に、SEOのランク追跡をバックエンドで回したい担当者。第三に、商用APIが対応していないエンジンを含めたい人で、READMEは「including engines the paid APIs don't cover」と書いている。英語圏のAPIでは手薄になりがちなYandex、Baidu、Ecosiaが同じJSONスキーマで扱える点は、地域をまたぐ調査では効いてくる。
ブラウザ描画でSERPを取るという設計と、その代償
openserpはHTTPリクエストでHTMLを取るのではなく、ブラウザで検索ページを描画して結果を抽出する。リポジトリの説明文は「Browser-rendered Google, Bing, Yandex, Baidu, DuckDuckGo and Ecosia search」と明記しており、これが6エンジン共通のJSONスキーマを成立させている仕組みだ。描画後のDOMからタイトル、URL、スニペット、favicon、ドメイン情報を取り出し、エンジンごとの差を吸収する。
この設計には代償がある。ブラウザの起動と描画はHTTP取得より重く、検索エンジン側のHTML構造が変われば抽出ロジックが壊れる。READMEが示すレスポンス例では meta.took_ms が720ミリ秒と記録されているが、これは著者が提示した一例であり、負荷やネットワーク条件によって変動する性質の数値だ。安定したレイテンシを前提にした設計をする場合は、この点を割り引いて考える必要がある。
レスポンスには meta.engines_responded と meta.engines_failed が含まれる。エンジン単位で成功と失敗が分離されるため、複数エンジンへ投げたときに一部が落ちても全体が失敗しない。障害の切り分けを呼び出し側に委ねる構造だと言える。
mega/search が複数エンジンをどう統合するか
単一エンジンのエンドポイントに加えて、複数エンジンを一度に叩く megasearch がある。READMEの例では次のように呼ぶ。
curl "http://127.0.0.1:7000/mega/search?engines=bing,google&text=golang+vs+rust&extract=1&mode=any"
engines にカンマ区切りで対象を渡し、text がクエリ、extract=1 で検索結果のページ本文も取得する。mode=any は「最初に応答したエンジンを返す」挙動で、例のレスポンスでは engines_requested が bing と google の2つ、engines_responded が bing のみになっている。つまり mode=any は速さを優先し、網羅性を捨てる指定である。全エンジンの結果を突き合わせたい場合は mode を変える必要がある。
統合の結果は results 配列だけでなく clusters にも現れる。同じ canonical_url を持つ結果をまとめ、occurrences にどのエンジンの何位だったかを記録し、engines_count と best_rank と score を付ける。複数エンジンで上位に来たURLほど score が高くなる仕組みで、ランク追跡や重複排除を呼び出し側で書かずに済む。検索結果の横断比較を自前で組む手間を、APIのレスポンス形式に押し込んだ形だ。
起動方法と設定の実際
起動経路は三つ用意されている。Docker Hub の karust/openserp を使う方法、go install、ソースからのビルドだ。READMEのコマンドをそのまま引く。
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 に絞っている点は注目に値する。認証が前提にないAPIを外部に晒さないための書き方で、そのまま踏襲するのが無難だ。serve サブコマンドは -a でバインドアドレス、-p でポートを取る。
Go環境がある場合は次の二行で済む。
go install github.com/karust/openserp@latest openserp search duckduckgo "open source serp api" --format markdown
CLIは search サブコマンドでエンジン名を取り、--format で出力形式を指定する。対応形式は JSON、Markdown、Text、NdJSON の4つ。ソースからビルドする場合は git clone の後に go build -o openserp . を実行し、./openserp serve で起動する。
機能としてREADMEが挙げているのは、プロキシ、キャッシュ、resilient mode、そして6エンジン共通のJSONスキーマだ。フィルタは言語、日付範囲、ファイル種別、サイト指定に対応する。SERP features としてAI要約、answer box、people-also-ask、関連検索も取得対象に含まれる。画像検索も同じ枠組みに入っている。
extract=1 が返すものと、返さないもの
extract=1 を付けると、検索結果の各URLについてページ本文を取得し、markdownとして results の extracted フィールドに格納する。READMEの例では format が markdown、mode_used が fast、content にページ冒頭の見出しと本文が入り、fetched_at に取得時刻が記録されている。検索と本文取得を1回の呼び出しで済ませられるため、検索結果をLLMに渡す前段の処理を短縮できる。
ただし抽出が成功するのは、対象ページがブラウザで描画可能で、本文がDOM上に存在する場合に限られる。JavaScriptで後から描画されるページや、ボットを拒否するページでは結果が変わる。READMEの例でも抽出が付いているのは1件目の結果だけで、2件目のWikipediaには extracted フィールドが存在しない。全件に本文が付く保証はなく、呼び出し側は extracted の有無を確認してから使う必要がある。
content は省略記号で切れた形で例示されており、完全な本文が常に返るとは限らない。抽出の深さや上限に関する記述は提供された範囲には見当たらない。
向かない場面: 再現性とSLAが要る用途
openserpが構造的に苦手とするのは、同じクエリに対して同じ結果を保証したい場面だ。検索エンジン自体がパーソナライズと地域差と時間変動を持つため、ブラウザ描画で取った結果は呼ぶたびに変わりうる。READMEのレスポンス例に含まれる request_id と requested_at は、各レスポンスが時点付きの記録であることを示している。監査や再現実験のために結果を固定したい用途では、取得時点の生データを自分で保存しておく必要がある。
もう一つの境界は、検索エンジン側の変更への追従だ。抽出ロジックは各エンジンのDOM構造に依存する。提供された情報からは、どのくらいの頻度で構造変更に追従しているかを確認できない。v0.8.12 が 2026-07-22、v0.8.6 が 2026-06-29、v0.8.3 が 2026-06-12 というリリース間隔は比較的短いが、これが機能追加なのか修正なのかはリリースノートを見ないと分からない。
大量のクエリを短時間に投げる用途も慎重に考えるべきだ。ブラウザ描画はリソースを消費し、検索エンジン側から見れば自動アクセスのパターンになる。READMEがプロキシと resilient mode を機能として挙げていることは、この問題が実際に発生することを示唆している。
代替手段との違い: 商用SERP APIと自前スクレイパー
比較対象として最も分かりやすいのは商用SERP APIだ。違いは課金と運用の置き場所にある。商用APIは検索ごとの料金と引き換えに、エンジン側の変更追従とプロキシ管理をベンダーが引き受ける。openserpはその両方を利用者側に移す。コストは下がるが、壊れたときに直すのは自分である。
自前のスクレイパーを書く選択肢との違いは、抽象化の層にある。スクレイパーを自作する場合、エンジンごとにHTML構造を解析し、結果を共通形式に正規化し、失敗時の再試行とプロキシローテーションを実装する。openserpはこの部分を6エンジン共通のJSONスキーマとして既に提供し、clusters による重複排除まで含める。自作する理由があるとすれば、特定エンジンの特殊なフィールドを細かく取りたい場合や、抽出ロジックを完全に制御したい場合だろう。
SDKの有無も判断材料になる。公式クライアントとして JavaScript/TypeScript の @openserp/sdk、Python の openserp、AIエージェント向けの @openserp/mcp、n8n のコミュニティノードが用意されている。READMEによれば、これらは baseUrl を自前サーバーに向ければ self-hosted で、apiKey を設定すれば hosted API で動く。同じクライアントコードのまま、インフラの持ち方を後から変えられる構造だ。
ライセンスとメンテナンスの見取り図
ライセンスはMIT。商用利用を含めて制限が少なく、改変と再配布が許される。ただしMITが保証するのはコードの利用条件であって、検索エンジンの利用規約ではない。ブラウザで検索ページを取得する行為が各エンジンの規約にどう触れるかは、ライセンスとは別に利用者自身が確認すべき領域だ。ここで法的な判断を示すことはできない。
メンテナンスコストは、追従すべき対象の数で決まる。6エンジンそれぞれのDOM構造、SERP features の有無、抽出ロジックの挙動が対象になる。リリースは v0.8.3 から v0.8.12 まで約1か月半で複数回出ており、継続的な更新があることは確認できる。ただし更新の内容がエンジン追従なのか機能追加なのかは、提供された情報からは判別できない。
アップグレードの判断材料として使えるのは、レスポンスの meta.version だ。READMEの例では "2.1" が返っている。APIレスポンスのスキーマ版が明示されるため、クライアント側で互換性を確認する手がかりになる。Dockerイメージは karust/openserp:latest のほかタグ付きで公開されているため、latest を追うかバージョンを固定するかを選べる。
編集部の結論
自前のインフラで検索結果を構造化データとして扱いたい開発者、特にLLMやエージェントの検索ツールとして使いたい人、複数エンジンを横断して重複排除したいSEO担当者には向いている。逆に、検索結果の完全な再現性や安定したレイテンシをSLAで保証したい用途には向かない。導入前に確認すべきは、対象エンジンのHTML構造変更に対する追従頻度、プロキシ設定の実効性、そして抽出機能が返すmarkdownの品質である。READMEが示すのは「動くAPI」の仕様であって、検索エンジン側の仕様変更にどこまで耐えるかの保証ではない。
コミュニティノート