feat: 初始化Google Search API Wrapper项目

添加项目基础文件,包括Cargo.toml、.gitignore、.env、README.md和src/main.rs。实现基于Actix-web的RESTful API,支持搜索和补全建议功能,使用Google Custom Search API和Google Suggest API获取数据。
This commit is contained in:
Yakumo Hokori 2025-04-27 00:05:45 +08:00
commit 6eeb5f88bb
6 changed files with 2521 additions and 0 deletions

2
.env Normal file
View File

@ -0,0 +1,2 @@
GOOGLE_API_KEY=AIzaSyCB3N08QDsBa3p9DLZy5r5fq1Y727JJPz4
SEARCH_ENGINE_ID=d3655c32541054823

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

2296
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "search"
version = "0.1.0"
edition = "2024"
[dependencies]
actix-web = "4"
reqwest = { version = "0.12.15", features = ["json"] }
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
dotenv = "0.15"

24
README.md Normal file
View File

@ -0,0 +1,24 @@
# Google Search API Wrapper
这是一个使用Rust和Actix-web框架封装的Google Search API服务。
## 功能特性
- 提供RESTful API接口
- 支持JSON格式的请求和响应
- 异步处理提高性能
- 环境变量配置
## 技术栈
- [Actix-web](https://actix.rs/) - Rust的Web框架
- [Reqwest](https://docs.rs/reqwest/) - HTTP客户端
- [Tokio](https://tokio.rs/) - 异步运行时
- [Serde](https://serde.rs/) - 序列化/反序列化
## 快速开始
1. 克隆仓库
```bash
git clone https://github.com/your-repo/search.git
cd search

186
src/main.rs Normal file
View File

@ -0,0 +1,186 @@
use actix_web::{get, web, App, HttpServer, Responder, HttpResponse};
use serde::{Deserialize, Serialize};
use std::env;
use dotenv::dotenv;
use reqwest::header;
// 补全建议的JSON结构
#[derive(Debug, Serialize)]
struct CompletionResponse {
success: bool,
message: String,
suggestions: Vec<String>,
}
// 搜索结果的JSON结构
#[derive(Debug, Serialize, Deserialize)]
struct SearchResult {
title: String,
link: String,
snippet: String,
}
// API响应结构
#[derive(Debug, Serialize)]
struct ApiResponse {
success: bool,
message: String,
data: Option<Vec<SearchResult>>,
current_page: u32,
total_pages: u32,
}
// 查询参数结构
#[derive(Debug, Deserialize)]
struct SearchParams {
q: String,
page: Option<u32>,
}
// 从Google CSE获取搜索结果
async fn fetch_google_search_results(
query: &str,
start: u32,
) -> Result<(Vec<SearchResult>, u32), Box<dyn std::error::Error>> {
dotenv().ok();
let api_key = env::var("GOOGLE_API_KEY").expect("GOOGLE_API_KEY must be set");
let search_engine_id = env::var("SEARCH_ENGINE_ID").expect("SEARCH_ENGINE_ID must be set");
let client = reqwest::Client::new();
let num_results = 10; // 每页10条结果
let response = client
.get("https://www.googleapis.com/customsearch/v1")
.query(&[
("key", &api_key),
("cx", &search_engine_id),
("q", &query.to_string()),
("num", &num_results.to_string()),
("start", &start.to_string()),
])
.send()
.await?;
// 打印原始响应
let raw_response = response.text().await?;
let json: serde_json::Value = serde_json::from_str(&raw_response)?;
// 获取总结果数并计算总页数
let total_results = json["searchInformation"]["totalResults"]
.as_str()
.and_then(|s| s.parse::<u64>().ok()) // 使用u64处理大数字
.unwrap_or(0);
let total_pages = (total_results as f64 / num_results as f64).ceil() as u32;
// 解析结果项
let items = json["items"]
.as_array()
.map(|arr| {
arr.iter()
.map(|item| SearchResult {
title: item["title"].as_str().unwrap_or("").to_string(),
link: item["link"].as_str().unwrap_or("").to_string(),
snippet: item["snippet"].as_str().unwrap_or("").to_string(),
})
.collect()
})
.unwrap_or_else(Vec::new);
Ok((items, total_pages))
}
// 处理搜索请求
// 获取Google搜索建议
async fn fetch_google_suggestions(query: &str) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let url = format!("https://www.google.com/complete/search?q={}&client=chrome", query);
let response = client
.get(&url)
.header(header::USER_AGENT, "Mozilla/5.0")
.send()
.await?;
let suggestions: Vec<String> = response
.json::<Vec<serde_json::Value>>()
.await?
.get(1)
.and_then(|v| v.as_array())
.map(|arr| {
arr.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect()
})
.unwrap_or_else(Vec::new);
Ok(suggestions)
}
// 处理补全建议请求
#[get("/completion")]
async fn completion(params: web::Query<SearchParams>) -> impl Responder {
match fetch_google_suggestions(&params.q).await {
Ok(suggestions) => {
let response = CompletionResponse {
success: true,
message: "Suggestions fetched successfully".to_string(),
suggestions,
};
HttpResponse::Ok().json(response)
}
Err(e) => {
let error_response = CompletionResponse {
success: false,
message: format!("Failed to fetch suggestions: {}", e),
suggestions: Vec::new(),
};
HttpResponse::InternalServerError().json(error_response)
}
}
}
#[get("/search")]
async fn search(params: web::Query<SearchParams>) -> impl Responder {
let current_page = params.page.unwrap_or(1);
let start = (current_page - 1) * 10 + 1; // Google CSE的start参数从1开始
match fetch_google_search_results(&params.q, start).await {
Ok((results, total_pages)) => {
let response = ApiResponse {
success: true,
message: "Search successful".to_string(),
data: Some(results),
current_page,
total_pages,
};
HttpResponse::Ok().json(response)
}
Err(e) => {
let error_response = ApiResponse {
success: false,
message: format!("Search failed: {}", e),
data: None,
current_page: 0,
total_pages: 0,
};
HttpResponse::InternalServerError().json(error_response)
}
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
println!("Server started at http://0.0.0.0:8080");
HttpServer::new(|| {
App::new()
.service(search)
.service(completion)
})
.bind("0.0.0.0:8080")?
.run()
.await
}