HN 日本語サマリー

← 一覧へ戻る
プログラミング

RustによるSearXNG

SearXNG in Rust (github.com)

54 pointsby dluuuu19 コメント

要約

SearXNGスタイルのメタ検索エンジン「SearXNG in Rust」は、Rustで開発されています。このプロジェクトは、複数の検索エンジンに同時にクエリを送信し、HTML結果をスクレイピングし、正規化されたURLで重複を除去し、Reciprocal Rank Fusion (RRF) を使用してランキングを行います。ライブラリとしてもサーバーとしても利用可能で、Rust 1.75以降が必要です。

全文翻訳

metadata-search-engine-rs Rustで書かれたSearXNGスタイルのメタ検索エンジン。 複数の検索エンジンにクエリを同時にファンアウトし、HTML結果をスクレイピングし、正規化されたURLで重複を除去し、Reciprocal Rank Fusion (RRF) を使用してランキングします。 仕組み 検索リクエストが GET /search?q=<query> に到着します。 クエリは、reqwest を介して DuckDuckGo、Brave、Startpage、Yahoo に同時に送信されます。 各エンジンは、scraper(Mozilla の html5ever 上の CSS セレクタ)を使用して HTML レスポンスを解析します。 結果は正規化された URL(追跡パラメータを削除、ロケールプレフィックスを削除、クエリパラメータをソート)で重複排除されます。 重複した URL はマージされ、RRF(スコア = Σ 1/(60 + rank) エンジン間)でスコアリングされます — 複数のエンジンによって返されたページはより高くランク付けされます。 トップの結果は JSON として返されます。 要件 Rust 1.75+ Cargo インストール ライブラリとして cargo add metadata-search-engine-rs または Cargo.toml に手動で追加します。 [dependencies] metadata-search-engine-rs = "0.1" サーバーとして(ソースから) git clone https://github.com/MikeLuu99/searxng-rust cd searxng-rust cargo build --release 実行 PORT=8080 MAX_RESULTS=20 cargo run デバッグログを有効にする: RUST_LOG=debug cargo run 例 Cargo.toml に追加します。 [dependencies] metadata-search-engine-rs = "0.1" tokio = { version = "1", features = ["full"] } 単一のエンジンをクエリする use std::sync::Arc; use metadata_search_engine_rs::engines::{DuckDuckGoEngine, SearchEngine, build_http_client}; #[tokio::main] async fn main() -> anyhow::Result<()> { let client = Arc::new(build_http_client()?); let engine = DuckDuckGoEngine { client }; let results = engine.search("rust programming", 5).await?; for r in results { println!("{}\n {}", r.title, r.url); } Ok(()) } すべてのエンジンにファンアウトして RRF スコアリングされた結果を取得する use std::sync::Arc; use metadata_search_engine_rs::{ aggregator::{aggregate, query_all_engines}, engines::{BraveEngine, DuckDuckGoEngine, SearchEngine, StartpageEngine, YahooEngine, build_http_client}, }; #[tokio::main] async fn main() -> anyhow::Result<()> { let client = Arc::new(build_http_client()?); let engines: Vec<Arc<dyn SearchEngine>> = vec![ Arc::new(DuckDuckGoEngine { client: Arc::clone(&client) }), Arc::new(BraveEngine { client: Arc::clone(&client) }), Arc::new(StartpageEngine { client: Arc::clone(&client) }), Arc::new(YahooEngine { client: Arc::clone(&client) }), ]; let (successes, failures) = query_all_engines(&engines, "rust programming", 10).await; for (name, err) in &failures { eprintln!("engine {name} failed: {err}"); } let results = aggregate(successes, 10); for r in &results { println!("[{:.3}] ({}) {}", r.score, r.engines.join(", "), r.title); println!(" {}", r.url); } Ok(()) } 特定のエンジンのみを使用する use std::sync::Arc; use metadata_search_engine_rs::{ aggregator::{aggregate, query_all_engines}, engines::{BraveEngine, DuckDuckGoEngine, SearchEngine, build_http_client}, }; #[tokio::main] async fn main() -> anyhow::Result<()> { let client = Arc::new(build_http_client()?); let engines: Vec<Arc<dyn SearchEngine>> = vec![ Arc::new(DuckDuckGoEngine { client: Arc::clone(&client) }), Arc::new(BraveEngine { client: Arc::clone(&client) }), ]; let (successes, _) = query_all_engines(&engines, "tokio async rust", 5).await; for r in aggregate(successes, 5) { println!("{} — {}", r.title, r.url); if let Some(snippet) = r.snippet { println!(" {snippet}"); } } Ok(()) } API GET /health curl http://localhost:3000/health { "status": "ok" } GET /search?q=<query> curl "http://localhost:3000/search?q=rust" { "query": "rust", "results": [ { "title": "Rust Programming Language", "url": "https://rust-lang.org/", "snippet": "A language empowering everyone to build reliable and efficient software.", "engines": ["duckduckgo", "brave", "startpage", "yahoo"], "score": 0.049 } ], "engines_queried": ["duckduckgo", "brave", "startpage", "yahoo"], "engines_failed": [] } エラーレスポンス: クエリが見つからない場合 ステータス 400 ボディ {"error": "query parameter 'q' is required"} 空のクエリの場合 ステータス 400 ボディ {"error": "query parameter 'q' cannot be empty"} すべてのエンジンが失敗した場合 ステータス 503 ボディ {"error": "all engines failed to respond"} テスト すべての単体テスト cargo test 特定のモジュール cargo test normalizer cargo test aggregator cargo test engines::duckduckgo cargo test engines::brave cargo test engines::startpage cargo test engines::yahoo cargo test server::handlers ライブテスト(実際の検索エンジンにヒット — インターネット接続が必要) cargo test -- --ignored test_live ライブテストはデフォルトではCIで実行されないように #[ignore] がマークされています。実際のサイトに対してHTMLセレクタがまだ機能するかどうかを確認するために、手動で実行してください。 ターミナルUI ratatui ベースの TUI がクレートとして利用可能で、ここでインストールできます。コードは GitHub 経由でアクセスできます。 新しい検索エンジンの追加 src/engines/<name>.rs を作成します。 Arc<reqwest::Client> を保持する構造体を定義します。 SearchEngine トレイトを実装します。 fn name(&self) -> &'static str { "myengine" } fn search<'a>( &'a self, query: &'a str, max_results: usize, ) -> BoxFuture<'a, Result<Vec<SearchResult>, EngineError>> { Box::pin(async move { // HTMLを取得し、scraperで解析し、Vec<SearchResult>を返す }) } engines/mod.rs に追加し、main.rs で配線します。