SearXNGでひたすらローカルから検索する
SearXNG、たぶん「サーチ・エックス・エヌ・ジー」みたいな読みかただと思うんですけど、これを使ってローカルからびしばし検索をします。
キーワード
- メタ検索エンジン(SearXNG)
- Docker
- Python
課題
「検索エンジンをブラウザで開く」という行為との相性が悪い案件があり、ローカルから制限を気にせず検索できる、という世界観にしたいです。
調査
調べはじめると、kun432さんの記事がばあっと出てきて、「Perplexity」のクローンがあるよ、みたいなことをありがたく教わり、最終的にSearXNGの記事にたどりつきました。
概要
- Dockerを立ち上げる
- SearXNGのイメージを取り込む
- 設定をお好みにする
- バインドしたエンドポイントにパラメーターを叩き送る
- 出力結果(検索結果のJSONやCSV)を加工する
手続き
1. Dockerを立ち上げる
Docker Desktopを使うことでラクをします。
2. SearXNGのイメージを取り込む
たぶんいろんなやりかたがありますが、私はyamlファイル。
services:
searxng:
image: searxng/searxng
container_name: searxng
ports:
- "${PORT}:8080"
volumes:
- "./searxng:/etc/searxng"
environment:
- "BASE_URL=http://0.0.0.0:${PORT}/"
- "INSTANCE_NAME=SEARXNG"
Dockerで起動させる流れやニュアンスは公式ドキュメントを参考。たとえば、公式ではこういう立ち上げかたをしています。
$ mkdir my-instance
$ cd my-instance
$ export PORT=8080
$ docker pull searxng/searxng
$ docker run --rm \
-d -p ${PORT}:8080 \
-v "${PWD}/searxng:/etc/searxng" \
-e "BASE_URL=http://localhost:$PORT/" \
-e "INSTANCE_NAME=my-instance" \
searxng/searxng
2f998.... # container's ID
3. 設定をお好みにする
pullできたイメージのなかの設定ファイルをいじる。JSONで返ってくるようにお願いします。
search:
formats:
- html
+ - json
設定を変えたあとに、docker ps
でコンテナIDを調べて、docker restart {container_id}
をします、たしか。
ちなみに、どこにバインドするかとかは、server
のところにあります。
server:
port: 8888
bind_address: "127.0.0.1"
公式ドキュメントが詳しいので、良さげな設定がないか探してみてください。 engines
とかsearch
とかserver
とかoutgoing
あたりが匂いますね。
4. バインドしたエンドポイントにパラメーターを叩き送る
Seach APIで出来ることについては、公式ドキュメントだよりです。
curlとかでもいいんですけど、本件ではPythonだったのでPythonのrequestsパッケージを利用して飛ばすやつのデモやります。
import requests
import time
import random
# デモではまったくといっていいほど無意味ですが、まずは待つ姿勢を見せる
time.sleep(random.randint(3, 5))
# pagenoはページングの番号です
params = {
"q": "メイド喫茶 おすすめ",
"language": "ja",
"pageno": 1,
"engines": "google",
"format": "json",
}
res = requests.get("http://127.0.0.1",params=params)
output_json = json.loads(res.text)
5. 出力結果(検索結果のJSONやCSV)を加工する
query = output_json["query"] # 検索したクエリが改めて見たい
results = output_json["results"] # 検索結果はここに格納されている
for index, r in enumerate(results):
url = r["url"]
title = r["title"]
content = r["content"]
engine = r["engine"] # "google", "duckduckgo"
position = r["positions"] # 1,2,3,4,5...
ちょっとマシな全文
import sys
import requests
import time
import random
import json
SEARXNG_ENDPOINT = "http://127.0.0.1"
def main(params, engine="google", max_page=5):
for pageno in range(1, max_page + 1):
params["pageno"] = pageno
try:
time.sleep(random.randint(3, 5))
res = requests.get(SEARXNG_ENDPOINT, params=params)
if res.status_code != 200:
print(f"リクエストエラー:ステータスコード {res.status_code}")
return False
except Exception as e:
print(f"リクエストエラー:{e}")
return False
try:
output_json = res.json()
query = output_json["query"]
results = output_json["results"]
except Exception as e:
print(f"検索結果のパースエラー:{e}")
return False
for r in results:
print(f"検索結果:{r}")
try:
url = r["url"]
title = r["title"]
content = r["content"]
engine = r["engine"]
position = r["positions"]
# ここで保存や加工など何かしらの処理
except Exception as e:
print(f"結果の処理エラー:{e}")
return True
if __name__ == "__main__":
count = 0
while True:
count += 1
print(f"{count}回目のループ")
# 検索クエリを毎回かえたいときはここで調整
query = "メイド喫茶 おすすめ"
# 検索パラメーター
params = {
"q": query,
"language": "ja",
"pageno": 1,
"engines": "google",
"format": "json",
}
try:
if not main(params):
print("スクリプトを停止します")
sys.exit()
except Exception as e:
print(f"エラー: {e}")
sys.exit()
余談:Pythonのマネージャー
いまは「uv」が人気急騰中でしょうか。手癖でrye init
しがちですが、そろそろちゃんとuvに移行したいと思います。
(ryeの管理もuvのAstralに移行されたそうなので、、、)
まとめ
DuckDuckGoとは初めましてだったのですが、なんてことない顔して同梱されていました。個人的に検索したいデータは、どちらかというとGoogle検索よりはDuckDuckGoのほうにあったな、という感じです。
Torともいろいろできそうな感じが伝わってきたので、今度はTorとかTailscaleとかとからめた話もしたいですね。
案件募集中
お金ないので案件募集中です。課題を解決します(!)。
Discussion