diff --git a/README.md b/README.md index 3cf1f3d..a611eca 100644 --- a/README.md +++ b/README.md @@ -3,37 +3,35 @@ [![NuGet](https://img.shields.io/nuget/v/serpapi)](https://www.nuget.org/packages/serpapi) [![Build](https://github.com/serpapi/serpapi-dotnet/actions/workflows/ci.yml/badge.svg)](https://github.com/serpapi/serpapi-dotnet/actions/workflows/ci.yml) -Integrate search data into your .NET application, AI workflow, or LLM/RAG pipeline. This is the official .NET client for [SerpApi](https://serpapi.com). +> **Not yet published.** The `serpapi` package hasn't shipped its first NuGet release — the badge above will go green once v1.0.0 is out. Until then, build from source (see [Contributing](#contributing)) or reference the project directly. -SerpApi supports Google, Google Maps, Google Shopping, Bing, Baidu, Yandex, Yahoo, DuckDuckGo, eBay, Walmart, YouTube, and [100+ engines](https://serpapi.com). +Integrate search data into your AI workflow, RAG / fine-tuning, or .NET application using this official wrapper for [SerpApi](https://serpapi.com). -## Features - -- Async-first with full `CancellationToken` support -- Sync convenience wrappers -- `IAsyncEnumerable` pagination -- Dependency injection integration (`IHttpClientFactory`) -- Targets .NET Standard 2.0, .NET 7, 8, 9, and 10 -- Zero external runtime dependencies +SerpApi supports Google, Google Maps, Google Shopping, Bing, Baidu, Yandex, Yahoo, DuckDuckGo, eBay, Walmart, YouTube, App Stores, and [more](https://serpapi.com). -## Compatibility +Query a vast range of data at scale, including web search results, flight schedules, stock market data, news headlines, and [more](https://serpapi.com). -| Target framework | Minimum consumer runtime | Notes | -|---|---|---| -| `netstandard2.0` | .NET Framework 4.6.1+, .NET Core 2.0+, Mono, Xamarin, UWP | Ships `Microsoft.Bcl.AsyncInterfaces` and `System.Text.Json` as polyfills | -| `net7.0` | .NET 7 | Out of support upstream, still built and tested | -| `net8.0` | .NET 8 (LTS) | | -| `net9.0` | .NET 9 (STS) | | -| `net10.0` | .NET 10 (LTS) | Used for `dotnet pack` and local dev builds | +## Features -CI builds and tests every target framework above on both **Linux** and **Windows**. Building locally requires the [.NET 10 SDK](https://dotnet.microsoft.com/download) (a single SDK at or above the highest TFM can build all lower ones). +- `async`-first → non-blocking API with full `CancellationToken` support, plus sync convenience wrappers +- `IAsyncEnumerable` pagination → stream every page of results with one loop +- Dependency injection → `IHttpClientFactory` integration for ASP.NET Core / generic host +- Broad reach → targets .NET Standard 2.0 and .NET 7, 8, 9, 10 +- Zero external runtime dependencies on modern .NET +- Extensive documentation and real-world examples included throughout ## Installation +.NET 7 and higher are supported, plus .NET Framework 4.6.1+ via .NET Standard 2.0. Check [Supported .NET versions](#supported-net-versions) for the full matrix. + +If you are upgrading from the legacy [google-search-results-dotnet](https://github.com/serpapi/google-search-results-dotnet) library, check our [migration guide](#migration-quick-guide). + ```bash dotnet add package serpapi ``` +[NuGet package page](https://www.nuget.org/packages/serpapi) + ## Simple Usage ```csharp @@ -53,64 +51,74 @@ foreach (var result in results.OrganicResults!.Value.EnumerateArray()) } ``` -### Error handling +This example runs a search for "coffee" on Google Light. See the [playground](https://serpapi.com/playground) to generate your own code. -```csharp -try -{ - using var results = await client.SearchAsync(parameters); -} -catch (SerpApiKeyException) { /* 401 — invalid API key */ } -catch (SerpApiHttpException ex) { /* 429, 500, etc — ex.StatusCode */ } -catch (SerpApiTimeoutException) { /* request timed out */ } -catch (SerpApiException ex) { /* catch-all */ } -``` +The SerpApi key can be obtained from [serpapi.com/signup](https://serpapi.com/users/sign_up?plan=free). + +Environment variables are a secure, safe, and easy way to manage secrets. +Set `export SERPAPI_KEY=` in your shell. +.NET accesses these variables via `Environment.GetEnvironmentVariable("SERPAPI_KEY")`. -## Search API usage +## Search API advanced usage with Google search engine -### Get JSON results +This example dives into the available parameters for the Google search engine. +The list of parameters depends on the chosen search engine. ```csharp -using var results = await client.SearchAsync(new Dictionary +using SerpApi; + +// serpapi client created with an API key and optional configuration +using var client = new SerpApiClient( + Environment.GetEnvironmentVariable("SERPAPI_KEY")!, + new SerpApiClientOptions + { + Timeout = TimeSpan.FromSeconds(30) // HTTP timeout (default: 60s) + }); + +// search query overview (more fields available depending on search engine) +var parameters = new Dictionary { - ["engine"] = "google_light", - ["q"] = "coffee", - ["num"] = "10" -}); + // select the search engine (full list: https://serpapi.com/) + ["engine"] = "google", + // actual search query + ["q"] = "Coffee", + // then add search engine specific options. + // for example: google specific parameters: https://serpapi.com/search-api + ["google_domain"] = "google.com", + ["location"] = "Austin, Texas", // see: Location API + ["device"] = "desktop", // desktop|mobile|tablet + ["hl"] = "en", // Google UI language + ["gl"] = "us", // Google country + ["safe"] = "active", // safe search flag + ["start"] = "0", // pagination offset + ["num"] = "10" // number of results +}; +// search results as a parsed response +using var results = await client.SearchAsync(parameters); Console.WriteLine(results.SearchId); Console.WriteLine(results.OrganicResults); Console.WriteLine(results["local_results"]); -``` - -### Get HTML results -```csharp -string html = await client.HtmlAsync(new Dictionary -{ - ["engine"] = "google_light", - ["q"] = "coffee" -}); +// search results as a raw HTML string +string rawHtml = await client.HtmlAsync(parameters); ``` -### Pagination +→ [SerpApi documentation](https://serpapi.com/search-api). -```csharp -// Next page -using var page2 = await client.NextPageAsync(results); +### Documentations -// Iterate all pages as an async stream -await foreach (var page in client.SearchPagesAsync(parameters, maxPages: 5)) -{ - using (page) - { - Console.WriteLine($"Page has {page.OrganicResults?.GetArrayLength()} results"); - } -} -``` +- [Full documentation on SerpApi.com](https://serpapi.com) +- [Library GitHub page](https://github.com/serpapi/serpapi-dotnet) +- [Library NuGet page](https://www.nuget.org/packages/serpapi) +- [API health status](https://serpapi.com/status) + +## Advanced search API usage ### Search concurrently +A single `SerpApiClient` can run many searches at once — no thread pool or connection juggling required. + ```csharp var tasks = new[] { @@ -139,6 +147,78 @@ finally } ``` +### Pagination + +```csharp +// Next page +using var page2 = await client.NextPageAsync(results); + +// Iterate all pages as an async stream +await foreach (var page in client.SearchPagesAsync(parameters, maxPages: 5)) +{ + using (page) + { + Console.WriteLine($"Page has {page.OrganicResults?.GetArrayLength()} results"); + } +} +``` + +### Error handling + +```csharp +try +{ + using var results = await client.SearchAsync(parameters); +} +catch (SerpApiKeyException) { /* 401 — invalid API key */ } +catch (SerpApiHttpException ex) { /* 429, 500, etc — ex.StatusCode */ } +catch (SerpApiTimeoutException) { /* request timed out */ } +catch (SerpApiException ex) { /* catch-all */ } +``` + +### Dependency Injection + +```csharp +builder.Services.AddSerpApi(options => +{ + options.ApiKey = builder.Configuration["SerpApi:ApiKey"]!; + options.Timeout = TimeSpan.FromSeconds(30); +}); +``` + +Uses `IHttpClientFactory` for connection management. + +#### Resilience + +Install the `Microsoft.Extensions.Http.Resilience` package: + +```bash +dotnet add package Microsoft.Extensions.Http.Resilience +``` + +```csharp +builder.Services.AddSerpApi(options => +{ + options.ApiKey = builder.Configuration["SerpApi:ApiKey"]!; +}) +.AddStandardResilienceHandler(); +``` + +#### Corporate proxy + +```csharp +var handler = new HttpClientHandler +{ + Proxy = new WebProxy("http://proxy.corp.example:8080"), + UseProxy = true +}; +using var client = new SerpApiClient( + new HttpClient(handler), + new SerpApiClientOptions { ApiKey = "YOUR_API_KEY" }); +``` + +## APIs supported + ### Location API ```csharp @@ -149,12 +229,18 @@ foreach (var loc in locations.EnumerateArray()) } ``` +NOTE: api_key is not required for this endpoint. + ### Search Archive API -Retrieve a previous search (0 credits): +This API allows retrieving previous search results (free of charge). +First, run a search and save the search ID; then fetch it back from the archive. ```csharp -using var archived = await client.SearchArchiveAsync("previous_search_id"); +using var results = await client.SearchAsync(parameters); +string searchId = results.SearchId!; + +using var archived = await client.SearchArchiveAsync(searchId); ``` ### Account API @@ -164,9 +250,11 @@ using var account = await client.AccountAsync(); Console.WriteLine(account["plan_id"]); ``` -## Basic examples per search engine +It prints your account information: plan, searches left, usage this month, and more. + +## Basic example per search engine -### Search Google +### Search google ```csharp using var results = await client.SearchAsync(new Dictionary @@ -177,9 +265,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/search-api +see: [https://serpapi.com/search-api](https://serpapi.com/search-api) -### Search Google Light +### Search google light ```csharp using var results = await client.SearchAsync(new Dictionary @@ -189,9 +277,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/google-light-api +see: [https://serpapi.com/google-light-api](https://serpapi.com/google-light-api) -### Search Google Scholar +### Search google scholar ```csharp using var results = await client.SearchAsync(new Dictionary @@ -201,9 +289,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/google-scholar-api +see: [https://serpapi.com/google-scholar-api](https://serpapi.com/google-scholar-api) -### Search Google News +### Search google news ```csharp using var results = await client.SearchAsync(new Dictionary @@ -213,9 +301,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/google-news-api +see: [https://serpapi.com/google-news-api](https://serpapi.com/google-news-api) -### Search Google Maps +### Search google maps ```csharp using var results = await client.SearchAsync(new Dictionary @@ -226,9 +314,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/google-maps-api +see: [https://serpapi.com/google-maps-api](https://serpapi.com/google-maps-api) -### Search Google Shopping +### Search google shopping ```csharp using var results = await client.SearchAsync(new Dictionary @@ -238,9 +326,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/google-shopping-api +see: [https://serpapi.com/google-shopping-api](https://serpapi.com/google-shopping-api) -### Search Google Jobs +### Search google jobs ```csharp using var results = await client.SearchAsync(new Dictionary @@ -250,9 +338,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/google-jobs-api +see: [https://serpapi.com/google-jobs-api](https://serpapi.com/google-jobs-api) -### Search Google Images +### Search google images ```csharp using var results = await client.SearchAsync(new Dictionary @@ -262,9 +350,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/images-results +see: [https://serpapi.com/images-results](https://serpapi.com/images-results) -### Search Google Finance +### Search google finance ```csharp using var results = await client.SearchAsync(new Dictionary @@ -274,9 +362,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/google-finance-api +see: [https://serpapi.com/google-finance-api](https://serpapi.com/google-finance-api) -### Search Bing +### Search bing ```csharp using var results = await client.SearchAsync(new Dictionary @@ -286,9 +374,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/bing-search-api +see: [https://serpapi.com/bing-search-api](https://serpapi.com/bing-search-api) -### Search DuckDuckGo +### Search duckduckgo ```csharp using var results = await client.SearchAsync(new Dictionary @@ -298,9 +386,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/duckduckgo-search-api +see: [https://serpapi.com/duckduckgo-search-api](https://serpapi.com/duckduckgo-search-api) -### Search Baidu +### Search baidu ```csharp using var results = await client.SearchAsync(new Dictionary @@ -310,9 +398,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/baidu-search-api +see: [https://serpapi.com/baidu-search-api](https://serpapi.com/baidu-search-api) -### Search Yahoo +### Search yahoo ```csharp using var results = await client.SearchAsync(new Dictionary @@ -322,9 +410,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/yahoo-search-api +see: [https://serpapi.com/yahoo-search-api](https://serpapi.com/yahoo-search-api) -### Search YouTube +### Search youtube ```csharp using var results = await client.SearchAsync(new Dictionary @@ -334,9 +422,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/youtube-search-api +see: [https://serpapi.com/youtube-search-api](https://serpapi.com/youtube-search-api) -### Search Walmart +### Search walmart ```csharp using var results = await client.SearchAsync(new Dictionary @@ -346,9 +434,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/walmart-search-api +see: [https://serpapi.com/walmart-search-api](https://serpapi.com/walmart-search-api) -### Search eBay +### Search ebay ```csharp using var results = await client.SearchAsync(new Dictionary @@ -358,9 +446,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/ebay-search-api +see: [https://serpapi.com/ebay-search-api](https://serpapi.com/ebay-search-api) -### Search Amazon +### Search amazon ```csharp using var results = await client.SearchAsync(new Dictionary @@ -370,9 +458,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/amazon-search-api +see: [https://serpapi.com/amazon-search-api](https://serpapi.com/amazon-search-api) -### Search Naver +### Search naver ```csharp using var results = await client.SearchAsync(new Dictionary @@ -382,9 +470,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/naver-search-api +see: [https://serpapi.com/naver-search-api](https://serpapi.com/naver-search-api) -### Search Apple App Store +### Search apple app store ```csharp using var results = await client.SearchAsync(new Dictionary @@ -394,9 +482,9 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/apple-app-store +see: [https://serpapi.com/apple-app-store](https://serpapi.com/apple-app-store) -### Search Home Depot +### Search home depot ```csharp using var results = await client.SearchAsync(new Dictionary @@ -406,57 +494,7 @@ using var results = await client.SearchAsync(new Dictionary }); ``` -* see: https://serpapi.com/home-depot-search-api - -## Configuration - -```csharp -using var client = new SerpApiClient("YOUR_API_KEY", new SerpApiClientOptions -{ - Timeout = TimeSpan.FromSeconds(30) -}); -``` - -### Dependency Injection - -```csharp -builder.Services.AddSerpApi(options => -{ - options.ApiKey = builder.Configuration["SerpApi:ApiKey"]!; - options.Timeout = TimeSpan.FromSeconds(30); -}); -``` - -Uses `IHttpClientFactory` for connection management. - -#### Resilience - -Install the `Microsoft.Extensions.Http.Resilience` package: - -```bash -dotnet add package Microsoft.Extensions.Http.Resilience -``` - -```csharp -builder.Services.AddSerpApi(options => -{ - options.ApiKey = builder.Configuration["SerpApi:ApiKey"]!; -}) -.AddStandardResilienceHandler(); -``` - -#### Corporate proxy - -```csharp -var handler = new HttpClientHandler -{ - Proxy = new WebProxy("http://proxy.corp.example:8080"), - UseProxy = true -}; -using var client = new SerpApiClient( - new HttpClient(handler), - new SerpApiClientOptions { ApiKey = "YOUR_API_KEY" }); -``` +see: [https://serpapi.com/home-depot-search-api](https://serpapi.com/home-depot-search-api) ## Examples @@ -479,9 +517,72 @@ cd examples/LeadFinder dotnet run ``` +## Migration quick guide + +If you were already using the [google-search-results-dotnet](https://github.com/serpapi/google-search-results-dotnet) package, here are the changes. It's a new package ID — install `serpapi` instead; there's no automatic upgrade path. + +```csharp +// define a search +// old way: one class per engine (GoogleSearch, BingSearch, BaiduSearch, ...) +var ht = new Hashtable { { "q", "coffee" } }; +GoogleSearch search = new GoogleSearch(ht, apiKey); +// new way: one client for every engine, selected via the "engine" parameter +using var client = new SerpApiClient(apiKey); +var parameters = new Dictionary { ["engine"] = "google", ["q"] = "coffee" }; + +// search returns JSON +// old way (synchronous, Newtonsoft JObject) +JObject data = search.GetJson(); +// new way (async, System.Text.Json based SerpApiResponse) +using var results = await client.SearchAsync(parameters); + +// search returns raw HTML +// old way +string html = search.GetHtml(); +// new way +string html = await client.HtmlAsync(parameters); + +// other methods: the Get prefix is removed, Async suffix added +// old -> new way +// search.GetSearchArchiveJson(id) -> await client.SearchArchiveAsync(id) +// search.GetAccount() -> await client.AccountAsync() +// search.GetLocation(q, limit) -> await client.LocationAsync(q, limit) + +// timeout +// old way +search.setTimeoutSeconds(30); +// new way (at construction) +using var client = new SerpApiClient(apiKey, new SerpApiClientOptions { Timeout = TimeSpan.FromSeconds(30) }); + +// cleanup +// old way +search.Close(); +// new way: the client implements IDisposable +// (and each SerpApiResponse is IDisposable too) +``` + +Most notable improvements: +- Async-first API with `CancellationToken` support (sync wrappers still available). +- `System.Text.Json` instead of `Newtonsoft.Json` — zero external dependencies on modern .NET. +- Typed errors: `SerpApiKeyException`, `SerpApiHttpException`, `SerpApiTimeoutException`, `SerpApiException` (was a single `SerpApiSearchException`). +- Built-in pagination: `NextPageAsync(response)` and `SearchPagesAsync(parameters)` (`IAsyncEnumerable`). +- Dependency injection via `services.AddSerpApi(...)`. + +## Supported .NET versions + +| Target framework | Minimum consumer runtime | Notes | +|---|---|---| +| `netstandard2.0` | .NET Framework 4.6.1+, .NET Core 2.0+, Mono, Xamarin, UWP | Ships `Microsoft.Bcl.AsyncInterfaces` and `System.Text.Json` as polyfills | +| `net7.0` | .NET 7 | Out of support upstream, still built and tested | +| `net8.0` | .NET 8 (LTS) | | +| `net9.0` | .NET 9 (STS) | | +| `net10.0` | .NET 10 (LTS) | Used for `dotnet pack` and local dev builds | + +CI builds and tests every target framework above on both **Linux** and **Windows**. Building locally requires the [.NET 10 SDK](https://dotnet.microsoft.com/download) (a single SDK at or above the highest TFM can build all lower ones). + ## Contributing -Bug reports and pull requests are welcome on GitHub at https://github.com/serpapi/serpapi-dotnet. See [Compatibility](#compatibility) for SDK requirements. +Bug reports and pull requests are welcome on GitHub at https://github.com/serpapi/serpapi-dotnet. See [Supported .NET versions](#supported-net-versions) for SDK requirements. ```bash git clone https://github.com/serpapi/serpapi-dotnet.git diff --git a/serpapi/serpapi.csproj b/serpapi/serpapi.csproj index 71b50ba..b682209 100644 --- a/serpapi/serpapi.csproj +++ b/serpapi/serpapi.csproj @@ -3,7 +3,7 @@ netstandard2.0;net7.0;net8.0;net9.0;net10.0 SerpApi serpapi - 2.0.0 + 1.0.0 SerpApi SerpApi LLC MIT