Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/Cargo.lock
*.log
sandbox/
.env
19 changes: 18 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "serpapi"
version = "1.2.0"
version = "1.3.0"
edition = "2021"
description = "Integrate search data into your rust application. This library is the official wrapper for SerpApi. SerpApi supports Google, Google Maps, Google Shopping, Baidu, Yandex, Yahoo, eBay, App Stores, and more."
readme = "README.md"
Expand All @@ -25,8 +25,8 @@ include = [
serde_json = "1.0"
# Asynchronous runtime
tokio = { version = "1", features = ["full"] }
# HTTP client
reqwest = "0.11.7"
# HTTP client, multipart is required by the Image API upload
reqwest = { version = "0.11.7", features = ["multipart"] }

[dev-dependencies]
criterion = { version = "0.5", features = ["async_tokio", "html_reports"] }
Expand Down
138 changes: 136 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Integrate search data into your AI workflow, RAG, fine-tuning, or Rust applicati
To install in your Rust application, update Cargo.toml:

```sh
serpapi="1.2.0"
serpapi="1.3.0"
```

## Quick start
Expand Down Expand Up @@ -102,6 +102,33 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

More examples are available below.

### Response formats

Use `search` for structured results decoded into a `serde_json::Value`:

```rust
let results = client.search(parameter).await?;
```

Use `md` for a token-efficient Markdown `String` optimized for LLMs and AI agents.
It returns clean headings, links and tables behind a YAML frontmatter while using
roughly half the tokens of the JSON output:

```rust
let markdown = client.md(parameter).await?;
```

Use `html` when you need the raw response from the search engine:

```rust
let raw_html = client.html(parameter).await?;
```

`md` and `html` always force the output format, so any `output` search parameter is ignored.
Archived results are also available as Markdown with `client.search_archive_md(&id).await?`.

Learn more about [SerpApi Markdown output](https://serpapi.com/markdown-output).

#### Documentation

* [Full documentation on SerpApi.com](https://serpapi.com)
Expand All @@ -118,7 +145,7 @@ default.insert("api_key".to_string(), "your_secret_key".to_string());
let client = Client::new(default).unwrap();
let mut parameter = HashMap::<String, String>::new();
parameter.insert("q".to_string(), "coffee".to_string());
let markdown = client.markdown(parameter).await.expect("request");
let markdown = client.md(parameter).await.expect("request");
```

It returns the search results formatted as Markdown.
Expand All @@ -136,6 +163,39 @@ let locations = data.as_array().unwrap();

It returns the first 3 locations matching Austin (Texas, Texas, Rochester).

### Image API

Upload an image with the Image API, then use its `image_id` with a Search API engine
supporting uploaded images such as [Google Lens](https://serpapi.com/google-lens-upload-an-image).
Supported formats are jpg, jpeg, png and webp up to 500 KB. Uploaded image IDs expire after 10 minutes.

```rust
let mut default = HashMap::<String, String>::new();
default.insert("api_key".to_string(), "your_secret_key".to_string());
let client = Client::new(default).unwrap();

// upload an image from a file path
let upload = client.upload_image("./image.jpg", HashMap::new()).await?;
let image_id = upload["image_id"].as_str().expect("image id");

// search with google lens using the uploaded image
let mut parameter = HashMap::<String, String>::new();
parameter.insert("engine".to_string(), "google_lens".to_string());
parameter.insert("image_id".to_string(), image_id.to_string());
let results = client.search(parameter).await?;
let visual_matches = results["visual_matches"].as_array().unwrap();
```

An in-memory image can be uploaded with `upload_image_bytes`:

```rust
let upload = client.upload_image_bytes(image_bytes, "image.png", HashMap::new()).await?;
```

`upload_image` returns an error when the image is rejected by the API, for instance because of an unsupported format.

[See Image API documentation](https://serpapi.com/image-api) · [See Google Lens image upload documentation](https://serpapi.com/google-lens-upload-an-image)

### Search Archive API

```rust
Expand Down Expand Up @@ -167,6 +227,12 @@ println!("{}", archived_results);
assert_eq!(archive_id, search_id);
```

The same archived search is available as Markdown:

```rust
let markdown = client.search_archive_md(&id).await.expect("request");
```

### Account API
```rust
let client = Client::new(HashMap::<String, String>::new());
Expand All @@ -178,11 +244,17 @@ let account = client.account(parameter).await.expect("request");
It returns your account information.

### Technical features
- Search results as JSON with `search`, Markdown with `md`, or raw search engine HTML with `html`
- Image upload for Google Lens and other engines with `upload_image`
- Dynamic JSON decoding using Serde JSON
- Asyncronous HTTP request handle method using tokio and reqwest
- Async tests using Tokio

### Change log
- 1.3.0:
- Add Image API support with `client.upload_image(path, parameter)` and `client.upload_image_bytes(bytes, file_name, parameter)`.
- Requests are sent over https directly instead of following the http redirect.
- 1.2.1: Remove `markdown()` which called a non-existent endpoint, use `client.md(parameter)` and `client.search_archive_md(&id)` to return search results formatted as Markdown.
- 1.2.0: Add `markdown` method to return search results formatted as Markdown.
- 1.1.0: Always reuse the same client object instead of creating a new one for each search.
- This is a breaking change for the API because the client must be unwrapped in the main function.
Expand Down Expand Up @@ -1496,6 +1568,68 @@ Ok(())
* source code: [examples/google_images_search.rs](https://github.com/serpapi/serpapi-rust/blob/master/examples/google_images_search.rs)
see: [https://serpapi.com/images-results](https://serpapi.com/images-results)

### Search google lens with an uploaded image
```rust
let mut default = HashMap::new();
default.insert("api_key".to_string(), "your_secret_api_key".to_string());
default.insert("engine".to_string(), "google_lens".to_string());
// initialize the search engine
let client = Client::new(default).unwrap();

// upload the image given on the command line,
// or download a sample image when no path is provided:
// cargo run --example google_lens_upload_image -- ./image.jpg
println!("uploading...");
let upload = match env::args().nth(1) {
Some(path) => client.upload_image(&path, HashMap::new()).await?,
None => {
let image = reqwest::get("https://i.imgur.com/5bGzZi7.jpg")
.await?
.bytes()
.await?
.to_vec();
client
.upload_image_bytes(image, "image.jpg", HashMap::new())
.await?
}
};
let image_id = upload["image_id"].as_str().unwrap();
println!(" - image uploaded with id: {}", image_id);

// let's search with google lens using the uploaded image
let mut parameter = HashMap::new();
parameter.insert("image_id".to_string(), image_id.to_string());

// search returns a JSON as serde_json::Value which can be accessed like a HashMap.
println!("waiting...");
let results = client.search(parameter).await?;
println!("results received");
println!("--- JSON ---");
let status = &results["search_metadata"]["status"];
if status != "Success" {
println!("search failed with status: {}", status);
} else {
println!("search is successfull");
let visual_matches = results["visual_matches"].as_array().unwrap();
println!(" - number of visual_matches: {}", visual_matches.len());
println!(
" - visual_matches first result description: {}",
results["visual_matches"][0]
);
println!(
" - async search completed with {}\n",
results["search_parameters"]["engine"]
);
}

print!("ok");
Ok(())

```

* source code: [examples/google_lens_upload_image.rs](https://github.com/serpapi/serpapi-rust/blob/master/examples/google_lens_upload_image.rs)
see: [https://serpapi.com/google-lens-upload-an-image](https://serpapi.com/google-lens-upload-an-image)

## License

[MIT License](LICENSE)
Expand Down
74 changes: 74 additions & 0 deletions examples/google_lens_upload_image.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// search example for google_lens with an uploaded image
//
use serpapi::serpapi::Client;
use std::collections::HashMap;
use std::env;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Read your private API Key from an environment variable.
// Copy/paste from [https://serpapi.com/dashboard] to your shell:
// ```bash
// export API_key="paste_your_private_api_key"
// ```
let api_key = match env::var_os("SERPAPI_KEY") {
Some(v) => v.into_string().unwrap(),
None => panic!("$SERPAPI_KEY environment variable is not set!"),
};

println!("let's initiliaze the client to search on google_lens");
let mut default = HashMap::new();
default.insert("api_key".to_string(), api_key);
default.insert("engine".to_string(), "google_lens".to_string());
// initialize the search engine
let client = Client::new(default).unwrap();

// upload the image given on the command line,
// or download a sample image when no path is provided:
// cargo run --example google_lens_upload_image -- ./image.jpg
println!("uploading...");
let upload = match env::args().nth(1) {
Some(path) => client.upload_image(&path, HashMap::new()).await?,
None => {
let image = reqwest::get("https://i.imgur.com/5bGzZi7.jpg")
.await?
.bytes()
.await?
.to_vec();
client
.upload_image_bytes(image, "image.jpg", HashMap::new())
.await?
}
};
let image_id = upload["image_id"].as_str().unwrap();
println!(" - image uploaded with id: {}", image_id);

// let's search with google lens using the uploaded image
let mut parameter = HashMap::new();
parameter.insert("image_id".to_string(), image_id.to_string());

// search returns a JSON as serde_json::Value which can be accessed like a HashMap.
println!("waiting...");
let results = client.search(parameter).await?;
println!("results received");
println!("--- JSON ---");
let status = &results["search_metadata"]["status"];
if status != "Success" {
println!("search failed with status: {}", status);
} else {
println!("search is successfull");
let visual_matches = results["visual_matches"].as_array().unwrap();
println!(" - number of visual_matches: {}", visual_matches.len());
println!(
" - visual_matches first result description: {}",
results["visual_matches"][0]
);
println!(
" - async search completed with {}\n",
results["search_parameters"]["engine"]
);
}

print!("ok");
Ok(())
}
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,10 @@
//! SerpApi.com enables to do localized search, leverage advanced search engine features and a lot more...
//! A completed documentation is available at [SerpApi](https://serpapi.com).
//!
//! Search results are available as JSON with `search`, as [Markdown](https://serpapi.com/markdown-output)
//! optimized for LLMs and AI agents with `md`, or as raw search engine HTML with `html`.
//!
//! Images can be uploaded with `upload_image` using the [Image API](https://serpapi.com/image-api)
//! and searched with engines such as Google Lens.
//!
pub mod serpapi;
Loading
Loading