返回

文章详情

用 Rust 实现的 SearXNG

Hacker News2026年8月3日 16:41

一个用 Rust 编写的 SearXNG 风格的元数据搜索引擎。并发向多个搜索引擎发送查询,抓取它们的 HTML 结果,根据标准化的 URL 去重,并使用倒数排名融合 (RRF) 进行排名。工作原理 搜索请求到达 GET /search?q=<query> 查询并发发送到 DuckDuckGo、Brave、Startpage 和 Yahoo 使用 reqwest 每个搜索引擎使用 scraper(基于 Mozilla 的 html5ever 的 CSS 选择器)解析 HTML 响应 根据标准化的 URL 去重(去除跟踪参数、移除区域前缀、对查询参数进行排序) 合并重复的 URL 并使用 RRF 评分(得分 = Σ 1/(60 + 排名) 在多个引擎之间) — 被多个引擎返回的页面排名更高 返回的顶级结果作为 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 metadata-search-engine-rs cargo build --release 运行 cargo run PORT=8080 MAX_RESULTS=20 cargo run --release 启用调试日志: RUST_LOG=debug cargo run 示例 将其添加到你的 Cargo.toml: [ dependencies ] metadata-search-engine-rs = "0.1" tokio = { version = "1", features = [ "full" ] } 查询单个引擎 使用 std::sync::Arc; 使用 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 排名结果 使用 std::sync::Arc; 使用 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(()) } 仅使用特定引擎 使用 std::sync::Arc; 使用 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 编程语言", "url": "https://rust-lang.org/", "snippet": "一种赋权每个人构建可靠和高效软件的语言。", "engines": ["duckduckgo", "brave", "startpage", "yahoo"], "score": 0.049 }], "engines_queried": ["duckduckgo", "brave", "startpage", "yahoo"], "engines_failed": [] } 错误响应: 情况 状态 主体 缺少 q 400 {"error": "查询参数 'q' 是必需的"} 空 q 400 {"error": "查询参数 'q' 不能为空"} 所有引擎都失败 503 {"error": "所有引擎都失败回应"} 测试 # 所有单元测试 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 实时测试被标记为 #[ignore],因此它们在 CI 中默认不运行。手动运行以验证 HTML 选择器仍然对真实网站有效。 终端 UI 提供一个基于 ratatui 的 TUI。

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

请我喝杯咖啡