From 84b36a4244437e71a69a876739f4908c71b2f592 Mon Sep 17 00:00:00 2001 From: Adarsh Divakaran Date: Sun, 21 Jun 2026 11:19:51 +0530 Subject: [PATCH 1/7] WIP: great-docs for documentation --- .github/workflows/docs.yml | 60 +++ .gitignore | 2 +- .readthedocs.yaml | 32 -- README.md | 12 +- assets/serpapi-icon.png | Bin 0 -> 10828 bytes assets/serpapi-logo-dark.svg | 15 + assets/serpapi-logo.svg | 15 + assets/serpapi-theme.css | 353 ++++++++++++++++++ docs/Makefile | 20 - docs/_static/serpapi-python.png | Bin 137402 -> 0 bytes docs/conf.py | 69 ---- docs/examples/00-google-across-countries.md | 71 ++++ docs/examples/01-bing-search.md | 39 ++ .../examples/02-google-maps-local-business.md | 37 ++ docs/examples/03-google-shopping-products.md | 38 ++ docs/examples/04-google-flights-travel.md | 43 +++ .../examples/05-google-finance-market-data.md | 39 ++ docs/examples/06-google-trends-demand.md | 40 ++ docs/examples/07-google-ai-overview.md | 44 +++ docs/examples/08-google-news-monitoring.md | 37 ++ docs/examples/09-google-jobs-listings.md | 38 ++ docs/examples/10-youtube-video-search.md | 34 ++ docs/index.md | 124 ++++++ docs/index.rst | 191 ---------- docs/requirements.txt | 53 --- docs/user_guide/00-getting-started.md | 111 ++++++ docs/user_guide/01-client-usage.md | 163 ++++++++ docs/user_guide/02-parameters-and-engines.md | 79 ++++ docs/user_guide/03-pagination.md | 86 +++++ docs/user_guide/04-errors-and-timeouts.md | 76 ++++ docs/user_guide/05-account-and-locations.md | 49 +++ ...06-migrating-from-google-search-results.md | 116 ++++++ docs/user_guide/20-async-search-archive.md | 57 +++ docs/user_guide/21-threading.md | 72 ++++ docs/user_guide/22-multiprocessing.md | 74 ++++ docs/user_guide/23-zero-trace.md | 49 +++ great-docs.yml | 115 ++++++ index.md | 1 + pyproject.toml | 7 +- serpapi/core.py | 45 +-- serpapi/models.py | 16 +- tests/test_docs_examples.py | 57 +++ 42 files changed, 2176 insertions(+), 403 deletions(-) create mode 100644 .github/workflows/docs.yml delete mode 100644 .readthedocs.yaml create mode 100644 assets/serpapi-icon.png create mode 100644 assets/serpapi-logo-dark.svg create mode 100644 assets/serpapi-logo.svg create mode 100644 assets/serpapi-theme.css delete mode 100644 docs/Makefile delete mode 100644 docs/_static/serpapi-python.png delete mode 100644 docs/conf.py create mode 100644 docs/examples/00-google-across-countries.md create mode 100644 docs/examples/01-bing-search.md create mode 100644 docs/examples/02-google-maps-local-business.md create mode 100644 docs/examples/03-google-shopping-products.md create mode 100644 docs/examples/04-google-flights-travel.md create mode 100644 docs/examples/05-google-finance-market-data.md create mode 100644 docs/examples/06-google-trends-demand.md create mode 100644 docs/examples/07-google-ai-overview.md create mode 100644 docs/examples/08-google-news-monitoring.md create mode 100644 docs/examples/09-google-jobs-listings.md create mode 100644 docs/examples/10-youtube-video-search.md create mode 100644 docs/index.md delete mode 100644 docs/index.rst delete mode 100644 docs/requirements.txt create mode 100644 docs/user_guide/00-getting-started.md create mode 100644 docs/user_guide/01-client-usage.md create mode 100644 docs/user_guide/02-parameters-and-engines.md create mode 100644 docs/user_guide/03-pagination.md create mode 100644 docs/user_guide/04-errors-and-timeouts.md create mode 100644 docs/user_guide/05-account-and-locations.md create mode 100644 docs/user_guide/06-migrating-from-google-search-results.md create mode 100644 docs/user_guide/20-async-search-archive.md create mode 100644 docs/user_guide/21-threading.md create mode 100644 docs/user_guide/22-multiprocessing.md create mode 100644 docs/user_guide/23-zero-trace.md create mode 100644 great-docs.yml create mode 120000 index.md create mode 100644 tests/test_docs_examples.py diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..1bbc240 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,60 @@ +name: Docs + +on: + push: + branches: [ great-docs ] + pull_request: + branches: [ great-docs ] + workflow_dispatch: + +concurrency: + group: pages-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-docs: + name: Build Great Docs + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - name: Install package and documentation dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[docs]" + + - name: Set up Quarto + uses: quarto-dev/quarto-actions/setup@v2 + + - name: Build documentation + run: great-docs build + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v5 + with: + path: great-docs/_site + include-hidden-files: true + + deploy-docs: + name: Deploy GitHub Pages + runs-on: ubuntu-latest + needs: build-docs + if: github.event_name == 'push' && github.ref == 'refs/heads/great-docs' + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 diff --git a/.gitignore b/.gitignore index 143977d..44d5b48 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ script/ .coverage -docs/build +great-docs/ dist/ build/ .pytest_cache/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml deleted file mode 100644 index 7168b22..0000000 --- a/.readthedocs.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# .readthedocs.yaml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Set the OS, Python version and other tools you might need -build: - os: ubuntu-22.04 - tools: - python: "3.11" - # You can also specify other tool versions: - # nodejs: "19" - # rust: "1.64" - # golang: "1.19" - -# Build documentation in the "docs/" directory with Sphinx -sphinx: - configuration: docs/conf.py - -# Optionally build your docs in additional formats such as PDF and ePub -formats: - - pdf - - epub - -# Optional but recommended, declare the Python requirements required -# to build your documentation -# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html -python: - install: - - requirements: docs/requirements.txt diff --git a/README.md b/README.md index 15eb407..0cfdc92 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ To install the `serpapi` package, simply run the following command: $ pip install serpapi ``` -Please note that this package is separate from the legacy `serpapi` module, which is available on PyPi as `google-search-results`. This package is maintained by SerpApi, and is the recommended way to access the SerpApi service from Python. +Please note that this package is separate from the deprecated `google-search-results` SDK. This package is maintained by SerpApi and is the recommended way to access the SerpApi service from Python. ## Simple Usage @@ -75,7 +75,7 @@ except serpapi.TimeoutError as e: ## Documentation -Documentation is [available on Read the Docs](https://serpapi-python.readthedocs.io/en/latest/). +Documentation is built with [Great Docs](https://posit-dev.github.io/great-docs/) and published at the repository's default GitHub Pages site: [serpapi.github.io/serpapi-python](https://serpapi.github.io/serpapi-python/). Change history is [available on GitHub](https://github.com/serpapi/serpapi-python/blob/master/HISTORY.md). @@ -368,7 +368,13 @@ MIT License. ## Contributing -Bug reports and pull requests are welcome on GitHub. Once dependencies are installed, you can run the tests with `pytest`. +Bug reports and pull requests are welcome on GitHub. Once dependencies are installed, you can run the tests with `pytest`. Pytest is configured to include doctests from the `serpapi` package. + +To run only doctests: + +```bash +pytest --doctest-modules serpapi +``` ## Publishing a new release diff --git a/assets/serpapi-icon.png b/assets/serpapi-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e3754a2ba395c3b6e868e7a29505d6a681d6421b GIT binary patch literal 10828 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 E0k%%bS^xk5 literal 0 HcmV?d00001 diff --git a/assets/serpapi-logo-dark.svg b/assets/serpapi-logo-dark.svg new file mode 100644 index 0000000..743381b --- /dev/null +++ b/assets/serpapi-logo-dark.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + SerpApi + diff --git a/assets/serpapi-logo.svg b/assets/serpapi-logo.svg new file mode 100644 index 0000000..c400e7b --- /dev/null +++ b/assets/serpapi-logo.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + SerpApi + diff --git a/assets/serpapi-theme.css b/assets/serpapi-theme.css new file mode 100644 index 0000000..d8f75db --- /dev/null +++ b/assets/serpapi-theme.css @@ -0,0 +1,353 @@ +:root { + --serpapi-bg-soft: #f3f3f4; + --serpapi-bg-soft-2: #ffffff; + --serpapi-border: #dddfea; + --serpapi-text: #2f3030; + --serpapi-muted: #5f6570; + --serpapi-footer: #2f3030; + --serpapi-purple: #6937EA; + --serpapi-blue: #377FEA; + --serpapi-gradient: linear-gradient(135deg, var(--serpapi-blue) 0%, var(--serpapi-purple) 100%); + --serpapi-code-bg: #f6f8fa; + --serpapi-code-text: #17212b; + --serpapi-code-keyword: #5932b8; + --serpapi-code-string: #075985; + --serpapi-code-number: #9a3412; + --serpapi-code-function: #0f5e9c; + --serpapi-code-variable: #7c2d83; + --serpapi-code-comment: #4b5563; + --serpapi-code-warning: #b42318; + --gd-accent: var(--serpapi-blue); +} + +body { + color: var(--serpapi-text); + background-color: #ffffff; + font-family: Lato, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +a { + color: var(--serpapi-purple); +} + +a:hover, +a:focus { + color: var(--serpapi-purple); + text-decoration-color: var(--serpapi-blue); +} + +.navbar, +.navbar.quarto-navbar { + background: #ffffff !important; + border-bottom: 1px solid var(--serpapi-border); + box-shadow: 0 1px 0 rgba(24, 33, 43, 0.04); +} + +.navbar a, +.navbar .navbar-brand, +.navbar .nav-link { + color: var(--serpapi-text) !important; +} + +.navbar .navbar-brand img, +.navbar-brand-logo img, +.navbar-logo { + height: 32px !important; + max-height: 32px; + min-width: 140px; + width: auto !important; +} + +.navbar .nav-link:hover, +.navbar .nav-link:focus { + color: var(--serpapi-purple) !important; +} + +.hero, +.gd-hero, +.quarto-title-banner { + background: linear-gradient(180deg, #ffffff 0%, var(--serpapi-bg-soft) 100%) !important; + border-bottom: 1px solid var(--serpapi-border); + box-shadow: inset 0 3px 0 var(--serpapi-blue); + color: var(--serpapi-text); +} + +.hero a, +.gd-hero a, +.quarto-title-banner a { + color: var(--serpapi-purple); +} + +.hero p, +.gd-hero p, +.quarto-title-banner p { + color: var(--serpapi-muted); +} + +.btn-primary { + background: var(--serpapi-gradient) !important; + border-color: var(--serpapi-blue) !important; + color: #ffffff !important; +} + +.btn-primary:hover, +.btn-primary:focus { + background: linear-gradient(135deg, var(--serpapi-purple) 0%, var(--serpapi-blue) 100%) !important; + border-color: var(--serpapi-purple) !important; +} + +.btn-outline-primary { + border-color: var(--serpapi-blue) !important; + color: var(--serpapi-blue) !important; +} + +.btn-outline-primary:hover, +.btn-outline-primary:focus { + background: var(--serpapi-gradient) !important; + border-color: var(--serpapi-purple) !important; + color: #ffffff !important; +} + +pre, +code.sourceCode, +.sourceCode, +pre.sourceCode, +div.sourceCode { + background-color: var(--serpapi-code-bg) !important; + color: var(--serpapi-code-text) !important; +} + +pre code, +pre.sourceCode code, +code.sourceCode, +div.sourceCode pre, +div.sourceCode code { + background-color: var(--serpapi-code-bg) !important; + color: var(--serpapi-code-text) !important; +} + +pre code span, +pre.sourceCode code span, +code.sourceCode span, +div.sourceCode span, +.sourceCode span { + color: var(--serpapi-code-text) !important; +} + +code.sourceCode span.kw, +code.sourceCode span.cf, +code.sourceCode span.im { + color: var(--serpapi-code-keyword) !important; + font-weight: 600; +} + +code.sourceCode span.st, +code.sourceCode span.ch, +code.sourceCode span.sc, +code.sourceCode span.ss, +code.sourceCode span.vs { + color: var(--serpapi-code-string) !important; +} + +code.sourceCode span.dv, +code.sourceCode span.bn, +code.sourceCode span.fl, +code.sourceCode span.cn { + color: var(--serpapi-code-number) !important; +} + +code.sourceCode span.bu, +code.sourceCode span.fu, +code.sourceCode span.ex { + color: var(--serpapi-code-function) !important; +} + +code.sourceCode span.va, +code.sourceCode span.at, +code.sourceCode span.dt, +code.sourceCode span.ot { + color: var(--serpapi-code-variable) !important; +} + +code.sourceCode span.co, +code.sourceCode span.do, +code.sourceCode span.an, +code.sourceCode span.cv, +code.sourceCode span.in { + color: var(--serpapi-code-comment) !important; + font-style: italic; +} + +code.sourceCode span.al, +code.sourceCode span.er, +code.sourceCode span.wa { + color: var(--serpapi-code-warning) !important; +} + +pre a, +pre.sourceCode a, +div.sourceCode a { + color: inherit !important; +} + +.card, +.quarto-grid-item, +.listing-card { + border-radius: 8px; + border-color: var(--serpapi-border); + box-shadow: 0 10px 32px rgba(55, 127, 234, 0.06), 0 2px 9px rgba(105, 55, 234, 0.04); +} + +.docs-home-nav { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(280px, 360px); + gap: 1.5rem; + align-items: start; + margin: 2rem 0; +} + +.docs-home-nav__intro, +.docs-home-nav__sidebar { + background: #ffffff; + border: 1px solid var(--serpapi-border); + border-radius: 8px; + box-shadow: 0 8px 28px rgba(24, 33, 43, 0.05); + padding: 1.25rem; +} + +.docs-home-nav__sidebar { + position: sticky; + top: 5.25rem; +} + +.docs-home-nav__sidebar h3:first-child { + margin-top: 0; +} + +.docs-home-nav__sidebar h3 { + color: var(--serpapi-text); + font-size: 1rem; + margin: 1.1rem 0 0.35rem; +} + +.docs-home-nav__sidebar ul { + margin-bottom: 0.75rem; + padding-left: 1.1rem; +} + +.docs-home-nav__sidebar li { + margin: 0.32rem 0; +} + +.docs-home-nav__intro p, +.docs-home-nav__sidebar p { + color: var(--serpapi-muted); +} + +.docs-home-actions { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-top: 1rem; +} + +.docs-home-actions a { + border: 1px solid var(--serpapi-border); + border-radius: 6px; + color: var(--serpapi-purple); + display: inline-flex; + font-weight: 600; + line-height: 1.2; + padding: 0.55rem 0.8rem; + text-decoration: none; +} + +.docs-home-actions a:hover, +.docs-home-actions a:focus { + border-color: var(--serpapi-purple); + color: var(--serpapi-purple); +} + +@media (max-width: 991px) { + .docs-home-nav { + grid-template-columns: 1fr; + } + + .docs-home-nav__sidebar { + position: static; + } +} + +body.quarto-dark, +[data-bs-theme="dark"] body { + --serpapi-text: #ecf0f1; + --serpapi-muted: #c6c8d1; + --serpapi-bg-soft: #2f3030; + --serpapi-bg-soft-2: #262727; + --serpapi-border: #44464b; + --serpapi-code-bg: #111827; + --serpapi-code-text: #f8fafc; + --serpapi-code-keyword: #c9b7ff; + --serpapi-code-string: #91d9ff; + --serpapi-code-number: #ffd27a; + --serpapi-code-function: #8cc8ff; + --serpapi-code-variable: #f3b5ff; + --serpapi-code-comment: #c6ced9; + --serpapi-code-warning: #ffb4b4; + background-color: #202122; +} + +body.quarto-dark .navbar, +[data-bs-theme="dark"] .navbar, +body.quarto-dark .navbar.quarto-navbar, +[data-bs-theme="dark"] .navbar.quarto-navbar { + background: #2f3030 !important; + border-bottom-color: var(--serpapi-border); +} + +body.quarto-dark .navbar a, +body.quarto-dark .navbar .navbar-brand, +body.quarto-dark .navbar .nav-link, +[data-bs-theme="dark"] .navbar a, +[data-bs-theme="dark"] .navbar .navbar-brand, +[data-bs-theme="dark"] .navbar .nav-link { + color: #ffffff !important; +} + +body.quarto-dark .hero, +body.quarto-dark .gd-hero, +body.quarto-dark .quarto-title-banner, +[data-bs-theme="dark"] .hero, +[data-bs-theme="dark"] .gd-hero, +[data-bs-theme="dark"] .quarto-title-banner { + background: linear-gradient(180deg, #2f3030 0%, #262727 100%) !important; + border-bottom-color: var(--serpapi-border); + box-shadow: inset 0 3px 0 var(--serpapi-purple); + color: var(--serpapi-text); +} + +body.quarto-dark a, +[data-bs-theme="dark"] a { + color: #ffffff; +} + +body.quarto-dark a:hover, +body.quarto-dark a:focus, +[data-bs-theme="dark"] a:hover, +[data-bs-theme="dark"] a:focus { + color: var(--serpapi-purple); +} + +body.quarto-dark .docs-home-nav__intro, +body.quarto-dark .docs-home-nav__sidebar, +[data-bs-theme="dark"] .docs-home-nav__intro, +[data-bs-theme="dark"] .docs-home-nav__sidebar { + background: #2f3030; + border-color: var(--serpapi-border); +} + +body.quarto-dark .docs-home-actions a, +[data-bs-theme="dark"] .docs-home-actions a { + border-color: var(--serpapi-border); + color: #ffffff; +} diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index ed88099..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/serpapi-python.png b/docs/_static/serpapi-python.png deleted file mode 100644 index 4245bb6feafcf55174c2b9e7443f02237991d807..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 137402 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

mNNmd%7v%F*A;ufcpz^aJg(UqEHtW3Ttl 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∓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 diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 517e0c2..0000000 --- a/docs/conf.py +++ /dev/null @@ -1,69 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys - -sys.path.insert(0, os.path.abspath("../..")) - -import serpapi - -# -- Project information ----------------------------------------------------- - -project = "serpapi-python" -copyright = "2023 SerpApi, LLC" -author = "SerpApi, LLC" - -# The full version, including alpha/beta/rc tags -release = serpapi.__version__ - - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = ["sphinx.ext.githubpages", "sphinx.ext.autodoc"] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "alabaster" - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] - - -# -- Extension configuration ------------------------------------------------- -html_theme_options = { - "logo": "serpapi-python.png", - "logo_name": "serapi-python", -} - -html_sidebars = { - "**": [ - "about.html", - ] -} diff --git a/docs/examples/00-google-across-countries.md b/docs/examples/00-google-across-countries.md new file mode 100644 index 0000000..795bba4 --- /dev/null +++ b/docs/examples/00-google-across-countries.md @@ -0,0 +1,71 @@ +--- +title: "Google Across Countries" +description: "Run the same Google Search across multiple countries and locations." +--- + +# Google Across Countries + +Use `gl`, `hl`, `location`, and sometimes `google_domain` to compare localized Google results. + +## Compare Several Countries + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +markets = [ + {"name": "United States", "gl": "us", "hl": "en", "location": "Austin, Texas"}, + {"name": "United Kingdom", "gl": "gb", "hl": "en", "location": "London, England"}, + {"name": "France", "gl": "fr", "hl": "fr", "location": "Paris, France"}, + {"name": "Germany", "gl": "de", "hl": "de", "location": "Berlin, Germany"}, + {"name": "India", "gl": "in", "hl": "en", "location": "Mumbai, Maharashtra"}, +] + +for market in markets: + results = client.search({ + "engine": "google", + "q": "best coffee beans", + "location": market["location"], + "gl": market["gl"], + "hl": market["hl"], + }) + + organic = results.get("organic_results", []) + first = organic[0] if organic else {} + + print(market["name"]) + print(first.get("title")) + print(first.get("link")) + print() +``` + +## Use a Google Domain + +Some workflows also need a specific Google domain: + +```python +results = client.search({ + "engine": "google", + "q": "best coffee beans", + "google_domain": "google.co.in", + "gl": "in", + "hl": "en", + "location": "Mumbai, Maharashtra", +}) +``` + +## Finding Valid Locations + +Use the client helper: + +```python +locations = client.locations(q="Mumbai", limit=5) + +for location in locations: + print(location.get("canonical_name")) +``` + +See [Account and Locations](../../user-guide/account-and-locations.md) for the local guide to the locations helper. Use the [SerpApi Playground](https://serpapi.com/playground) to test location values with the rest of your search parameters. diff --git a/docs/examples/01-bing-search.md b/docs/examples/01-bing-search.md new file mode 100644 index 0000000..a2e71ab --- /dev/null +++ b/docs/examples/01-bing-search.md @@ -0,0 +1,39 @@ +--- +title: "Bing Search" +description: "Run a Bing web search and read organic result fields." +--- + +# Bing Search + +Use the Bing Search API when you want a second web index alongside Google, or when your product specifically needs Bing result ordering. The Python client sends Bing parameters directly to SerpApi, so you can start with a small query and add localization or pagination as your use case grows. + +## Search Bing + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search({ + "engine": "bing", + "q": "coffee", + "location": "Austin, Texas", +}) + +for result in results.get("organic_results", [])[:5]: + print(result.get("position"), result.get("title")) + print(result.get("link")) +``` + +## What to Read + +Most applications start with `organic_results`, then store the `title`, `link`, `snippet`, and `position` fields. If your query returns richer SERP blocks, inspect the top-level keys with: + +```python +print(results.keys()) +``` + +For every supported Bing parameter, use the [Bing Search API documentation](https://serpapi.com/bing-search-api). To tune a query interactively, use the [SerpApi Playground](https://serpapi.com/playground). + diff --git a/docs/examples/02-google-maps-local-business.md b/docs/examples/02-google-maps-local-business.md new file mode 100644 index 0000000..fb98d84 --- /dev/null +++ b/docs/examples/02-google-maps-local-business.md @@ -0,0 +1,37 @@ +--- +title: "Google Maps Local Businesses" +description: "Find local businesses with Google Maps results." +--- + +# Google Maps Local Businesses + +Use the Google Maps API for local discovery workflows: store locators, lead generation, local SEO checks, or building a shortlist of nearby places. The most important inputs are the business query and the map coordinate/zoom string in `ll`. + +## Search for Local Businesses + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search({ + "engine": "google_maps", + "q": "coffee shops", + "ll": "@30.2672,-97.7431,14z", + "type": "search", +}) + +for place in results.get("local_results", [])[:5]: + print(place.get("title")) + print(place.get("rating"), place.get("reviews")) + print(place.get("address")) +``` + +## What to Read + +Start with `local_results`. Common fields include `title`, `rating`, `reviews`, `address`, `phone`, `website`, and `gps_coordinates`. Some responses also include `place_results` when the query resolves to a specific place. + +For the complete parameter list, use the [Google Maps API documentation](https://serpapi.com/google-maps-api). Use the [SerpApi Playground](https://serpapi.com/playground) to find a working `ll` value for your target market. + diff --git a/docs/examples/03-google-shopping-products.md b/docs/examples/03-google-shopping-products.md new file mode 100644 index 0000000..97ff760 --- /dev/null +++ b/docs/examples/03-google-shopping-products.md @@ -0,0 +1,38 @@ +--- +title: "Google Shopping Products" +description: "Search Google Shopping and extract product cards." +--- + +# Google Shopping Products + +Use the Google Shopping API when you need product result cards, prices, merchants, and shopping links. It is a good fit for product discovery, price monitoring, catalog enrichment, and market research. + +## Search Products + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search({ + "engine": "google_shopping", + "q": "espresso machine", + "location": "Austin, Texas", + "gl": "us", + "hl": "en", +}) + +for product in results.get("shopping_results", [])[:5]: + print(product.get("title")) + print(product.get("price"), product.get("source")) + print(product.get("link")) +``` + +## What to Read + +Most shopping workflows use `shopping_results`. Save `title`, `price`, `source`, `rating`, `reviews`, `thumbnail`, and `link` if present. For filtering, sorting, and engine-specific options, check the live docs before hard-coding parameters. + +See the [Google Shopping API documentation](https://serpapi.com/google-shopping-api) and experiment in the [SerpApi Playground](https://serpapi.com/playground). + diff --git a/docs/examples/04-google-flights-travel.md b/docs/examples/04-google-flights-travel.md new file mode 100644 index 0000000..504a70f --- /dev/null +++ b/docs/examples/04-google-flights-travel.md @@ -0,0 +1,43 @@ +--- +title: "Google Flights Travel" +description: "Search round-trip flight options with Google Flights." +--- + +# Google Flights Travel + +Use the Google Flights API for travel search, flight price research, or building itinerary comparison tools. Flight searches are date-sensitive, so use future dates and choose airport codes that match the route you want. + +## Search Round-Trip Flights + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=30) + +results = client.search({ + "engine": "google_flights", + "departure_id": "AUS", + "arrival_id": "LAX", + "outbound_date": "2026-09-15", + "return_date": "2026-09-22", + "currency": "USD", + "hl": "en", + "gl": "us", +}) + +flights = results.get("best_flights") or results.get("other_flights", []) + +for itinerary in flights[:3]: + print("Total price:", itinerary.get("price")) + for flight in itinerary.get("flights", []): + print(flight.get("departure_airport", {}).get("id"), "->", flight.get("arrival_airport", {}).get("id")) +``` + +## What to Read + +Start with `best_flights`, then fall back to `other_flights` when needed. Useful fields include `price`, `total_duration`, `carbon_emissions`, and the nested `flights` list for airline, airport, and timing data. + +For trip type, cabin, date, currency, and airport parameters, see the [Google Flights API documentation](https://serpapi.com/google-flights-api). The [SerpApi Playground](https://serpapi.com/playground) is useful for testing routes before adding them to code. + diff --git a/docs/examples/05-google-finance-market-data.md b/docs/examples/05-google-finance-market-data.md new file mode 100644 index 0000000..f22b727 --- /dev/null +++ b/docs/examples/05-google-finance-market-data.md @@ -0,0 +1,39 @@ +--- +title: "Google Finance Market Data" +description: "Fetch quote, graph, and market context from Google Finance." +--- + +# Google Finance Market Data + +Use the Google Finance API for stock, index, mutual fund, currency, or futures lookups. It is useful for dashboards, watchlists, and enriching business workflows with current market context. + +## Fetch a Quote + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search({ + "engine": "google_finance", + "q": "GOOGL:NASDAQ", + "window": "1M", + "hl": "en", +}) + +summary = results.get("summary", {}) +knowledge = results.get("knowledge_graph", {}) + +print(summary.get("title") or knowledge.get("title")) +print(summary.get("price") or knowledge.get("price")) +print("Graph points:", len(results.get("graph", []))) +``` + +## What to Read + +Use `summary` and `knowledge_graph` for quote-level details, `graph` for time-series points, and `news_results` for finance-related headlines when present. Use `window` to change the graph range. + +See the [Google Finance API documentation](https://serpapi.com/google-finance-api) for supported `q` formats and time windows. You can test symbols in the [SerpApi Playground](https://serpapi.com/playground). + diff --git a/docs/examples/06-google-trends-demand.md b/docs/examples/06-google-trends-demand.md new file mode 100644 index 0000000..e38d680 --- /dev/null +++ b/docs/examples/06-google-trends-demand.md @@ -0,0 +1,40 @@ +--- +title: "Google Trends Demand" +description: "Measure search interest over time with Google Trends." +--- + +# Google Trends Demand + +Use the Google Trends API to compare demand, seasonality, and regional interest. This is useful for content planning, product research, launch timing, and market comparisons. + +## Interest Over Time + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search({ + "engine": "google_trends", + "q": "electric bikes", + "date": "today 12-m", + "geo": "US", + "data_type": "TIMESERIES", + "tz": "420", +}) + +timeline = results.get("interest_over_time", {}).get("timeline_data", []) + +for point in timeline[-5:]: + value = point.get("values", [{}])[0].get("extracted_value") + print(point.get("date"), value) +``` + +## What to Read + +For time-series charts, read `interest_over_time.timeline_data`. For regional demand, switch `data_type` to `GEO_MAP_0`. For related terms, use `RELATED_QUERIES` or `RELATED_TOPICS`. + +See the [Google Trends API documentation](https://serpapi.com/google-trends-api) for `data_type`, `geo`, `date`, and `tz` options. The [SerpApi Playground](https://serpapi.com/playground) helps confirm that a trend query returns the chart you expect. + diff --git a/docs/examples/07-google-ai-overview.md b/docs/examples/07-google-ai-overview.md new file mode 100644 index 0000000..0b80887 --- /dev/null +++ b/docs/examples/07-google-ai-overview.md @@ -0,0 +1,44 @@ +--- +title: "Google AI Overview" +description: "Read AI Overview data from Google Search results or fetch it with a page token." +--- + +# Google AI Overview + +Google AI Overview data can appear directly inside Google Search results. Sometimes Google requires an extra request using `ai_overview.page_token`; in that case, use the Google AI Overview API immediately because the token expires quickly. + +## Search and Fetch AI Overview + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +search = client.search({ + "engine": "google", + "q": "how do noise cancelling headphones work", + "location": "Austin, Texas", + "gl": "us", + "hl": "en", +}) + +ai_overview = search.get("ai_overview", {}) + +if ai_overview.get("page_token"): + ai_overview = client.search({ + "engine": "google_ai_overview", + "page_token": ai_overview["page_token"], + }) + +for block in ai_overview.get("text_blocks", [])[:3]: + print(block.get("snippet") or block.get("text")) +``` + +## What to Read + +Use `text_blocks` for the generated answer and `references` for cited sources when present. If the first Google Search response already includes the AI Overview content, you can read it directly without the second request. + +See the [Google Search API AI Overview docs](https://serpapi.com/search-api#api-examples-results-for-ai-overview) and the [Google AI Overview API documentation](https://serpapi.com/google-ai-overview-api). Use the [SerpApi Playground](https://serpapi.com/playground) to find queries that currently return AI Overview data. + diff --git a/docs/examples/08-google-news-monitoring.md b/docs/examples/08-google-news-monitoring.md new file mode 100644 index 0000000..fbd4f94 --- /dev/null +++ b/docs/examples/08-google-news-monitoring.md @@ -0,0 +1,37 @@ +--- +title: "Google News Monitoring" +description: "Collect recent headlines for a topic with Google News." +--- + +# Google News Monitoring + +Use the Google News API for media monitoring, topic tracking, and alerting workflows. It returns news-focused result cards instead of general web results. + +## Search News + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search({ + "engine": "google_news", + "q": "artificial intelligence", + "gl": "us", + "hl": "en", +}) + +for item in results.get("news_results", [])[:5]: + print(item.get("title")) + print(item.get("source", {}).get("name") or item.get("source")) + print(item.get("link")) +``` + +## What to Read + +Start with `news_results`. Save `title`, `link`, `source`, `date`, `snippet`, and `thumbnail` when present. News result shapes can vary by query, so inspect a sample response before deciding your storage schema. + +See the [Google News API documentation](https://serpapi.com/google-news-api) and tune your topic in the [SerpApi Playground](https://serpapi.com/playground). + diff --git a/docs/examples/09-google-jobs-listings.md b/docs/examples/09-google-jobs-listings.md new file mode 100644 index 0000000..dbdc3a7 --- /dev/null +++ b/docs/examples/09-google-jobs-listings.md @@ -0,0 +1,38 @@ +--- +title: "Google Jobs Listings" +description: "Search job listings by role and location." +--- + +# Google Jobs Listings + +Use the Google Jobs API to collect job cards for a role, market, or hiring workflow. It is useful for market maps, compensation research, and recruiting dashboards. + +## Search Jobs + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search({ + "engine": "google_jobs", + "q": "software engineer", + "location": "Austin, Texas", + "hl": "en", + "gl": "us", +}) + +for job in results.get("jobs_results", [])[:5]: + print(job.get("title")) + print(job.get("company_name"), "-", job.get("location")) + print(job.get("via")) +``` + +## What to Read + +Use `jobs_results` for the listing cards. Typical fields include `title`, `company_name`, `location`, `via`, `description`, `detected_extensions`, and `related_links`. For detail pages, check whether the response includes identifiers or links that fit your workflow. + +See the [Google Jobs API documentation](https://serpapi.com/google-jobs-api) for localization and pagination parameters. Use the [SerpApi Playground](https://serpapi.com/playground) to test role/location combinations. + diff --git a/docs/examples/10-youtube-video-search.md b/docs/examples/10-youtube-video-search.md new file mode 100644 index 0000000..e03a3de --- /dev/null +++ b/docs/examples/10-youtube-video-search.md @@ -0,0 +1,34 @@ +--- +title: "YouTube Video Search" +description: "Search YouTube videos and read video result metadata." +--- + +# YouTube Video Search + +Use the YouTube Search API when you need video discovery results, channel references, or topic-level video monitoring. YouTube uses `search_query` instead of `q`. + +## Search Videos + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search({ + "engine": "youtube", + "search_query": "coffee brewing guide", +}) + +for video in results.get("video_results", [])[:5]: + print(video.get("title")) + print(video.get("channel", {}).get("name")) + print(video.get("link")) +``` + +## What to Read + +Start with `video_results`. Useful fields include `title`, `link`, `channel`, `views`, `published_date`, `length`, and `thumbnail`. Some queries return additional sections such as channels, playlists, shorts, or related searches. + +See the [YouTube Search API documentation](https://serpapi.com/youtube-search-api) for YouTube-specific parameters. The [SerpApi Playground](https://serpapi.com/playground) is the quickest way to inspect the exact response shape for your query. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..082b43f --- /dev/null +++ b/docs/index.md @@ -0,0 +1,124 @@ +--- +title: "SerpApi Python Library & Package" +description: "Official Python client for SerpApi search data in applications, AI workflows, RAG, and data pipelines." +--- + +Integrate search data into your AI workflow, RAG or fine-tuning pipeline, Python application, or data product using the official wrapper for [SerpApi](https://serpapi.com). + +SerpApi supports Google, Google Maps, Google Shopping, Bing, DuckDuckGo, Baidu, Yandex, Yahoo, eBay, YouTube, App Stores, Walmart, Home Depot, Naver, and many more engines. + +Query a wide range of data at scale, including web search results, local business listings, shopping results, flight schedules, stock market data, job listings, trends, news headlines, AI Overview results, and video search results. + +This package is separate from the deprecated `google-search-results` SDK. This package is maintained by SerpApi and is the recommended Python package for new integrations. If your project still depends on `google-search-results`, see [Migrating from google-search-results](user-guide/migrating-from-google-search-results.md). + +## Install + +With `pip`: + +```bash +pip install serpapi +``` + +With `uv` in a project: + +```bash +uv add serpapi +``` + +With `uv` in an existing environment: + +```bash +uv pip install serpapi +``` + +Python 3.6 or newer is required by the package. Building this documentation site uses Great Docs and Quarto on Python 3.11 or newer. + +## First Request + +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"]) +results = client.search({ + "engine": "google", + "q": "coffee", + "location": "Austin, Texas", + "hl": "en", + "gl": "us", +}) + +print(results["organic_results"][0]["link"]) +``` + +The `results` variable contains a `SerpResults` object. It behaves like a standard dictionary and adds convenience helpers for response conversion, pagination, and fetching search archives. + +Request parameters map directly to the SerpApi HTTP API. The actual full supported engine list and engine-specific parameters are maintained in the [SerpApi API documentation](https://serpapi.com/search-api). Use the [SerpApi Playground](https://serpapi.com/playground) to build a request visually, test it, and copy the final parameters into Python. + +:::{.docs-home-nav} + +:::{.docs-home-nav__intro} + +## Documentation Map + +The full guide and examples are listed here on the homepage so you can jump directly into the workflow you need. Guide pages also include the standard collapsible docs sidebar when you open them. + +

+ +::: + +:::{.docs-home-nav__sidebar} + +### User Guide + +- [Getting Started](user-guide/getting-started.md) +- [Client Usage](user-guide/client-usage.md) +- [Parameters and Engines](user-guide/parameters-and-engines.md) +- [Pagination](user-guide/pagination.md) +- [Errors and Timeouts](user-guide/errors-and-timeouts.md) +- [Account and Locations](user-guide/account-and-locations.md) + +### Migration + +- [Migrating from google-search-results](user-guide/migrating-from-google-search-results.md) + +### Advanced Usage + +- [Async Search Archive](user-guide/async-search-archive.md) +- [Parallel Requests with Threads](user-guide/threading.md) +- [Parallel Requests with Multiprocessing](user-guide/multiprocessing.md) +- [Zero Trace and Cache Controls](user-guide/zero-trace.md) + +### Examples + +- [Google Across Countries](docs/examples/google-across-countries.md) +- [Bing Search](docs/examples/bing-search.md) +- [Google Maps Local Business](docs/examples/google-maps-local-business.md) +- [Google Shopping Products](docs/examples/google-shopping-products.md) +- [Google Flights Travel](docs/examples/google-flights-travel.md) +- [Google Finance Market Data](docs/examples/google-finance-market-data.md) +- [Google Trends Demand](docs/examples/google-trends-demand.md) +- [Google AI Overview](docs/examples/google-ai-overview.md) +- [Google News Monitoring](docs/examples/google-news-monitoring.md) +- [Google Jobs Listings](docs/examples/google-jobs-listings.md) +- [YouTube Video Search](docs/examples/youtube-video-search.md) + +::: + +::: + +## Where to Go Next + +- Start with [Getting Started](user-guide/getting-started.md) for installation and a first request. +- Read [Client Usage](user-guide/client-usage.md) to learn the Python API, response helpers, request options, and archive helpers. +- Use [Migrating from google-search-results](user-guide/migrating-from-google-search-results.md) if your project still depends on the deprecated SDK. +- Use [Parameters and Engines](user-guide/parameters-and-engines.md) for links to the full supported engine and parameter docs. +- Browse [Examples](docs/examples/google-flights-travel.md) for focused recipes covering travel, finance, trends, AI Overview, local business, shopping, Bing, jobs, news, and video search. +- Review the generated API reference for `Client`, `SerpResults`, and exception classes. +- Use the [SerpApi Playground](https://serpapi.com/playground) to build and test request parameters before putting them in code. diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 717c69a..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,191 +0,0 @@ -.. serpapi-python documentation master file, created by - sphinx-quickstart on Sun Apr 3 21:09:40 2022. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -**serpapi-python** -================== - -an official Python client library for `SerpApi `_. - --------------- - -Installation ------------- - -To install ``serpapi-python``, simply use `pip`:: - - $ pip install serpapi - - -Please note that Python 3.6+ is required. - - -Usage ------ - -Usage of this module is fairly straight-forward. In general, this module attempts to be as close to the actual API as possible, while still being Pythonic. - -For example, the API endpoint ``https://serpapi.com/search.json`` is represented by the method ``serpapi.search()``. - -.. code-block:: python - - >>> import serpapi - >>> s = serpapi.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us") - >>> s["organic_results"][0]["link"] - 'https://en.wikipedia.org/wiki/Coffee' - -Any parameters that you pass to ``search()`` will be passed to the API. This includes the ``api_key`` parameter, which is required for all requests. - -.. _using-api-client-directly: - -Using the API Client directly -^^^^^^^^^ - -To make this less repetitive, and gain the benefit of connection pooling, let's start using the API Client directly:: - - >>> client = serpapi.Client(api_key="secret_api_key") - >>> s = client.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us") - -The ``api_key`` parameter is now automatically passed to all requests made by the client. - - -Concise Tutorial ----------------- - -Let's start by searching for ``Coffee`` on Google:: - - >>> import serpapi - >>> s = serpapi.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us") - -The ``s`` variable now contains a :class:`SerpResults ` object, which acts just like a standard dictionary, with some convenient functions added on top. - -Let's print the first result:: - - >>> print(s["organic_results"][0]["link"]) - https://en.wikipedia.org/wiki/Coffee - -Let's print the title of the first result, but in a more Pythonic way:: - - >>> print(s["organic_results"][0].get("title")) - Coffee - Wikipedia - -The `SerpApi.com API Documentation `_ contains a list of all the possible parameters that can be passed to the API. - - -API Reference -------------- - -.. _api-reference: - -This part of the documentation covers all the interfaces of :class:`serpapi` Python module. - -.. module:: serpapi - :platform: Unix, Windows - :synopsis: SerpApi Python Library - -.. autofunction:: serpapi.search -.. autofunction:: serpapi.search_archive -.. autofunction:: serpapi.locations -.. autofunction:: serpapi.account - - - -Results from SerpApi.com ------------------------- - -When a successful search has been executed, the method returns -a :class:`SerpResults ` object, which acts just like a standard dictionary, -with some convenient functions added on top. - - -.. code-block:: python - - >>> s = serpapi.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us") - >>> type(s) - - - >>> s["organic_results"][0]["link"] - 'https://en.wikipedia.org/wiki/Coffee' - - >>> s["search_metadata"] - {'id': '64c148d35119a60ab1e00cc9', 'status': 'Success', 'json_endpoint': 'https://serpapi.com/searches/a15e1b92727f292c/64c148d35119a60ab1e00cc9.json', 'created_at': '2023-07-26 16:24:51 UTC', 'processed_at': '2023-07-26 16:24:51 UTC', 'google_url': 'https://www.google.com/search?q=Coffee&oq=Coffee&uule=w+CAIQICIdQXVzdGluLFRYLFRleGFzLFVuaXRlZCBTdGF0ZXM&hl=en&gl=us&sourceid=chrome&ie=UTF-8', 'raw_html_file': 'https://serpapi.com/searches/a15e1b92727f292c/64c148d35119a60ab1e00cc9.html', 'total_time_taken': 1.55} - -Optionally, if you want exactly a dictionary of the entire response, you can use the ``as_dict()`` method:: - - >>> type(s.as_dict()) - - -You can get the next page of results:: - - >>> type(s.next_page()) - - -To iterate over all pages of results, it's recommended to :ref:`use the API Client directly `:: - - >>> client = serpapi.Client(api_key="secret_api_key") - >>> search = client.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us") - >>> for page in search.yield_pages(): - ... print(page["search_metadata"]["page_number"]) - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - - -Here's documentation of the class itself and its methods: - -.. autoclass:: serpapi.SerpResults - - .. automethod:: SerpResults.next_page - .. automethod:: SerpResults.yield_pages - .. autoproperty:: SerpResults.next_page_url - - -API Client ----------- - -The primary interface to `serpapi-python` is through the :class:`serpapi.Client` class. -The primary benefit of using this class is to benefit from Requests' HTTP Connection Pooling. -This class also alleviates the need to pass an ``api_key``` along with every search made to the platform. - -.. autoclass:: serpapi.Client - - .. automethod:: Client.search - .. automethod:: Client.search_archive - .. automethod:: Client.account - .. automethod:: Client.locations - - - -Exceptions ----------- - -.. autoexception:: serpapi.SerpApiError - :members: - -.. autoexception:: serpapi.SearchIDNotProvided - :members: - -.. autoexception:: serpapi.HTTPError - :members: - -.. autoexception:: serpapi.HTTPConnectionError - :members: - - - - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 987dd00..0000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,53 +0,0 @@ -alabaster==0.7.13 -astroid==2.15.5 -Babel==2.12.1 -bleach==6.0.0 -certifi==2023.5.7 -charset-normalizer==3.2.0 -coverage==7.2.7 -dill==0.3.6 -docutils==0.20.1 -idna==3.4 -imagesize==1.4.1 -importlib-metadata==6.7.0 -iniconfig==2.0.0 -isort==5.12.0 -jaraco.classes==3.2.3 -Jinja2==3.1.2 -keyring==24.2.0 -lazy-object-proxy==1.9.0 -markdown-it-py==3.0.0 -MarkupSafe==2.1.3 -mccabe==0.7.0 -mdurl==0.1.2 -more-itertools==9.1.0 -packaging==23.1 -pkginfo==1.9.6 -platformdirs==3.8.0 -pluggy==1.2.0 -pycodestyle==2.10.0 -Pygments==2.15.1 -pylint==2.17.4 -pytest==7.4.0 -pytest-cov==4.1.0 -readme-renderer==40.0 -requests==2.31.0 -requests-toolbelt==1.0.0 -rfc3986==2.0.0 -rich==13.4.2 -six==1.16.0 -snowballstemmer==2.2.0 -Sphinx==7.0.1 -sphinxcontrib-applehelp==1.0.4 -sphinxcontrib-devhelp==1.0.2 -sphinxcontrib-htmlhelp==2.0.1 -sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.3 -sphinxcontrib-serializinghtml==1.1.5 -tomlkit==0.11.8 -twine==4.0.2 -urllib3==2.0.3 -webencodings==0.5.1 -wrapt==1.15.0 -zipp==3.15.0 --e . diff --git a/docs/user_guide/00-getting-started.md b/docs/user_guide/00-getting-started.md new file mode 100644 index 0000000..f2d8b37 --- /dev/null +++ b/docs/user_guide/00-getting-started.md @@ -0,0 +1,111 @@ +--- +title: "Getting Started" +description: "Install serpapi, create a client, and run your first search." +guide-section: "Basics" +--- + +# Getting Started + +SerpApi Python is the official Python client for [SerpApi](https://serpapi.com). It wraps SerpApi HTTP endpoints with a small Python API while keeping request parameters close to the underlying API. + +Use this page to install the package, configure your API key, and make a first Google Search request. + +## Installation + +Install with `pip`: + +```bash +pip install serpapi +``` + +Or add it to a project with `uv`: + +```bash +uv add serpapi +``` + +For an existing virtual environment managed outside a `uv` project: + +```bash +uv pip install serpapi +``` + +Python 3.6 or newer is required by the package. + +Set your API key in the environment: + +```bash +export SERPAPI_KEY="secret_api_key" +``` + +On Windows PowerShell: + +```powershell +$env:SERPAPI_KEY="secret_api_key" +``` + +## First Search + +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search({ + "engine": "google", + "q": "coffee shops", + "location": "Austin, Texas", + "hl": "en", + "gl": "us", +}) + +first_result = results["organic_results"][0] +print(first_result["title"]) +print(first_result["link"]) +``` + +`client.search()` returns a `SerpResults` object for JSON responses. It behaves like a dictionary and adds helpers such as `as_dict()`, `next_page()`, and `yield_pages()`. + +## What the Parameters Mean + +The example above sends a Google Search request: + +| Parameter | Purpose | +| --- | --- | +| `engine` | Selects the SerpApi engine. `google` is Google Search. | +| `q` | Search query. | +| `location` | Search location used by SerpApi. | +| `hl` | Interface language. | +| `gl` | Country for Google results. | + +SerpApi supports many engines and engine-specific parameters. The authoritative list lives in the [SerpApi API documentation](https://serpapi.com/search-api). The [SerpApi Playground](https://serpapi.com/playground) is the fastest way to build a request and copy the final parameters into Python. + +## When to Use the Client + +Use `serpapi.Client` for application code, scripts that make more than one request, pagination, and concurrent workloads. The client stores the API key, timeout, and HTTP session configuration in one place. + +The module-level helpers are useful for quick experiments: + +```python +import serpapi + +results = serpapi.search( + api_key="secret_api_key", + engine="google", + q="coffee", +) +``` + +For production code, prefer a client instance: + +```python +client = serpapi.Client(api_key="secret_api_key", timeout=20) +results = client.search({"engine": "google", "q": "coffee"}) +``` + +## Next Steps + +- Read [Client Usage](client-usage.md) for response helpers, request options, and archive helpers. +- Read [Pagination](pagination.md) when collecting more than one page of results. +- Try [Google Across Countries](../docs/examples/google-across-countries.md) for a practical localization example. diff --git a/docs/user_guide/01-client-usage.md b/docs/user_guide/01-client-usage.md new file mode 100644 index 0000000..b9019f8 --- /dev/null +++ b/docs/user_guide/01-client-usage.md @@ -0,0 +1,163 @@ +--- +title: "Client Usage" +description: "Use the SerpApi client, module helpers, request options, and response helpers." +guide-section: "Basics" +--- + +# Client Usage + +The primary interface is `serpapi.Client`. A client keeps the API key, timeout, and HTTP session configuration together, which is useful for applications and scripts that make more than one request. + +## Create a Client + +```python +import os +import serpapi + +client = serpapi.Client( + api_key=os.environ["SERPAPI_KEY"], + timeout=20, +) +``` + +`timeout` becomes the default request timeout for searches, archive lookups, account calls, and locations calls made by this client. + +## Search + +Pass parameters as a dictionary: + +```python +results = client.search({ + "engine": "google", + "q": "coffee", +}) +``` + +Keyword arguments are also supported: + +```python +results = client.search(engine="google", q="coffee") +``` + +Dictionary parameters and keyword arguments can be mixed. Keyword arguments update the dictionary: + +```python +params = {"engine": "google", "q": "coffee"} +results = client.search(params, location="Austin, Texas") +``` + +## Module Helpers + +For quick scripts, the package exposes module-level helpers: + +```python +import serpapi + +results = serpapi.search( + api_key="secret_api_key", + engine="google", + q="coffee", +) +``` + +The helpers use a shared client under the hood. For applications, tests, pagination, and concurrent workloads, prefer creating your own `serpapi.Client`. + +## Search Archive + +Use `search_archive()` when you have a SerpApi search ID: + +```python +search_id = results["search_metadata"]["id"] +archived = client.search_archive(search_id=search_id) +``` + +If `search_id` is missing, the client raises `serpapi.SearchIDNotProvided`. + +## Account and Locations + +```python +account = client.account() +locations = client.locations(q="Austin", limit=3) +``` + +`account()` returns account information for the configured API key. `locations()` returns supported Google locations that match the query; see [Account and Locations](account-and-locations.md) for the local guide and example. + +## Request Options + +`search()`, `search_archive()`, `account()`, and `locations()` pass these keyword arguments to the underlying `requests` call: + +| Option | Use | +| --- | --- | +| `timeout` | Override the client timeout for one request. | +| `proxies` | Send the request through a proxy. | +| `verify` | Control TLS certificate verification. | +| `stream` | Stream the HTTP response. | +| `cert` | Use a client certificate. | + +Example: + +```python +results = client.search( + {"engine": "google", "q": "coffee"}, + timeout=10, +) +``` + +## Response Objects + +JSON search responses are returned as `serpapi.SerpResults`. It behaves like a dictionary: + +```python +first = results["organic_results"][0] +print(first.get("title")) +print(first.get("link")) +``` + +Use `as_dict()` when another library needs a plain dictionary: + +```python +payload = results.as_dict() +``` + +If you request `output=html`, the response is plain text instead of `SerpResults`: + +```python +html = client.search(engine="google", q="coffee", output="html") +print(html[:500]) +``` + +## Pagination Helpers + +For one additional page, call `next_page()`: + +```python +next_results = results.next_page() +``` + +To iterate through all available pages, use `yield_pages()`: + +```python +for page in results.yield_pages(max_pages=10): + print(page["search_metadata"].get("page_number")) +``` + +See [Pagination](pagination.md) for fuller pagination examples and Google `start` offsets. + +## Error Handling + +The client raises SerpApi-specific exceptions for common failure modes: + +```python +import serpapi + +try: + results = client.search(engine="google", q="coffee") +except serpapi.TimeoutError: + print("The request timed out.") +except serpapi.HTTPConnectionError: + print("Could not connect to SerpApi.") +except serpapi.HTTPError as exc: + print(exc.status_code, exc.error) +``` + +See [Errors and Timeouts](errors-and-timeouts.md) for status handling guidance. diff --git a/docs/user_guide/02-parameters-and-engines.md b/docs/user_guide/02-parameters-and-engines.md new file mode 100644 index 0000000..b7325de --- /dev/null +++ b/docs/user_guide/02-parameters-and-engines.md @@ -0,0 +1,79 @@ +--- +title: "Parameters and Engines" +description: "How Python parameters map to SerpApi engines and where to find the full supported list." +guide-section: "User Guide" +--- + +# Parameters and Engines + +SerpApi Python forwards search parameters to the SerpApi HTTP API. The package does not keep a separate engine schema, so new engines and engine parameters can be used as soon as they are supported by SerpApi. + +## Full Engine and Parameter Reference + +Use these SerpApi resources as the source of truth: + +- [SerpApi Search API documentation](https://serpapi.com/search-api) lists supported engines and engine-specific parameters. +- [SerpApi Playground](https://serpapi.com/playground) lets you test a search and copy the exact parameters into Python. +- [Account and Locations](account-and-locations.md) shows how to find valid `location` values from Python. + +The Search API docs include engines such as Google Search, Google Maps, Google Images, Google Shopping, Google Scholar, Google Jobs, Bing, DuckDuckGo, Yahoo, Yandex, Baidu, YouTube, eBay, Walmart, Naver, Apple App Store, Home Depot, and many more. Check the docs for the current full list. + +## Passing Parameters + +Pass engine parameters as a dictionary: + +```python +results = client.search({ + "engine": "google", + "q": "coffee", + "location": "Austin, Texas", + "hl": "en", + "gl": "us", +}) +``` + +Or pass them as keyword arguments: + +```python +results = client.search( + engine="google", + q="coffee", + location="Austin, Texas", + hl="en", + gl="us", +) +``` + +## Common Google Parameters + +These are common Google Search parameters. Other engines have their own parameter names. + +| Parameter | Purpose | +| --- | --- | +| `engine` | Search engine. Use `google` for Google Search. | +| `q` | Search query. | +| `location` | Geographic location for the search. | +| `google_domain` | Google domain, such as `google.com` or `google.co.in`. | +| `gl` | Country code for the search. | +| `hl` | Interface language. | +| `start` | Result offset for pagination. | +| `device` | Device type, such as desktop or mobile. | +| `output` | Response format, usually `json` or `html`. | +| `async` | Submit a server-side async search. | +| `no_cache` | Force SerpApi to fetch fresh results. | +| `zero_trace` | Request zero data retention behavior when enabled for your account. | + +For the full supported list and the exact meaning of each parameter, use the [SerpApi Search API documentation](https://serpapi.com/search-api). + +## Engine-Specific Names + +Different engines use different query parameter names. For example: + +```python +google = client.search(engine="google", q="coffee") +ebay = client.search(engine="ebay", _nkw="coffee grinder") +youtube = client.search(engine="youtube", search_query="coffee brewing") +walmart = client.search(engine="walmart", query="coffee") +``` + +When in doubt, open the engine in the [SerpApi Playground](https://serpapi.com/playground), set up the search, and copy the generated parameters. diff --git a/docs/user_guide/03-pagination.md b/docs/user_guide/03-pagination.md new file mode 100644 index 0000000..d23e1af --- /dev/null +++ b/docs/user_guide/03-pagination.md @@ -0,0 +1,86 @@ +--- +title: "Pagination" +description: "Collect additional pages with SerpResults helpers or engine-specific offsets." +guide-section: "User Guide" +--- + +# Pagination + +Many SerpApi responses include pagination metadata. SerpApi Python exposes this through `SerpResults.next_page_url`, `next_page()`, and `yield_pages()`. + +## Fetch One More Page + +```python +results = client.search({ + "engine": "google", + "q": "coffee", + "location": "Austin, Texas", +}) + +next_results = results.next_page() + +if next_results: + for item in next_results.get("organic_results", []): + print(item.get("title")) +``` + +`next_page()` returns `None` when the response does not include a next page URL. + +## Iterate Through Pages + +Use `yield_pages()` when you want the current page plus following pages: + +```python +results = client.search({ + "engine": "google", + "q": "coffee", + "location": "Austin, Texas", +}) + +for page in results.yield_pages(max_pages=5): + for item in page.get("organic_results", []): + print(item.get("position"), item.get("title")) +``` + +Always set `max_pages` deliberately. Search result pages can change over time and some queries expose many pages. + +## Google `start` Offsets + +Google Search also supports explicit result offsets: + +```python +for start in [0, 10, 20]: + page = client.search({ + "engine": "google", + "q": "coffee", + "location": "Austin, Texas", + "start": start, + }) + + print("Offset:", start) + for item in page.get("organic_results", []): + print(item.get("title")) +``` + +Use this style when you need deterministic offsets or when you want to parallelize independent pages. + +## Store Only What You Need + +Search responses can be large. For long crawls or scheduled jobs, extract and store only the fields your application needs: + +```python +records = [] + +for page in results.yield_pages(max_pages=3): + for item in page.get("organic_results", []): + records.append({ + "title": item.get("title"), + "link": item.get("link"), + "snippet": item.get("snippet"), + }) +``` + +## Pagination Parameters Vary by Engine + +`start` is common for Google Search, but other engines may use different pagination parameters. Check the relevant engine page in the [SerpApi Search API documentation](https://serpapi.com/search-api) or build the request in the [SerpApi Playground](https://serpapi.com/playground). + diff --git a/docs/user_guide/04-errors-and-timeouts.md b/docs/user_guide/04-errors-and-timeouts.md new file mode 100644 index 0000000..d5e3a8c --- /dev/null +++ b/docs/user_guide/04-errors-and-timeouts.md @@ -0,0 +1,76 @@ +--- +title: "Errors and Timeouts" +description: "Handle SerpApi HTTP errors, connection errors, timeouts, and API-level errors." +guide-section: "User Guide" +--- + +# Errors and Timeouts + +SerpApi Python wraps common failure modes in package-specific exceptions while preserving useful details from `requests`. + +## HTTP Errors + +Non-200 responses raise `serpapi.HTTPError`: + +```python +import serpapi + +try: + results = client.search(engine="google", q="coffee") +except serpapi.HTTPError as exc: + print("Status:", exc.status_code) + print("Error:", exc.error) +``` + +`exc.status_code` contains the HTTP status code when available. `exc.error` contains the `error` field from SerpApi's JSON response when available. + +See [SerpApi API status and error codes](https://serpapi.com/api-status-and-error-codes) for status guidance. + +## Connection Errors and Timeouts + +```python +try: + results = client.search(engine="google", q="coffee", timeout=10) +except serpapi.TimeoutError: + print("The request timed out.") +except serpapi.HTTPConnectionError: + print("Could not connect to SerpApi.") +``` + +Set a default timeout on the client: + +```python +client = serpapi.Client(api_key="secret_api_key", timeout=20) +``` + +Override it for one request: + +```python +results = client.search( + {"engine": "google", "q": "coffee"}, + timeout=5, +) +``` + +## Missing Search IDs + +`search_archive()` requires a `search_id`: + +```python +try: + archived = client.search_archive() +except serpapi.SearchIDNotProvided: + print("Provide search_id from search_metadata.id.") +``` + +## API-Level Errors in JSON + +Some API responses may include an `error` field in JSON. If your workflow continues after a response is returned, check for that field before using result arrays: + +```python +results = client.search(engine="google", q="coffee") + +if "error" in results: + raise RuntimeError(results["error"]) +``` + diff --git a/docs/user_guide/05-account-and-locations.md b/docs/user_guide/05-account-and-locations.md new file mode 100644 index 0000000..63ef89a --- /dev/null +++ b/docs/user_guide/05-account-and-locations.md @@ -0,0 +1,49 @@ +--- +title: "Account and Locations" +description: "Use account and locations helpers for account metadata and valid Google locations." +guide-section: "User Guide" +--- + +# Account and Locations + +The client includes helpers for SerpApi account metadata and Google locations. + +## Account + +Use `account()` to inspect information for the configured API key: + +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"]) + +account = client.account() +print(account) +``` + +This is useful in diagnostics, internal tooling, and setup checks. + +## Locations + +Use `locations()` to find supported Google locations: + +```python +locations = client.locations(q="Austin", limit=5) + +for location in locations: + print(location.get("name"), location.get("canonical_name")) +``` + +Then pass the selected location to a search: + +```python +results = client.search({ + "engine": "google", + "q": "coffee", + "location": "Austin, Texas, United States", +}) +``` + +The [SerpApi Locations API documentation](https://serpapi.com/locations-api) has the full endpoint behavior. + diff --git a/docs/user_guide/06-migrating-from-google-search-results.md b/docs/user_guide/06-migrating-from-google-search-results.md new file mode 100644 index 0000000..5093fd6 --- /dev/null +++ b/docs/user_guide/06-migrating-from-google-search-results.md @@ -0,0 +1,116 @@ +--- +title: "Migrating from google-search-results" +description: "Move from the deprecated google-search-results SDK to the recommended serpapi package." +guide-section: "Migration" +--- + +SerpApi has two Python libraries that use similar import names. For new projects and active integrations, use the recommended `serpapi` package. + +## Recommended Package + +The new and recommended client is [`serpapi` on PyPI](https://pypi.org/project/serpapi/). + +Install it with: + +```bash +pip install serpapi +``` + +Use `serpapi.Client`: + +```python +import os +import serpapi + +YOUR_API_KEY = os.environ["SERPAPI_KEY"] + +client = serpapi.Client(api_key=YOUR_API_KEY) +results = client.search({ + "engine": "google", + "q": "coffee", +}) + +print(results) +``` + +This is the package used throughout the current documentation. + +## Deprecated Package + +The old Python SDK is [`google-search-results` on PyPI](https://pypi.org/project/google-search-results/). It is deprecated for new integrations. + +It was installed with: + +```bash +pip install google-search-results +``` + +Older code often looks like this: + + +```python +from serpapi import GoogleSearch + +search = GoogleSearch({ + "q": "coffee", + "location": "Austin,Texas", + "api_key": "", +}) +result = search.get_dict() +``` + +Do not add this package to new projects. + +## Update Your Dependencies + +Remove `google-search-results` from your dependency files, such as `requirements.txt`, `pyproject.toml`, `setup.py`, `Pipfile`, or Poetry dependency configuration. + +Then add `serpapi` instead: + +```bash +pip install serpapi +``` + +Avoid installing both packages in the same environment. They can share the `serpapi` import namespace, which makes migration harder to reason about. + +## Update Your Code + +Replace `GoogleSearch(...).get_dict()` with `serpapi.Client(...).search(...)`. + +Old: + + +```python +from serpapi import GoogleSearch + +search = GoogleSearch({ + "q": "coffee", + "location": "Austin,Texas", + "api_key": "", +}) +result = search.get_dict() +``` + +New: + +```python +import os +import serpapi + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"]) +results = client.search({ + "engine": "google", + "q": "coffee", + "location": "Austin, Texas", +}) +``` + +Search parameters are still passed as a dictionary, so most request parameter usage can move over directly. The main change is the client interface. + +## Migration Checklist + +- Remove `google-search-results` from requirements and dependency files. +- Install `serpapi` with `pip install serpapi`. +- Replace `from serpapi import GoogleSearch` with `import serpapi`. +- Replace `GoogleSearch(params).get_dict()` with `serpapi.Client(api_key=...).search(params)`. +- Keep using the [SerpApi Playground](https://serpapi.com/playground) and the [SerpApi API documentation](https://serpapi.com/search-api) to confirm engine parameters. diff --git a/docs/user_guide/20-async-search-archive.md b/docs/user_guide/20-async-search-archive.md new file mode 100644 index 0000000..3434179 --- /dev/null +++ b/docs/user_guide/20-async-search-archive.md @@ -0,0 +1,57 @@ +--- +title: "Async Search Archive" +description: "Submit server-side async searches and retrieve them later from the Search Archive API." +guide-section: "Advanced Usage" +--- + +# Async Search Archive + +SerpApi supports server-side async searches. This is different from Python `asyncio`: you submit a search with `async=true`, receive a search ID, and retrieve the completed search later with the Search Archive API. + +## Submit an Async Search + +```python +submitted = client.search({ + "engine": "google", + "q": "coffee", + "location": "Austin, Texas", + "async": True, +}) + +search_id = submitted["search_metadata"]["id"] +print(search_id) +``` + +## Retrieve the Search + +```python +archived = client.search_archive(search_id=search_id) + +status = archived.get("search_metadata", {}).get("status") +print(status) +``` + +If the search is still queued or processing, wait and call `search_archive()` again: + +```python +import time + +while True: + archived = client.search_archive(search_id=search_id) + status = archived.get("search_metadata", {}).get("status") + + if status == "Success": + break + + if status == "Error": + raise RuntimeError(archived.get("error", "Search failed")) + + time.sleep(2) +``` + +## When to Use Async Searches + +Async searches are useful when you want to submit work quickly and collect results later, especially from job queues, scheduled workers, or data pipelines. + +Do not combine `async=true` with `no_cache=true`. Use the [SerpApi Search Archive API documentation](https://serpapi.com/search-archive-api) for retention windows and status details. + diff --git a/docs/user_guide/21-threading.md b/docs/user_guide/21-threading.md new file mode 100644 index 0000000..2f127ec --- /dev/null +++ b/docs/user_guide/21-threading.md @@ -0,0 +1,72 @@ +--- +title: "Threading" +description: "Parallelize independent SerpApi requests with ThreadPoolExecutor." +guide-section: "Advanced Usage" +--- + +# Threading + +SerpApi requests are network-bound, so threads are often a simple way to run independent searches concurrently. + +## ThreadPoolExecutor Example + +```python +import os +import serpapi + +from concurrent.futures import ThreadPoolExecutor, as_completed + + +API_KEY = os.environ["SERPAPI_KEY"] + + +def search_country(country): + client = serpapi.Client(api_key=API_KEY, timeout=20) + results = client.search({ + "engine": "google", + "q": "best coffee beans", + "gl": country, + "hl": "en", + }) + return country, results.get("organic_results", []) + + +countries = ["us", "gb", "ca", "au", "in"] + +with ThreadPoolExecutor(max_workers=5) as executor: + futures = [executor.submit(search_country, country) for country in countries] + + for future in as_completed(futures): + country, organic_results = future.result() + first = organic_results[0] if organic_results else {} + print(country, first.get("title")) +``` + +## Practical Guidance + +- Keep `max_workers` modest and tune it for your quota, latency, and downstream processing. +- Handle exceptions per future so one failed request does not hide all other results. +- Create a client inside each worker when you want clear isolation between threads. +- Use explicit timeouts so blocked network calls do not stall the whole batch. + +## Handling Per-Request Errors + +```python +with ThreadPoolExecutor(max_workers=5) as executor: + future_to_country = { + executor.submit(search_country, country): country + for country in countries + } + + for future in as_completed(future_to_country): + country = future_to_country[future] + + try: + country, organic_results = future.result() + except serpapi.SerpApiError as exc: + print("Failed:", country, exc) + continue + + print("Finished:", country, len(organic_results)) +``` + diff --git a/docs/user_guide/22-multiprocessing.md b/docs/user_guide/22-multiprocessing.md new file mode 100644 index 0000000..0ea1827 --- /dev/null +++ b/docs/user_guide/22-multiprocessing.md @@ -0,0 +1,74 @@ +--- +title: "Multiprocessing" +description: "Parallelize CPU-heavy or isolated SerpApi workloads with ProcessPoolExecutor." +guide-section: "Advanced Usage" +--- + +# Multiprocessing + +Use multiprocessing when each search has heavy CPU-bound post-processing, when you need process isolation, or when your worker code already runs in a process pool. For simple network-bound searches, threads are usually lighter. + +## ProcessPoolExecutor Example + +```python +import os +import serpapi + +from concurrent.futures import ProcessPoolExecutor, as_completed + + +def search_country(country): + client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + results = client.search({ + "engine": "google", + "q": "best coffee beans", + "gl": country, + "hl": "en", + }) + + organic_results = results.get("organic_results", []) + return { + "country": country, + "count": len(organic_results), + "first_title": organic_results[0].get("title") if organic_results else None, + } + + +if __name__ == "__main__": + countries = ["us", "gb", "ca", "au", "in"] + + with ProcessPoolExecutor(max_workers=4) as executor: + futures = [executor.submit(search_country, country) for country in countries] + + for future in as_completed(futures): + print(future.result()) +``` + +The `if __name__ == "__main__"` guard is important for multiprocessing, especially on macOS and Windows. + +## Practical Guidance + +- Create the `serpapi.Client` inside the worker process. +- Return plain serializable data such as dictionaries, lists, strings, and numbers. +- Avoid passing open sessions, response objects, or client instances between processes. +- Set explicit timeouts. +- Prefer threading for simple concurrent HTTP requests unless CPU-heavy work makes multiprocessing worthwhile. + +## Combining Pagination and Processes + +For Google Search, independent `start` offsets can be distributed across workers: + +```python +def search_offset(start): + client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + results = client.search({ + "engine": "google", + "q": "coffee", + "location": "Austin, Texas", + "start": start, + }) + return results.get("organic_results", []) +``` + +Check the relevant engine docs before parallelizing offsets because pagination parameters vary by engine. + diff --git a/docs/user_guide/23-zero-trace.md b/docs/user_guide/23-zero-trace.md new file mode 100644 index 0000000..2a197bd --- /dev/null +++ b/docs/user_guide/23-zero-trace.md @@ -0,0 +1,49 @@ +--- +title: "Zero Trace and Data Retention" +description: "Use zero_trace, no_cache, and async choices intentionally." +guide-section: "Advanced Usage" +--- + +# Zero Trace and Data Retention + +SerpApi exposes request parameters for data retention and cache behavior. The Python client passes these parameters through to the API. + +## Zero Trace + +When enabled for your account, pass `zero_trace=true` to request zero data retention behavior for a search: + +```python +results = client.search({ + "engine": "google", + "q": "coffee", + "location": "Austin, Texas", + "zero_trace": True, +}) +``` + +Use this for workloads that require stricter data handling. Zero trace availability and behavior are account-dependent, so confirm details in the [SerpApi Search API documentation](https://serpapi.com/search-api) and your plan. + +## Cache Controls + +Use `no_cache=true` when you want SerpApi to fetch fresh results instead of using cached results: + +```python +results = client.search({ + "engine": "google", + "q": "coffee", + "no_cache": True, +}) +``` + +`no_cache=true` and `async=true` should not be used together. + +## Choosing Between Modes + +| Need | Parameter | +| --- | --- | +| Fresh live results | `no_cache=true` | +| Submit now and fetch later | `async=true` plus `search_archive()` | +| Zero data retention behavior | `zero_trace=true` when enabled for your account | + +See [Async Search Archive](async-search-archive.md) for async search usage. + diff --git a/great-docs.yml b/great-docs.yml new file mode 100644 index 0000000..cb76f05 --- /dev/null +++ b/great-docs.yml @@ -0,0 +1,115 @@ +display_name: SerpApi Python +module: serpapi +parser: sphinx +dynamic: true + +repo: https://github.com/serpapi/serpapi-python +site_url: https://serpapi.github.io/serpapi-python/ +github_style: icon +pypi: true + +source: + enabled: true + branch: master + path: serpapi + placement: title + +authors: + - name: SerpApi + role: Author + email: support@serpapi.com + homepage: https://serpapi.com + +funding: + name: SerpApi, LLC + roles: + - Copyright holder + homepage: https://serpapi.com + +logo: + light: assets/serpapi-logo.svg + dark: assets/serpapi-logo-dark.svg + alt: SerpApi + height: 32px + href: index.html + +favicon: assets/serpapi-icon.png + +hero: + enabled: false + logo: false + name: SerpApi Python + tagline: Official Python client for SerpApi search data. + badges: auto + +homepage: index +user_guide: docs/user_guide +sections: + - title: Examples + dir: docs/examples +markdown_pages: true +dark_mode_toggle: true + +accent_color: + light: "#377FEA" + dark: "#6937EA" +navbar_color: + light: "#FFFFFF" + dark: "#2F3030" + +site: + theme: flatly + toc: true + toc-depth: 2 + language: en + show_dates: false + show_author: false + +include_in_header: + - text: '' + - text: '' + +changelog: + enabled: false + +skill: + enabled: false + +sidebar_filter: + enabled: true + min_items: 8 + +inline_methods: true +exclude: + - HTTPClient + - http + - models + - textui + - utils + - core + - requests + - exceptions + - SerpResults.__getstate__ + - SerpResults.__setstate__ + - SerpResults.__repr__ + - SerpResults.from_http_response + - http.raise_for_status + - models.prettify_json + - textui.prettify_json + +reference: + - title: Search Client + desc: Main entry points for making requests and working with responses. + contents: + - Client + - SerpResults + + - title: Exceptions + desc: Errors raised by the client for API, HTTP, connection, and timeout failures. + contents: + - SerpApiError + - APIKeyNotProvided + - SearchIDNotProvided + - HTTPError + - HTTPConnectionError + - TimeoutError diff --git a/index.md b/index.md new file mode 120000 index 0000000..e892330 --- /dev/null +++ b/index.md @@ -0,0 +1 @@ +docs/index.md \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 0df45a5..af808fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,11 +39,12 @@ classifiers = [ [project.optional-dependencies] color = ["pygments"] test = ["pytest"] +docs = ["great-docs>=0.12; python_version >= '3.11'"] [project.urls] Homepage = "https://github.com/serpapi/serpapi-python" Source = "https://github.com/serpapi/serpapi-python" -Documentation = "https://serpapi-python.readthedocs.io/en/latest/" +Documentation = "https://serpapi.github.io/serpapi-python/" [tool.setuptools.dynamic] version = { attr = "serpapi.__version__.__version__" } @@ -52,4 +53,6 @@ version = { attr = "serpapi.__version__.__version__" } exclude = ["tests", "tests.*"] [tool.pytest.ini_options] -testpaths = ["tests"] +testpaths = ["tests", "serpapi"] +addopts = "--doctest-modules" +doctest_optionflags = ["ELLIPSIS", "NORMALIZE_WHITESPACE"] diff --git a/serpapi/core.py b/serpapi/core.py index 7d4454a..007a993 100644 --- a/serpapi/core.py +++ b/serpapi/core.py @@ -4,9 +4,10 @@ class Client(HTTPClient): - """A class that handles API requests to SerpApi in a user–friendly manner. + """A class that handles API requests to SerpApi in a user-friendly manner. :param api_key: The API Key to use for SerpApi.com. + :param timeout: The default timeout to use for requests. Please provide ``api_key`` when instantiating this class. We recommend storing this in an environment variable, like so: @@ -34,28 +35,21 @@ def __repr__(self): def search(self, params: dict = None, **kwargs): """Fetch a page of results from SerpApi. Returns a :class:`SerpResults ` object, or unicode text (*e.g.* if ``'output': 'html'`` was passed). - The following three calls are equivalent: + Dictionary parameters and keyword parameters are both supported: .. code-block:: python - >>> s = serpapi.search(q="Coffee", location="Austin, Texas, United States") - - .. code-block:: python - - >>> params = {"q": "Coffee", "location": "Austin, Texas, United States"} - >>> s = serpapi.search(**params) - - .. code-block:: python - - >>> params = {"q": "Coffee", "location": "Austin, Texas, United States"} - >>> s = serpapi.search(params) + >>> import types + >>> client = Client(api_key="secret_api_key") + >>> client.request = lambda method, path, params, **kwargs: types.SimpleNamespace(json=lambda: {"search_parameters": params}, text="") + >>> client.search({"engine": "google", "q": "Coffee"})["search_parameters"]["q"] + 'Coffee' + >>> client.search(engine="google", q="Coffee")["search_parameters"]["engine"] + 'google' - :param q: typically, this is the parameter for the search engine query. - :param engine: the search engine to use. Defaults to ``google``. - :param output: the output format desired (``html`` or ``json``). Defaults to ``json``. - :param api_key: the API Key to use for SerpApi.com. - :param **: any additional parameters to pass to the API. + :param params: SerpApi search parameters such as ``engine``, ``q``, ``location``, and ``output``. + :param kwargs: Additional SerpApi parameters or request options. ``timeout``, ``proxies``, ``verify``, ``stream``, and ``cert`` are passed to the underlying HTTP request. **Learn more**: https://serpapi.com/search-api @@ -79,10 +73,8 @@ def search(self, params: dict = None, **kwargs): def search_archive(self, params: dict = None, **kwargs): """Get a result from the SerpApi Search Archive API. - :param search_id: the Search ID of the search to retrieve from the archive. - :param api_key: the API Key to use for SerpApi.com. - :param output: the output format desired (``html`` or ``json``). Defaults to ``json``. - :param **: any additional parameters to pass to the API. + :param params: Archive parameters. Must include ``search_id``. + :param kwargs: Additional archive parameters or request options. ``timeout``, ``proxies``, ``verify``, ``stream``, and ``cert`` are passed to the underlying HTTP request. **Learn more**: https://serpapi.com/search-archive-api """ @@ -112,9 +104,8 @@ def locations(self, params: dict = None, **kwargs): """Get a list of supported Google locations. - :param q: restricts your search to locations that contain the supplied string. - :param limit: limits the number of locations returned. - :param **: any additional parameters to pass to the API. + :param params: Location API parameters such as ``q`` and ``limit``. + :param kwargs: Additional location parameters or request options. ``timeout``, ``proxies``, ``verify``, ``stream``, and ``cert`` are passed to the underlying HTTP request. **Learn more**: https://serpapi.com/locations-api """ @@ -142,8 +133,8 @@ def locations(self, params: dict = None, **kwargs): def account(self, params: dict = None, **kwargs): """Get SerpApi account information. - :param api_key: the API Key to use for SerpApi.com. - :param **: any additional parameters to pass to the API. + :param params: Account API parameters. + :param kwargs: Additional account parameters or request options. ``timeout``, ``proxies``, ``verify``, ``stream``, and ``cert`` are passed to the underlying HTTP request. **Learn more**: https://serpapi.com/account-api """ diff --git a/serpapi/models.py b/serpapi/models.py index 0aa7720..6af044a 100644 --- a/serpapi/models.py +++ b/serpapi/models.py @@ -12,10 +12,9 @@ class SerpResults(UserDict): .. code-block:: python - >>> search = serpapi.search(q="Coffee", location="Austin, Texas, United States") - + >>> search = SerpResults({"search_metadata": {"id": "abc", "status": "Success"}}, client=None) >>> print(search["search_metadata"].keys()) - dict_keys(['id', 'status', 'json_endpoint', 'created_at', 'processed_at', 'google_url', 'raw_html_file', 'total_time_taken']) + dict_keys(['id', 'status']) An instance of this class is returned if the response is a valid JSON object. It can be used like a dictionary, but also has some additional methods. @@ -40,7 +39,14 @@ def __repr__(self): def as_dict(self): """Returns the data as a standard Python dictionary. - This can be useful when using ``json.dumps(search), for example.""" + This can be useful when using ``json.dumps(search)``, for example. + + .. code-block:: python + + >>> search = SerpResults({"answer": 42}, client=None) + >>> search.as_dict() + {'answer': 42} + """ return self.data.copy() @@ -85,7 +91,7 @@ def yield_pages(self, max_pages=1_000): def from_http_response(cls, r, *, client=None): """Construct a SerpResults object from an HTTP response. - :param assert_200: if ``True`` (default), raise an exception if the status code is not 200. + :param r: The HTTP response to parse. :param client: the Client instance which was used to send this request. An instance of this class is returned if the response is a valid JSON object. diff --git a/tests/test_docs_examples.py b/tests/test_docs_examples.py new file mode 100644 index 0000000..efbc49b --- /dev/null +++ b/tests/test_docs_examples.py @@ -0,0 +1,57 @@ +import os +import re +from pathlib import Path + +import pytest +import serpapi + + +ROOT = Path(__file__).resolve().parents[1] +PYTHON_BLOCK_RE = re.compile( + r"(?P\s*)?```python\n(?P.*?)```", + re.DOTALL, +) +API_KEY = os.environ.get("SERPAPI_KEY") or os.environ.get("API_KEY") + + +def docs_pages(): + return [ + ROOT / "README.md", + ROOT / "docs" / "index.md", + *sorted((ROOT / "docs" / "user_guide").glob("*.md")), + *sorted((ROOT / "docs" / "examples").glob("*.md")), + ] + + +def runnable_block(block): + if API_KEY is None: + return block + + return ( + block + .replace('"secret_api_key"', repr(API_KEY)) + .replace("'secret_api_key'", repr(API_KEY)) + ) + + +@pytest.mark.skipif(API_KEY is None, reason="SERPAPI_KEY or API_KEY is required") +@pytest.mark.parametrize("path", docs_pages(), ids=lambda path: path.relative_to(ROOT).as_posix()) +def test_markdown_python_blocks_execute(path, monkeypatch): + monkeypatch.setenv("SERPAPI_KEY", API_KEY) + monkeypatch.setenv("API_KEY", API_KEY) + + blocks = list(PYTHON_BLOCK_RE.finditer(path.read_text(encoding="utf-8"))) + assert blocks, f"{path} has no Python examples" + + namespace = { + "__name__": "docs_examples", + "client": serpapi.Client(api_key=API_KEY, timeout=20), + "os": os, + "serpapi": serpapi, + } + for number, block in enumerate(blocks, start=1): + if block.group("skip"): + continue + + code = runnable_block(block.group("code")) + exec(compile(code, f"{path}::python-block-{number}", "exec"), namespace) From ccd4cfa1e689ec629a14943985d025407f3aef7a Mon Sep 17 00:00:00 2001 From: Adarsh Divakaran Date: Sun, 21 Jun 2026 11:20:47 +0530 Subject: [PATCH 2/7] WIP: great-docs for documentation --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0cfdc92..03e554d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # SerpApi Python Library & Package [![Package](https://img.shields.io/pypi/v/serpapi?color=green)](https://pypi.org/project/serpapi) [![serpapi-python](https://github.com/serpapi/serpapi-python/actions/workflows/ci.yml/badge.svg)](https://github.com/serpapi/serpapi-python/actions/workflows/ci.yml) +[Full documentation](https://serpapi.github.io/serpapi-python/) + Integrate search data into your AI workflow, RAG / fine-tuning, or Python application using this official wrapper for [SerpApi](https://serpapi.com). SerpApi supports Google, Google Maps, Google Shopping, Baidu, Yandex, Yahoo, eBay, App Stores, and [more](https://serpapi.com). From cb68ed37f9e17f2f38eee3c9dfc3eec3891e2c10 Mon Sep 17 00:00:00 2001 From: Adarsh Divakaran Date: Fri, 10 Jul 2026 15:55:42 +0530 Subject: [PATCH 3/7] feat: move to static docs site using great-docs package --- .github/workflows/docs.yml | 26 ------------------- .github/workflows/release.yml | 47 +++++++++++++++++++++++++++++++++++ README.md | 16 +++--------- docs/index.md | 35 +++++++++++++------------- 4 files changed, 68 insertions(+), 56 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1bbc240..fc35486 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,10 +1,6 @@ name: Docs on: - push: - branches: [ great-docs ] - pull_request: - branches: [ great-docs ] workflow_dispatch: concurrency: @@ -36,25 +32,3 @@ jobs: - name: Build documentation run: great-docs build - - - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v5 - with: - path: great-docs/_site - include-hidden-files: true - - deploy-docs: - name: Deploy GitHub Pages - runs-on: ubuntu-latest - needs: build-docs - if: github.event_name == 'push' && github.ref == 'refs/heads/great-docs' - permissions: - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c3c5335..fa6e091 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -135,3 +135,50 @@ jobs: results = client.search({"engine": "google", "q": "coffee"}) assert results.get("organic_results"), "No organic results returned" print(f"OK: live search returned {len(results['organic_results'])} organic results") + + build-docs: + name: Build Great Docs + needs: [smoke-test] + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + + - name: Install package and documentation dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[docs]" + + - name: Set up Quarto + uses: quarto-dev/quarto-actions/setup@v2 + + - name: Build documentation + run: great-docs build + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v5 + with: + path: great-docs/_site + include-hidden-files: true + + deploy-docs: + name: Deploy GitHub Pages + needs: [build-docs] + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 diff --git a/README.md b/README.md index 03e554d..a6893de 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # SerpApi Python Library & Package [![Package](https://img.shields.io/pypi/v/serpapi?color=green)](https://pypi.org/project/serpapi) [![serpapi-python](https://github.com/serpapi/serpapi-python/actions/workflows/ci.yml/badge.svg)](https://github.com/serpapi/serpapi-python/actions/workflows/ci.yml) -[Full documentation](https://serpapi.github.io/serpapi-python/) - Integrate search data into your AI workflow, RAG / fine-tuning, or Python application using this official wrapper for [SerpApi](https://serpapi.com). SerpApi supports Google, Google Maps, Google Shopping, Baidu, Yandex, Yahoo, eBay, App Stores, and [more](https://serpapi.com). @@ -17,7 +15,7 @@ To install the `serpapi` package, simply run the following command: $ pip install serpapi ``` -Please note that this package is separate from the deprecated `google-search-results` SDK. This package is maintained by SerpApi and is the recommended way to access the SerpApi service from Python. +Please note that this package is separate from the legacy `serpapi` module, which is available on PyPi as `google-search-results`. This package is maintained by SerpApi, and is the recommended way to access the SerpApi service from Python. ## Simple Usage @@ -77,7 +75,7 @@ except serpapi.TimeoutError as e: ## Documentation -Documentation is built with [Great Docs](https://posit-dev.github.io/great-docs/) and published at the repository's default GitHub Pages site: [serpapi.github.io/serpapi-python](https://serpapi.github.io/serpapi-python/). +Documentation is [available on Read the Docs](https://serpapi-python.readthedocs.io/en/latest/). Change history is [available on GitHub](https://github.com/serpapi/serpapi-python/blob/master/HISTORY.md). @@ -370,13 +368,7 @@ MIT License. ## Contributing -Bug reports and pull requests are welcome on GitHub. Once dependencies are installed, you can run the tests with `pytest`. Pytest is configured to include doctests from the `serpapi` package. - -To run only doctests: - -```bash -pytest --doctest-modules serpapi -``` +Bug reports and pull requests are welcome on GitHub. Once dependencies are installed, you can run the tests with `pytest`. ## Publishing a new release @@ -388,4 +380,4 @@ pytest --doctest-modules serpapi ``` This triggers the [release workflow](.github/workflows/release.yml), which tests, builds, and publishes to PyPI, then smoke-tests the published package. -> **Required secrets:** `PYPI_API_TOKEN` (PyPI upload token) and `API_KEY` (used in smoke-test live search). +> **Required secrets:** `PYPI_API_TOKEN` (PyPI upload token) and `API_KEY` (used in smoke-test live search). \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 082b43f..4308cd9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,8 +9,6 @@ SerpApi supports Google, Google Maps, Google Shopping, Bing, DuckDuckGo, Baidu, Query a wide range of data at scale, including web search results, local business listings, shopping results, flight schedules, stock market data, job listings, trends, news headlines, AI Overview results, and video search results. -This package is separate from the deprecated `google-search-results` SDK. This package is maintained by SerpApi and is the recommended Python package for new integrations. If your project still depends on `google-search-results`, see [Migrating from google-search-results](user-guide/migrating-from-google-search-results.md). - ## Install With `pip`: @@ -19,22 +17,21 @@ With `pip`: pip install serpapi ``` -With `uv` in a project: +Or, if you are using `uv` for dependency management: ```bash uv add serpapi ``` -With `uv` in an existing environment: - -```bash -uv pip install serpapi -``` Python 3.6 or newer is required by the package. Building this documentation site uses Great Docs and Quarto on Python 3.11 or newer. ## First Request +Sign up at [SerpApi](https://serpapi.com/users/sign_up) and get your free API key from [dashboard](https://serpapi.com/manage-api-key) . Then, set the `SERPAPI_KEY` environment variable in your shell. + +```bash + ```python import os import serpapi @@ -59,6 +56,18 @@ Request parameters map directly to the SerpApi HTTP API. The actual full support :::{.docs-home-nav__intro} + +## Where to Go Next + +- Start with [Getting Started](user-guide/getting-started.md) for installation and a first request. +- Read [Client Usage](user-guide/client-usage.md) to learn the Python API, response helpers, request options, and archive helpers. +- Use [Migrating from google-search-results](user-guide/migrating-from-google-search-results.md) if your project still depends on the deprecated SDK. +- Use [Parameters and Engines](user-guide/parameters-and-engines.md) for links to the full supported engine and parameter docs. +- Browse [Examples](docs/examples/google-flights-travel.md) for focused recipes covering travel, finance, trends, AI Overview, local business, shopping, Bing, jobs, news, and video search. +- Review the generated API reference for `Client`, `SerpResults`, and exception classes. +- Use the [SerpApi Playground](https://serpapi.com/playground) to build and test request parameters before putting them in code. + + ## Documentation Map The full guide and examples are listed here on the homepage so you can jump directly into the workflow you need. Guide pages also include the standard collapsible docs sidebar when you open them. @@ -112,13 +121,3 @@ The full guide and examples are listed here on the homepage so you can jump dire ::: ::: - -## Where to Go Next - -- Start with [Getting Started](user-guide/getting-started.md) for installation and a first request. -- Read [Client Usage](user-guide/client-usage.md) to learn the Python API, response helpers, request options, and archive helpers. -- Use [Migrating from google-search-results](user-guide/migrating-from-google-search-results.md) if your project still depends on the deprecated SDK. -- Use [Parameters and Engines](user-guide/parameters-and-engines.md) for links to the full supported engine and parameter docs. -- Browse [Examples](docs/examples/google-flights-travel.md) for focused recipes covering travel, finance, trends, AI Overview, local business, shopping, Bing, jobs, news, and video search. -- Review the generated API reference for `Client`, `SerpResults`, and exception classes. -- Use the [SerpApi Playground](https://serpapi.com/playground) to build and test request parameters before putting them in code. From 89f4225a5d9d0a062e15c6f52c7ef125a872e3ef Mon Sep 17 00:00:00 2001 From: Adarsh Divakaran Date: Fri, 10 Jul 2026 21:50:32 +0530 Subject: [PATCH 4/7] feat: move to static docs site using great-docs package --- assets/serpapi-theme.css | 626 ++++++++++++++++-- docs/examples/00-google-across-countries.md | 30 +- docs/examples/01-bing-search.md | 11 +- .../examples/02-google-maps-local-business.md | 13 +- docs/examples/03-google-shopping-products.md | 15 +- docs/examples/04-google-flights-travel.md | 21 +- .../examples/05-google-finance-market-data.md | 13 +- docs/examples/06-google-trends-demand.md | 17 +- docs/examples/07-google-ai-overview.md | 23 +- docs/examples/08-google-news-monitoring.md | 13 +- docs/examples/09-google-jobs-listings.md | 15 +- docs/examples/10-youtube-video-search.md | 8 +- docs/index.md | 65 +- docs/user_guide/00-getting-started.md | 40 +- docs/user_guide/01-client-usage.md | 22 +- docs/user_guide/02-parameters-and-engines.md | 28 +- docs/user_guide/03-pagination.md | 33 +- docs/user_guide/04-errors-and-timeouts.md | 4 +- docs/user_guide/05-account-and-locations.md | 11 +- ...06-migrating-from-google-search-results.md | 21 +- docs/user_guide/20-async-search-archive.md | 13 +- docs/user_guide/21-threading.md | 13 +- docs/user_guide/22-multiprocessing.md | 25 +- docs/user_guide/23-zero-trace.md | 23 +- docs/user_guide/24-json-restrictor.md | 136 ++++ docs/user_guide/25-request-options.md | 134 ++++ great-docs.yml | 12 +- scripts/build_llms_txt.py | 168 +++++ serpapi/core.py | 36 +- serpapi/models.py | 14 +- 30 files changed, 1283 insertions(+), 320 deletions(-) create mode 100644 docs/user_guide/24-json-restrictor.md create mode 100644 docs/user_guide/25-request-options.md create mode 100755 scripts/build_llms_txt.py diff --git a/assets/serpapi-theme.css b/assets/serpapi-theme.css index d8f75db..9a3138d 100644 --- a/assets/serpapi-theme.css +++ b/assets/serpapi-theme.css @@ -1,15 +1,21 @@ :root { --serpapi-bg-soft: #f3f3f4; --serpapi-bg-soft-2: #ffffff; + --serpapi-surface: #ffffff; + --serpapi-surface-raised: #ffffff; --serpapi-border: #dddfea; --serpapi-text: #2f3030; - --serpapi-muted: #5f6570; + --serpapi-muted: #58606d; --serpapi-footer: #2f3030; - --serpapi-purple: #6937EA; - --serpapi-blue: #377FEA; - --serpapi-gradient: linear-gradient(135deg, var(--serpapi-blue) 0%, var(--serpapi-purple) 100%); + --serpapi-purple: #6937ea; + --serpapi-blue: #377fea; + --serpapi-link: #265fb8; + --serpapi-accent: #377fea; + --serpapi-gradient: linear-gradient(135deg, var(--serpapi-blue) 0%, var(--serpapi-accent) 100%); --serpapi-code-bg: #f6f8fa; --serpapi-code-text: #17212b; + --serpapi-code-border: #d8e1ee; + --serpapi-code-chrome: #eef3f8; --serpapi-code-keyword: #5932b8; --serpapi-code-string: #075985; --serpapi-code-number: #9a3412; @@ -26,14 +32,58 @@ body { font-family: Lato, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } +body.quarto-light, +html.quarto-light, +html.quarto-light body.quarto-light, +body.nav-fixed.quarto-light, +body.nav-sidebar.quarto-light, +body.gd-homepage.quarto-light, +[data-bs-theme="light"] body { + background-color: #ffffff !important; + color: var(--serpapi-text) !important; +} + +body.quarto-light main, +body.quarto-light main.content, +body.quarto-light #quarto-content, +body.quarto-light #quarto-document-content, +body.quarto-light .page-columns, +body.quarto-light .content, +body.quarto-light .quarto-container, +body.quarto-light section, +body.quarto-light section.level1, +body.quarto-light section.level2, +html.quarto-light main, +html.quarto-light main.content, +html.quarto-light #quarto-content, +html.quarto-light #quarto-document-content, +html.quarto-light .page-columns, +html.quarto-light .content, +html.quarto-light .quarto-container, +html.quarto-light section, +html.quarto-light section.level1, +html.quarto-light section.level2, +[data-bs-theme="light"] main, +[data-bs-theme="light"] main.content, +[data-bs-theme="light"] #quarto-content, +[data-bs-theme="light"] #quarto-document-content, +[data-bs-theme="light"] .page-columns, +[data-bs-theme="light"] .content, +[data-bs-theme="light"] .quarto-container, +[data-bs-theme="light"] section, +[data-bs-theme="light"] section.level1, +[data-bs-theme="light"] section.level2 { + background: transparent !important; +} + a { - color: var(--serpapi-purple); + color: var(--serpapi-link); } a:hover, a:focus { - color: var(--serpapi-purple); - text-decoration-color: var(--serpapi-blue); + color: var(--serpapi-blue); + text-decoration-color: var(--serpapi-accent); } .navbar, @@ -54,13 +104,13 @@ a:focus { .navbar-logo { height: 32px !important; max-height: 32px; - min-width: 140px; + min-width: 0; width: auto !important; } .navbar .nav-link:hover, .navbar .nav-link:focus { - color: var(--serpapi-purple) !important; + color: var(--serpapi-link) !important; } .hero, @@ -75,7 +125,7 @@ a:focus { .hero a, .gd-hero a, .quarto-title-banner a { - color: var(--serpapi-purple); + color: var(--serpapi-link); } .hero p, @@ -92,8 +142,8 @@ a:focus { .btn-primary:hover, .btn-primary:focus { - background: linear-gradient(135deg, var(--serpapi-purple) 0%, var(--serpapi-blue) 100%) !important; - border-color: var(--serpapi-purple) !important; + background: linear-gradient(135deg, #215fb7 0%, #0d837f 100%) !important; + border-color: var(--serpapi-blue) !important; } .btn-outline-primary { @@ -104,10 +154,16 @@ a:focus { .btn-outline-primary:hover, .btn-outline-primary:focus { background: var(--serpapi-gradient) !important; - border-color: var(--serpapi-purple) !important; + border-color: var(--serpapi-blue) !important; color: #ffffff !important; } +pre, +div.sourceCode { + border: 1px solid var(--serpapi-code-border); + border-radius: 6px; +} + pre, code.sourceCode, .sourceCode, @@ -117,6 +173,20 @@ div.sourceCode { color: var(--serpapi-code-text) !important; } +pre { + padding: 0.9rem 1rem; +} + +div.sourceCode { + margin: 1rem 0; +} + +div.sourceCode pre { + border: 0; + margin: 0; + padding: 0.9rem 1rem; +} + pre code, pre.sourceCode code, code.sourceCode, @@ -190,12 +260,83 @@ div.sourceCode a { color: inherit !important; } +p code:not(.sourceCode), +li code:not(.sourceCode), +td code:not(.sourceCode) { + background: rgba(47, 115, 217, 0.08); + border: 1px solid rgba(47, 115, 217, 0.14); + border-radius: 4px; + color: #405179; + padding: 0.08rem 0.28rem; +} + +.panel-tabset { + background: var(--serpapi-code-bg); + border: 1px solid var(--serpapi-code-border); + border-radius: 8px; + box-shadow: none; + margin: 0.85rem 0 1.1rem; + overflow: hidden; +} + +.panel-tabset > .nav-tabs { + background: var(--serpapi-code-bg); + border-bottom: 1px solid var(--serpapi-code-border); + display: flex; + gap: 0.6rem; + margin: 0; + padding: 0 0.9rem; +} + +.panel-tabset > .nav-tabs .nav-link { + background: transparent; + border: 0; + border-radius: 0; + color: var(--serpapi-muted); + font-weight: 700; + padding: 0.72rem 0 0.62rem; + text-decoration: none; +} + +.panel-tabset > .nav-tabs .nav-link:hover, +.panel-tabset > .nav-tabs .nav-link:focus { + color: var(--serpapi-link); +} + +.panel-tabset > .nav-tabs .nav-link.active { + background: transparent; + box-shadow: inset 0 -2px 0 var(--serpapi-accent); + color: var(--serpapi-text); + margin-bottom: -1px; +} + +.panel-tabset > .tab-content { + background: var(--serpapi-code-bg); + border: 0 !important; + margin: 0; + padding: 0; +} + +.panel-tabset .tab-pane { + padding: 0; +} + +.panel-tabset div.sourceCode { + border: 0; + border-radius: 0; + margin: 0; +} + +.panel-tabset div.sourceCode pre { + border-radius: 0; +} + .card, .quarto-grid-item, .listing-card { border-radius: 8px; border-color: var(--serpapi-border); - box-shadow: 0 10px 32px rgba(55, 127, 234, 0.06), 0 2px 9px rgba(105, 55, 234, 0.04); + box-shadow: 0 10px 28px rgba(24, 33, 43, 0.06); } .docs-home-nav { @@ -208,10 +349,10 @@ div.sourceCode a { .docs-home-nav__intro, .docs-home-nav__sidebar { - background: #ffffff; + background: var(--serpapi-surface-raised); border: 1px solid var(--serpapi-border); border-radius: 8px; - box-shadow: 0 8px 28px rgba(24, 33, 43, 0.05); + box-shadow: 0 10px 28px rgba(24, 33, 43, 0.05); padding: 1.25rem; } @@ -244,6 +385,22 @@ div.sourceCode a { color: var(--serpapi-muted); } +.description, +.quarto-title .description, +.quarto-title .subtitle, +.listing-description, +.quarto-title-meta, +.quarto-title-meta-contents, +#quarto-margin-sidebar, +#quarto-sidebar { + color: var(--serpapi-muted); +} + +#quarto-margin-sidebar #toc-title, +.quarto-title-meta-heading { + color: var(--serpapi-text); +} + .docs-home-actions { display: flex; flex-wrap: wrap; @@ -251,10 +408,17 @@ div.sourceCode a { margin-top: 1rem; } +.docs-home-actions p { + display: flex; + flex-wrap: wrap; + gap: 0.65rem; + margin: 0; +} + .docs-home-actions a { border: 1px solid var(--serpapi-border); border-radius: 6px; - color: var(--serpapi-purple); + color: var(--serpapi-link); display: inline-flex; font-weight: 600; line-height: 1.2; @@ -264,8 +428,218 @@ div.sourceCode a { .docs-home-actions a:hover, .docs-home-actions a:focus { - border-color: var(--serpapi-purple); - color: var(--serpapi-purple); + background: rgba(47, 115, 217, 0.06); + border-color: var(--serpapi-blue); + color: var(--serpapi-blue); +} + +#quarto-sidebar, +.sidebar-navigation { + background: var(--serpapi-bg-soft); + border-right: 1px solid var(--serpapi-border); +} + +#quarto-sidebar .sidebar-menu-container { + padding: 1rem 0.75rem; +} + +.sidebar .sidebar-item-section > .sidebar-item-container > a, +.sidebar .sidebar-item-section > .sidebar-item-container > .sidebar-item-text { + color: var(--serpapi-muted); + font-size: 0.82rem; + font-weight: 800; + text-transform: uppercase; +} + +.sidebar .sidebar-link { + border-radius: 6px; + color: var(--serpapi-muted); + padding: 0.22rem 0.35rem; + text-decoration: none; +} + +.sidebar .sidebar-link:hover, +.sidebar .sidebar-link:focus { + background: rgba(47, 115, 217, 0.08); + color: var(--serpapi-link); +} + +.sidebar .sidebar-link.active { + background: rgba(47, 115, 217, 0.1); + color: var(--serpapi-link); + font-weight: 800; +} + +#quarto-sidebar a.sidebar-item-text.sidebar-link.active, +#quarto-sidebar .sidebar-link.active { + color: var(--serpapi-link) !important; + text-decoration: none !important; +} + +#quarto-margin-sidebar { + color: var(--serpapi-muted); +} + +.doc-usage-source, +.doc-usage-source p { + color: var(--serpapi-muted) !important; +} + +.doc-usage-source a { + color: var(--serpapi-link) !important; +} + +.doc-usage-source a:hover, +.doc-usage-source a:focus { + color: var(--serpapi-blue) !important; +} + +#TOC .nav-link { + border-left: 2px solid var(--serpapi-border); + color: var(--serpapi-muted); + padding-left: 0.7rem; + text-decoration: none; +} + +#TOC .nav-link:hover, +#TOC .nav-link:focus { + color: var(--serpapi-link); +} + +#TOC .nav-link.active { + border-left-color: var(--serpapi-accent); + color: var(--serpapi-link) !important; + font-weight: 700; +} + +@media (max-width: 991.98px) { + .navbar, + .navbar.quarto-navbar { + padding: 0.65rem 0.75rem; + } + + .navbar .navbar-container { + align-items: center; + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + padding-left: 0 !important; + padding-right: 0 !important; + width: 100%; + } + + .navbar .navbar-toggler { + flex: 0 0 34px; + height: 34px; + margin: 0 !important; + order: 1; + padding: 0; + width: 34px; + } + + .navbar .navbar-brand-container, + .navbar .navbar-brand-container.mx-auto { + flex: 0 1 auto; + margin-left: 0 !important; + margin-right: auto !important; + min-width: 0; + order: 2; + } + + .navbar .navbar-brand { + align-items: center; + display: flex; + margin: 0 !important; + max-width: 36vw; + min-width: 0; + padding: 0.2rem 0 !important; + } + + .navbar .navbar-brand img, + .navbar-brand-logo img, + .navbar-logo { + height: 28px !important; + margin: 0 !important; + max-height: 28px; + max-width: min(36vw, 128px); + min-width: 0; + object-fit: contain; + width: auto !important; + } + + .navbar .quarto-navbar-tools { + align-items: center; + display: flex !important; + flex: 0 0 auto; + gap: 0.35rem; + margin-left: 0 !important; + order: 3; + padding: 0 !important; + } + + .navbar .quarto-navbar-tools button, + .navbar .quarto-navbar-tools .quarto-navigation-tool, + .navbar .dark-mode-toggle, + .navbar .gh-widget-trigger, + .navbar .nav-item.compact .nav-link, + .navbar .gd-navbar-icon { + align-items: center; + display: inline-flex; + height: 34px; + justify-content: center; + min-width: 34px; + padding: 0 !important; + width: 34px; + } + + .navbar #navbarCollapse { + flex-basis: 100%; + order: 4; + } + + .navbar #navbarCollapse.show, + .navbar #navbarCollapse.collapsing { + margin-top: 0.35rem; + } +} + +@media (max-width: 374px) { + .navbar, + .navbar.quarto-navbar { + padding: 0.55rem 0.65rem; + } + + .navbar .navbar-container { + gap: 0.4rem; + } + + .navbar .navbar-brand { + max-width: 34vw; + } + + .navbar .navbar-brand img, + .navbar-brand-logo img, + .navbar-logo { + height: 26px !important; + max-height: 26px; + max-width: min(34vw, 116px); + } + + .navbar .quarto-navbar-tools { + gap: 0.25rem; + } + + .navbar .navbar-toggler, + .navbar .quarto-navbar-tools button, + .navbar .quarto-navbar-tools .quarto-navigation-tool, + .navbar .dark-mode-toggle, + .navbar .gh-widget-trigger, + .navbar .nav-item.compact .nav-link, + .navbar .gd-navbar-icon { + height: 32px; + min-width: 32px; + width: 32px; + } } @media (max-width: 991px) { @@ -279,29 +653,92 @@ div.sourceCode a { } body.quarto-dark, +html.quarto-dark, +html.quarto-dark body.quarto-dark, +body.nav-fixed.quarto-dark, +body.nav-sidebar.quarto-dark, +body.gd-homepage.quarto-dark, [data-bs-theme="dark"] body { - --serpapi-text: #ecf0f1; - --serpapi-muted: #c6c8d1; - --serpapi-bg-soft: #2f3030; - --serpapi-bg-soft-2: #262727; - --serpapi-border: #44464b; - --serpapi-code-bg: #111827; - --serpapi-code-text: #f8fafc; - --serpapi-code-keyword: #c9b7ff; - --serpapi-code-string: #91d9ff; - --serpapi-code-number: #ffd27a; - --serpapi-code-function: #8cc8ff; - --serpapi-code-variable: #f3b5ff; - --serpapi-code-comment: #c6ced9; - --serpapi-code-warning: #ffb4b4; - background-color: #202122; + --serpapi-bg-soft: #111827; + --serpapi-bg-soft-2: #151c26; + --serpapi-surface: #151c26; + --serpapi-surface-raised: #1b2430; + --serpapi-border: #2a3646; + --serpapi-text: #e7edf5; + --serpapi-muted: #a9b6c8; + --serpapi-link: #67d8ef; + --serpapi-blue: #60a5fa; + --serpapi-accent: #2dd4bf; + --serpapi-code-bg: #0b1220; + --serpapi-code-text: #dbeafe; + --serpapi-code-border: #324256; + --serpapi-code-chrome: #101a29; + --serpapi-code-keyword: #93c5fd; + --serpapi-code-string: #5eead4; + --serpapi-code-number: #fbbf24; + --serpapi-code-function: #7dd3fc; + --serpapi-code-variable: #c4b5fd; + --serpapi-code-comment: #94a3b8; + --serpapi-code-warning: #fca5a5; + background-color: #0f141b !important; +} + +body.quarto-dark main, +body.quarto-dark main.content, +body.quarto-dark #quarto-content, +body.quarto-dark #quarto-document-content, +body.quarto-dark .page-columns, +body.quarto-dark .content, +body.quarto-dark .quarto-container, +body.quarto-dark section, +body.quarto-dark section.level1, +body.quarto-dark section.level2, +html.quarto-dark main, +html.quarto-dark main.content, +html.quarto-dark #quarto-content, +html.quarto-dark #quarto-document-content, +html.quarto-dark .page-columns, +html.quarto-dark .content, +html.quarto-dark .quarto-container, +html.quarto-dark section, +html.quarto-dark section.level1, +html.quarto-dark section.level2, +[data-bs-theme="dark"] main, +[data-bs-theme="dark"] main.content, +[data-bs-theme="dark"] #quarto-content, +[data-bs-theme="dark"] #quarto-document-content, +[data-bs-theme="dark"] .page-columns, +[data-bs-theme="dark"] .content, +[data-bs-theme="dark"] .quarto-container, +[data-bs-theme="dark"] section, +[data-bs-theme="dark"] section.level1, +[data-bs-theme="dark"] section.level2 { + background: transparent !important; +} + +body.quarto-dark #quarto-sidebar, +body.quarto-dark .sidebar-navigation, +html.quarto-dark #quarto-sidebar, +html.quarto-dark .sidebar-navigation, +[data-bs-theme="dark"] #quarto-sidebar, +[data-bs-theme="dark"] .sidebar-navigation { + background: #0f141b !important; + border-color: var(--serpapi-border) !important; +} + +body.quarto-dark #quarto-sidebar .sidebar-menu-container, +html.quarto-dark #quarto-sidebar .sidebar-menu-container, +[data-bs-theme="dark"] #quarto-sidebar .sidebar-menu-container { + background: transparent !important; } body.quarto-dark .navbar, +html.quarto-dark .navbar, [data-bs-theme="dark"] .navbar, body.quarto-dark .navbar.quarto-navbar, +html.quarto-dark .navbar.quarto-navbar, [data-bs-theme="dark"] .navbar.quarto-navbar { - background: #2f3030 !important; + background: #111827 !important; border-bottom-color: var(--serpapi-border); } @@ -315,39 +752,146 @@ body.quarto-dark .navbar .nav-link, } body.quarto-dark .hero, +html.quarto-dark .hero, body.quarto-dark .gd-hero, +html.quarto-dark .gd-hero, body.quarto-dark .quarto-title-banner, +html.quarto-dark .quarto-title-banner, [data-bs-theme="dark"] .hero, [data-bs-theme="dark"] .gd-hero, [data-bs-theme="dark"] .quarto-title-banner { - background: linear-gradient(180deg, #2f3030 0%, #262727 100%) !important; + background: linear-gradient(180deg, #111827 0%, #151c26 100%) !important; border-bottom-color: var(--serpapi-border); - box-shadow: inset 0 3px 0 var(--serpapi-purple); + box-shadow: inset 0 3px 0 var(--serpapi-accent); color: var(--serpapi-text); } body.quarto-dark a, +html.quarto-dark a, [data-bs-theme="dark"] a { - color: #ffffff; + color: var(--serpapi-link); } body.quarto-dark a:hover, body.quarto-dark a:focus, +html.quarto-dark a:hover, +html.quarto-dark a:focus, [data-bs-theme="dark"] a:hover, [data-bs-theme="dark"] a:focus { - color: var(--serpapi-purple); + color: var(--serpapi-accent); } body.quarto-dark .docs-home-nav__intro, body.quarto-dark .docs-home-nav__sidebar, +html.quarto-dark .docs-home-nav__intro, +html.quarto-dark .docs-home-nav__sidebar, [data-bs-theme="dark"] .docs-home-nav__intro, [data-bs-theme="dark"] .docs-home-nav__sidebar { - background: #2f3030; + background: var(--serpapi-surface-raised); border-color: var(--serpapi-border); } body.quarto-dark .docs-home-actions a, +html.quarto-dark .docs-home-actions a, [data-bs-theme="dark"] .docs-home-actions a { border-color: var(--serpapi-border); - color: #ffffff; + color: var(--serpapi-text); +} + +body.quarto-dark .docs-home-actions a:hover, +body.quarto-dark .docs-home-actions a:focus, +html.quarto-dark .docs-home-actions a:hover, +html.quarto-dark .docs-home-actions a:focus, +[data-bs-theme="dark"] .docs-home-actions a:hover, +[data-bs-theme="dark"] .docs-home-actions a:focus { + background: rgba(103, 216, 239, 0.1); + border-color: var(--serpapi-link); + color: var(--serpapi-link); +} + +body.quarto-dark .panel-tabset, +html.quarto-dark .panel-tabset, +[data-bs-theme="dark"] .panel-tabset { + background: var(--serpapi-code-bg); + border-color: var(--serpapi-code-border); + box-shadow: none; + margin: 0.85rem 0 1.1rem; +} + +body.quarto-dark .panel-tabset > .nav-tabs, +html.quarto-dark .panel-tabset > .nav-tabs, +[data-bs-theme="dark"] .panel-tabset > .nav-tabs { + background: var(--serpapi-code-bg); + border-bottom: 1px solid var(--serpapi-code-border); + gap: 0.6rem; + padding: 0 0.9rem; +} + +body.quarto-dark .panel-tabset > .nav-tabs .nav-link, +html.quarto-dark .panel-tabset > .nav-tabs .nav-link, +[data-bs-theme="dark"] .panel-tabset > .nav-tabs .nav-link { + background: transparent; + border: 0; + border-radius: 0; + padding: 0.72rem 0 0.62rem; +} + +body.quarto-dark .panel-tabset > .nav-tabs .nav-link.active, +html.quarto-dark .panel-tabset > .nav-tabs .nav-link.active, +[data-bs-theme="dark"] .panel-tabset > .nav-tabs .nav-link.active { + background: transparent; + box-shadow: inset 0 -2px 0 var(--serpapi-accent); + color: var(--serpapi-text); +} + +body.quarto-dark .panel-tabset > .tab-content, +html.quarto-dark .panel-tabset > .tab-content, +[data-bs-theme="dark"] .panel-tabset > .tab-content { + background: var(--serpapi-code-bg); + border: 0 !important; + margin: 0; + padding: 0; +} + +body.quarto-dark .panel-tabset div.sourceCode, +html.quarto-dark .panel-tabset div.sourceCode, +[data-bs-theme="dark"] .panel-tabset div.sourceCode { + border: 0; + border-radius: 0; + margin: 0; +} + +body.quarto-dark .panel-tabset div.sourceCode pre, +html.quarto-dark .panel-tabset div.sourceCode pre, +[data-bs-theme="dark"] .panel-tabset div.sourceCode pre { + border-radius: 0; +} + +body.quarto-dark p code:not(.sourceCode), +body.quarto-dark li code:not(.sourceCode), +body.quarto-dark td code:not(.sourceCode), +html.quarto-dark p code:not(.sourceCode), +html.quarto-dark li code:not(.sourceCode), +html.quarto-dark td code:not(.sourceCode), +[data-bs-theme="dark"] p code:not(.sourceCode), +[data-bs-theme="dark"] li code:not(.sourceCode), +[data-bs-theme="dark"] td code:not(.sourceCode) { + background: rgba(103, 216, 239, 0.09); + border-color: rgba(103, 216, 239, 0.18); + color: #9ee7f5; +} + +body.quarto-dark table, +html.quarto-dark table, +[data-bs-theme="dark"] table { + border-color: var(--serpapi-border); +} + +body.quarto-dark th, +body.quarto-dark td, +html.quarto-dark th, +html.quarto-dark td, +[data-bs-theme="dark"] th, +[data-bs-theme="dark"] td { + border-color: var(--serpapi-border); } diff --git a/docs/examples/00-google-across-countries.md b/docs/examples/00-google-across-countries.md index 795bba4..7c1ae4c 100644 --- a/docs/examples/00-google-across-countries.md +++ b/docs/examples/00-google-across-countries.md @@ -25,13 +25,13 @@ markets = [ ] for market in markets: - results = client.search({ - "engine": "google", - "q": "best coffee beans", - "location": market["location"], - "gl": market["gl"], - "hl": market["hl"], - }) + results = client.search( + engine="google", + q="best coffee beans", + location=market["location"], + gl=market["gl"], + hl=market["hl"], + ) organic = results.get("organic_results", []) first = organic[0] if organic else {} @@ -47,14 +47,14 @@ for market in markets: Some workflows also need a specific Google domain: ```python -results = client.search({ - "engine": "google", - "q": "best coffee beans", - "google_domain": "google.co.in", - "gl": "in", - "hl": "en", - "location": "Mumbai, Maharashtra", -}) +results = client.search( + engine="google", + q="best coffee beans", + google_domain="google.co.in", + gl="in", + hl="en", + location="Mumbai, Maharashtra", +) ``` ## Finding Valid Locations diff --git a/docs/examples/01-bing-search.md b/docs/examples/01-bing-search.md index a2e71ab..83d2b01 100644 --- a/docs/examples/01-bing-search.md +++ b/docs/examples/01-bing-search.md @@ -16,11 +16,11 @@ import serpapi client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) -results = client.search({ - "engine": "bing", - "q": "coffee", - "location": "Austin, Texas", -}) +results = client.search( + engine="bing", + q="coffee", + location="Austin, Texas", +) for result in results.get("organic_results", [])[:5]: print(result.get("position"), result.get("title")) @@ -36,4 +36,3 @@ print(results.keys()) ``` For every supported Bing parameter, use the [Bing Search API documentation](https://serpapi.com/bing-search-api). To tune a query interactively, use the [SerpApi Playground](https://serpapi.com/playground). - diff --git a/docs/examples/02-google-maps-local-business.md b/docs/examples/02-google-maps-local-business.md index fb98d84..642a627 100644 --- a/docs/examples/02-google-maps-local-business.md +++ b/docs/examples/02-google-maps-local-business.md @@ -16,12 +16,12 @@ import serpapi client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) -results = client.search({ - "engine": "google_maps", - "q": "coffee shops", - "ll": "@30.2672,-97.7431,14z", - "type": "search", -}) +results = client.search( + engine="google_maps", + q="coffee shops", + ll="@30.2672,-97.7431,14z", + type="search", +) for place in results.get("local_results", [])[:5]: print(place.get("title")) @@ -34,4 +34,3 @@ for place in results.get("local_results", [])[:5]: Start with `local_results`. Common fields include `title`, `rating`, `reviews`, `address`, `phone`, `website`, and `gps_coordinates`. Some responses also include `place_results` when the query resolves to a specific place. For the complete parameter list, use the [Google Maps API documentation](https://serpapi.com/google-maps-api). Use the [SerpApi Playground](https://serpapi.com/playground) to find a working `ll` value for your target market. - diff --git a/docs/examples/03-google-shopping-products.md b/docs/examples/03-google-shopping-products.md index 97ff760..3ff97b9 100644 --- a/docs/examples/03-google-shopping-products.md +++ b/docs/examples/03-google-shopping-products.md @@ -16,13 +16,13 @@ import serpapi client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) -results = client.search({ - "engine": "google_shopping", - "q": "espresso machine", - "location": "Austin, Texas", - "gl": "us", - "hl": "en", -}) +results = client.search( + engine="google_shopping", + q="espresso machine", + location="Austin, Texas", + gl="us", + hl="en", +) for product in results.get("shopping_results", [])[:5]: print(product.get("title")) @@ -35,4 +35,3 @@ for product in results.get("shopping_results", [])[:5]: Most shopping workflows use `shopping_results`. Save `title`, `price`, `source`, `rating`, `reviews`, `thumbnail`, and `link` if present. For filtering, sorting, and engine-specific options, check the live docs before hard-coding parameters. See the [Google Shopping API documentation](https://serpapi.com/google-shopping-api) and experiment in the [SerpApi Playground](https://serpapi.com/playground). - diff --git a/docs/examples/04-google-flights-travel.md b/docs/examples/04-google-flights-travel.md index 504a70f..bdfab71 100644 --- a/docs/examples/04-google-flights-travel.md +++ b/docs/examples/04-google-flights-travel.md @@ -16,16 +16,16 @@ import serpapi client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=30) -results = client.search({ - "engine": "google_flights", - "departure_id": "AUS", - "arrival_id": "LAX", - "outbound_date": "2026-09-15", - "return_date": "2026-09-22", - "currency": "USD", - "hl": "en", - "gl": "us", -}) +results = client.search( + engine="google_flights", + departure_id="AUS", + arrival_id="LAX", + outbound_date="2026-09-15", + return_date="2026-09-22", + currency="USD", + hl="en", + gl="us", +) flights = results.get("best_flights") or results.get("other_flights", []) @@ -40,4 +40,3 @@ for itinerary in flights[:3]: Start with `best_flights`, then fall back to `other_flights` when needed. Useful fields include `price`, `total_duration`, `carbon_emissions`, and the nested `flights` list for airline, airport, and timing data. For trip type, cabin, date, currency, and airport parameters, see the [Google Flights API documentation](https://serpapi.com/google-flights-api). The [SerpApi Playground](https://serpapi.com/playground) is useful for testing routes before adding them to code. - diff --git a/docs/examples/05-google-finance-market-data.md b/docs/examples/05-google-finance-market-data.md index f22b727..35d9c73 100644 --- a/docs/examples/05-google-finance-market-data.md +++ b/docs/examples/05-google-finance-market-data.md @@ -16,12 +16,12 @@ import serpapi client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) -results = client.search({ - "engine": "google_finance", - "q": "GOOGL:NASDAQ", - "window": "1M", - "hl": "en", -}) +results = client.search( + engine="google_finance", + q="GOOGL:NASDAQ", + window="1M", + hl="en", +) summary = results.get("summary", {}) knowledge = results.get("knowledge_graph", {}) @@ -36,4 +36,3 @@ print("Graph points:", len(results.get("graph", []))) Use `summary` and `knowledge_graph` for quote-level details, `graph` for time-series points, and `news_results` for finance-related headlines when present. Use `window` to change the graph range. See the [Google Finance API documentation](https://serpapi.com/google-finance-api) for supported `q` formats and time windows. You can test symbols in the [SerpApi Playground](https://serpapi.com/playground). - diff --git a/docs/examples/06-google-trends-demand.md b/docs/examples/06-google-trends-demand.md index e38d680..fa86551 100644 --- a/docs/examples/06-google-trends-demand.md +++ b/docs/examples/06-google-trends-demand.md @@ -16,14 +16,14 @@ import serpapi client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) -results = client.search({ - "engine": "google_trends", - "q": "electric bikes", - "date": "today 12-m", - "geo": "US", - "data_type": "TIMESERIES", - "tz": "420", -}) +results = client.search( + engine="google_trends", + q="electric bikes", + date="today 12-m", + geo="US", + data_type="TIMESERIES", + tz="420", +) timeline = results.get("interest_over_time", {}).get("timeline_data", []) @@ -37,4 +37,3 @@ for point in timeline[-5:]: For time-series charts, read `interest_over_time.timeline_data`. For regional demand, switch `data_type` to `GEO_MAP_0`. For related terms, use `RELATED_QUERIES` or `RELATED_TOPICS`. See the [Google Trends API documentation](https://serpapi.com/google-trends-api) for `data_type`, `geo`, `date`, and `tz` options. The [SerpApi Playground](https://serpapi.com/playground) helps confirm that a trend query returns the chart you expect. - diff --git a/docs/examples/07-google-ai-overview.md b/docs/examples/07-google-ai-overview.md index 0b80887..2cd1419 100644 --- a/docs/examples/07-google-ai-overview.md +++ b/docs/examples/07-google-ai-overview.md @@ -16,21 +16,21 @@ import serpapi client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) -search = client.search({ - "engine": "google", - "q": "how do noise cancelling headphones work", - "location": "Austin, Texas", - "gl": "us", - "hl": "en", -}) +search = client.search( + engine="google", + q="how do noise cancelling headphones work", + location="Austin, Texas", + gl="us", + hl="en", +) ai_overview = search.get("ai_overview", {}) if ai_overview.get("page_token"): - ai_overview = client.search({ - "engine": "google_ai_overview", - "page_token": ai_overview["page_token"], - }) + ai_overview = client.search( + engine="google_ai_overview", + page_token=ai_overview["page_token"], + ) for block in ai_overview.get("text_blocks", [])[:3]: print(block.get("snippet") or block.get("text")) @@ -41,4 +41,3 @@ for block in ai_overview.get("text_blocks", [])[:3]: Use `text_blocks` for the generated answer and `references` for cited sources when present. If the first Google Search response already includes the AI Overview content, you can read it directly without the second request. See the [Google Search API AI Overview docs](https://serpapi.com/search-api#api-examples-results-for-ai-overview) and the [Google AI Overview API documentation](https://serpapi.com/google-ai-overview-api). Use the [SerpApi Playground](https://serpapi.com/playground) to find queries that currently return AI Overview data. - diff --git a/docs/examples/08-google-news-monitoring.md b/docs/examples/08-google-news-monitoring.md index fbd4f94..0ebc9b3 100644 --- a/docs/examples/08-google-news-monitoring.md +++ b/docs/examples/08-google-news-monitoring.md @@ -16,12 +16,12 @@ import serpapi client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) -results = client.search({ - "engine": "google_news", - "q": "artificial intelligence", - "gl": "us", - "hl": "en", -}) +results = client.search( + engine="google_news", + q="artificial intelligence", + gl="us", + hl="en", +) for item in results.get("news_results", [])[:5]: print(item.get("title")) @@ -34,4 +34,3 @@ for item in results.get("news_results", [])[:5]: Start with `news_results`. Save `title`, `link`, `source`, `date`, `snippet`, and `thumbnail` when present. News result shapes can vary by query, so inspect a sample response before deciding your storage schema. See the [Google News API documentation](https://serpapi.com/google-news-api) and tune your topic in the [SerpApi Playground](https://serpapi.com/playground). - diff --git a/docs/examples/09-google-jobs-listings.md b/docs/examples/09-google-jobs-listings.md index dbdc3a7..3ce45e7 100644 --- a/docs/examples/09-google-jobs-listings.md +++ b/docs/examples/09-google-jobs-listings.md @@ -16,13 +16,13 @@ import serpapi client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) -results = client.search({ - "engine": "google_jobs", - "q": "software engineer", - "location": "Austin, Texas", - "hl": "en", - "gl": "us", -}) +results = client.search( + engine="google_jobs", + q="software engineer", + location="Austin, Texas", + hl="en", + gl="us", +) for job in results.get("jobs_results", [])[:5]: print(job.get("title")) @@ -35,4 +35,3 @@ for job in results.get("jobs_results", [])[:5]: Use `jobs_results` for the listing cards. Typical fields include `title`, `company_name`, `location`, `via`, `description`, `detected_extensions`, and `related_links`. For detail pages, check whether the response includes identifiers or links that fit your workflow. See the [Google Jobs API documentation](https://serpapi.com/google-jobs-api) for localization and pagination parameters. Use the [SerpApi Playground](https://serpapi.com/playground) to test role/location combinations. - diff --git a/docs/examples/10-youtube-video-search.md b/docs/examples/10-youtube-video-search.md index e03a3de..c932372 100644 --- a/docs/examples/10-youtube-video-search.md +++ b/docs/examples/10-youtube-video-search.md @@ -16,10 +16,10 @@ import serpapi client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) -results = client.search({ - "engine": "youtube", - "search_query": "coffee brewing guide", -}) +results = client.search( + engine="youtube", + search_query="coffee brewing guide", +) for video in results.get("video_results", [])[:5]: print(video.get("title")) diff --git a/docs/index.md b/docs/index.md index 4308cd9..d70297c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,72 +11,83 @@ Query a wide range of data at scale, including web search results, local busines ## Install -With `pip`: +:::{.panel-tabset} + +## pip ```bash pip install serpapi ``` -Or, if you are using `uv` for dependency management: +## uv ```bash uv add serpapi ``` +::: -Python 3.6 or newer is required by the package. Building this documentation site uses Great Docs and Quarto on Python 3.11 or newer. +Python 3.6 or newer is required by the package. ## First Request -Sign up at [SerpApi](https://serpapi.com/users/sign_up) and get your free API key from [dashboard](https://serpapi.com/manage-api-key) . Then, set the `SERPAPI_KEY` environment variable in your shell. +Sign up at [SerpApi](https://serpapi.com/users/sign_up), copy your API key from the [dashboard](https://serpapi.com/manage-api-key), and set it in your shell: + +:::{.panel-tabset} + +## macOS / Linux ```bash +export SERPAPI_KEY="secret_api_key" +``` + +## Windows + +```powershell +$env:SERPAPI_KEY = "secret_api_key" +``` + +::: ```python import os import serpapi client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"]) -results = client.search({ - "engine": "google", - "q": "coffee", - "location": "Austin, Texas", - "hl": "en", - "gl": "us", -}) +results = client.search( + engine="google", + q="coffee", + location="Austin, Texas", + hl="en", + gl="us", +) print(results["organic_results"][0]["link"]) ``` The `results` variable contains a `SerpResults` object. It behaves like a standard dictionary and adds convenience helpers for response conversion, pagination, and fetching search archives. -Request parameters map directly to the SerpApi HTTP API. The actual full supported engine list and engine-specific parameters are maintained in the [SerpApi API documentation](https://serpapi.com/search-api). Use the [SerpApi Playground](https://serpapi.com/playground) to build a request visually, test it, and copy the final parameters into Python. +Request parameters map directly to the SerpApi HTTP API. For the full engine list and engine-specific parameters, use the [SerpApi API documentation](https://serpapi.com/search-api). Use the [SerpApi Playground](https://serpapi.com/playground) to build and test a request before moving it into Python. :::{.docs-home-nav} :::{.docs-home-nav__intro} - -## Where to Go Next - -- Start with [Getting Started](user-guide/getting-started.md) for installation and a first request. -- Read [Client Usage](user-guide/client-usage.md) to learn the Python API, response helpers, request options, and archive helpers. -- Use [Migrating from google-search-results](user-guide/migrating-from-google-search-results.md) if your project still depends on the deprecated SDK. -- Use [Parameters and Engines](user-guide/parameters-and-engines.md) for links to the full supported engine and parameter docs. -- Browse [Examples](docs/examples/google-flights-travel.md) for focused recipes covering travel, finance, trends, AI Overview, local business, shopping, Bing, jobs, news, and video search. -- Review the generated API reference for `Client`, `SerpResults`, and exception classes. -- Use the [SerpApi Playground](https://serpapi.com/playground) to build and test request parameters before putting them in code. - - ## Documentation Map -The full guide and examples are listed here on the homepage so you can jump directly into the workflow you need. Guide pages also include the standard collapsible docs sidebar when you open them. +Choose the path that matches what you are building. Start with setup and a first request, then move into client behavior, request parameters, pagination, error handling, or engine-specific examples. + +- New integrations: read [Getting Started](user-guide/getting-started.md), then [Client Usage](user-guide/client-usage.md). +- Existing `google-search-results` users: follow the [Migration Guide](user-guide/migrating-from-google-search-results.md). +- Engine setup: use [Parameters and Engines](user-guide/parameters-and-engines.md) with the [SerpApi Playground](https://serpapi.com/playground). +- Production workflows: review [pagination](user-guide/pagination.md), [timeouts and errors](user-guide/errors-and-timeouts.md), [request options](user-guide/request-options.md), [account/location helpers](user-guide/account-and-locations.md), [async search archive](user-guide/async-search-archive.md), [JSON Restrictor](user-guide/json-restrictor.md), and [zero trace controls](user-guide/zero-trace.md). +- [Examples](docs/examples/google-across-countries.md): browse focused recipes for search, maps, shopping, flights, finance, trends, AI Overview, news, jobs, and YouTube. @@ -103,6 +114,8 @@ The full guide and examples are listed here on the homepage so you can jump dire - [Parallel Requests with Threads](user-guide/threading.md) - [Parallel Requests with Multiprocessing](user-guide/multiprocessing.md) - [Zero Trace and Cache Controls](user-guide/zero-trace.md) +- [JSON Restrictor](user-guide/json-restrictor.md) +- [Request Options](user-guide/request-options.md) ### Examples diff --git a/docs/user_guide/00-getting-started.md b/docs/user_guide/00-getting-started.md index f2d8b37..63e1a43 100644 --- a/docs/user_guide/00-getting-started.md +++ b/docs/user_guide/00-getting-started.md @@ -12,38 +12,48 @@ Use this page to install the package, configure your API key, and make a first G ## Installation -Install with `pip`: +:::{.panel-tabset} + +## pip ```bash pip install serpapi ``` -Or add it to a project with `uv`: +## uv project ```bash uv add serpapi ``` -For an existing virtual environment managed outside a `uv` project: +## uv environment ```bash uv pip install serpapi ``` +::: + Python 3.6 or newer is required by the package. -Set your API key in the environment: +Create or sign in to your SerpApi account, copy your API key from the [dashboard](https://serpapi.com/manage-api-key), and set it in the environment: + +:::{.panel-tabset} + +## macOS / Linux ```bash export SERPAPI_KEY="secret_api_key" ``` -On Windows PowerShell: +## Windows ```powershell -$env:SERPAPI_KEY="secret_api_key" +$env:SERPAPI_KEY = "secret_api_key" ``` +::: + ## First Search ```python @@ -52,13 +62,13 @@ import serpapi client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) -results = client.search({ - "engine": "google", - "q": "coffee shops", - "location": "Austin, Texas", - "hl": "en", - "gl": "us", -}) +results = client.search( + engine="google", + q="coffee shops", + location="Austin, Texas", + hl="en", + gl="us", +) first_result = results["organic_results"][0] print(first_result["title"]) @@ -79,7 +89,7 @@ The example above sends a Google Search request: | `hl` | Interface language. | | `gl` | Country for Google results. | -SerpApi supports many engines and engine-specific parameters. The authoritative list lives in the [SerpApi API documentation](https://serpapi.com/search-api). The [SerpApi Playground](https://serpapi.com/playground) is the fastest way to build a request and copy the final parameters into Python. +SerpApi supports many engines and engine-specific parameters. The authoritative list lives in the [SerpApi API documentation](https://serpapi.com/search-api). The [SerpApi Playground](https://serpapi.com/playground) is the fastest way to build and test a request before moving it into Python. ## When to Use the Client @@ -101,7 +111,7 @@ For production code, prefer a client instance: ```python client = serpapi.Client(api_key="secret_api_key", timeout=20) -results = client.search({"engine": "google", "q": "coffee"}) +results = client.search(engine="google", q="coffee") ``` ## Next Steps diff --git a/docs/user_guide/01-client-usage.md b/docs/user_guide/01-client-usage.md index b9019f8..f77a641 100644 --- a/docs/user_guide/01-client-usage.md +++ b/docs/user_guide/01-client-usage.md @@ -24,22 +24,23 @@ client = serpapi.Client( ## Search -Pass parameters as a dictionary: +Pass engine parameters as keyword arguments: ```python -results = client.search({ - "engine": "google", - "q": "coffee", -}) +results = client.search(engine="google", q="coffee") ``` -Keyword arguments are also supported: +You can also pass a dictionary when parameters are already stored in one: ```python -results = client.search(engine="google", q="coffee") +params = { + "engine": "google", + "q": "coffee", +} +results = client.search(params) ``` -Dictionary parameters and keyword arguments can be mixed. Keyword arguments update the dictionary: +Dictionary parameters and keyword arguments can be mixed. Keyword arguments update the dictionary, which is useful when you start from a saved parameter set: ```python params = {"engine": "google", "q": "coffee"} @@ -98,11 +99,14 @@ Example: ```python results = client.search( - {"engine": "google", "q": "coffee"}, + engine="google", + q="coffee", timeout=10, ) ``` +See [Request Options](request-options.md) for proxy, TLS, certificate, and per-request timeout examples. + ## Response Objects JSON search responses are returned as `serpapi.SerpResults`. It behaves like a dictionary: diff --git a/docs/user_guide/02-parameters-and-engines.md b/docs/user_guide/02-parameters-and-engines.md index b7325de..07af0cd 100644 --- a/docs/user_guide/02-parameters-and-engines.md +++ b/docs/user_guide/02-parameters-and-engines.md @@ -20,19 +20,7 @@ The Search API docs include engines such as Google Search, Google Maps, Google I ## Passing Parameters -Pass engine parameters as a dictionary: - -```python -results = client.search({ - "engine": "google", - "q": "coffee", - "location": "Austin, Texas", - "hl": "en", - "gl": "us", -}) -``` - -Or pass them as keyword arguments: +Pass engine parameters as keyword arguments: ```python results = client.search( @@ -44,6 +32,19 @@ results = client.search( ) ``` +You can also pass a dictionary when you already have parameters in one: + +```python +params = { + "engine": "google", + "q": "coffee", + "location": "Austin, Texas", + "hl": "en", + "gl": "us", +} +results = client.search(params) +``` + ## Common Google Parameters These are common Google Search parameters. Other engines have their own parameter names. @@ -62,6 +63,7 @@ These are common Google Search parameters. Other engines have their own paramete | `async` | Submit a server-side async search. | | `no_cache` | Force SerpApi to fetch fresh results. | | `zero_trace` | Request zero data retention behavior when enabled for your account. | +| `json_restrictor` | Return only selected JSON fields from the API response. | For the full supported list and the exact meaning of each parameter, use the [SerpApi Search API documentation](https://serpapi.com/search-api). diff --git a/docs/user_guide/03-pagination.md b/docs/user_guide/03-pagination.md index d23e1af..4c80bac 100644 --- a/docs/user_guide/03-pagination.md +++ b/docs/user_guide/03-pagination.md @@ -11,11 +11,11 @@ Many SerpApi responses include pagination metadata. SerpApi Python exposes this ## Fetch One More Page ```python -results = client.search({ - "engine": "google", - "q": "coffee", - "location": "Austin, Texas", -}) +results = client.search( + engine="google", + q="coffee", + location="Austin, Texas", +) next_results = results.next_page() @@ -31,11 +31,11 @@ if next_results: Use `yield_pages()` when you want the current page plus following pages: ```python -results = client.search({ - "engine": "google", - "q": "coffee", - "location": "Austin, Texas", -}) +results = client.search( + engine="google", + q="coffee", + location="Austin, Texas", +) for page in results.yield_pages(max_pages=5): for item in page.get("organic_results", []): @@ -50,12 +50,12 @@ Google Search also supports explicit result offsets: ```python for start in [0, 10, 20]: - page = client.search({ - "engine": "google", - "q": "coffee", - "location": "Austin, Texas", - "start": start, - }) + page = client.search( + engine="google", + q="coffee", + location="Austin, Texas", + start=start, + ) print("Offset:", start) for item in page.get("organic_results", []): @@ -83,4 +83,3 @@ for page in results.yield_pages(max_pages=3): ## Pagination Parameters Vary by Engine `start` is common for Google Search, but other engines may use different pagination parameters. Check the relevant engine page in the [SerpApi Search API documentation](https://serpapi.com/search-api) or build the request in the [SerpApi Playground](https://serpapi.com/playground). - diff --git a/docs/user_guide/04-errors-and-timeouts.md b/docs/user_guide/04-errors-and-timeouts.md index d5e3a8c..b6c66f5 100644 --- a/docs/user_guide/04-errors-and-timeouts.md +++ b/docs/user_guide/04-errors-and-timeouts.md @@ -47,7 +47,8 @@ Override it for one request: ```python results = client.search( - {"engine": "google", "q": "coffee"}, + engine="google", + q="coffee", timeout=5, ) ``` @@ -73,4 +74,3 @@ results = client.search(engine="google", q="coffee") if "error" in results: raise RuntimeError(results["error"]) ``` - diff --git a/docs/user_guide/05-account-and-locations.md b/docs/user_guide/05-account-and-locations.md index 63ef89a..94fdc5c 100644 --- a/docs/user_guide/05-account-and-locations.md +++ b/docs/user_guide/05-account-and-locations.md @@ -38,12 +38,11 @@ for location in locations: Then pass the selected location to a search: ```python -results = client.search({ - "engine": "google", - "q": "coffee", - "location": "Austin, Texas, United States", -}) +results = client.search( + engine="google", + q="coffee", + location="Austin, Texas, United States", +) ``` The [SerpApi Locations API documentation](https://serpapi.com/locations-api) has the full endpoint behavior. - diff --git a/docs/user_guide/06-migrating-from-google-search-results.md b/docs/user_guide/06-migrating-from-google-search-results.md index 5093fd6..0b62740 100644 --- a/docs/user_guide/06-migrating-from-google-search-results.md +++ b/docs/user_guide/06-migrating-from-google-search-results.md @@ -4,6 +4,8 @@ description: "Move from the deprecated google-search-results SDK to the recommen guide-section: "Migration" --- +# Migrating from google-search-results + SerpApi has two Python libraries that use similar import names. For new projects and active integrations, use the recommended `serpapi` package. ## Recommended Package @@ -25,10 +27,7 @@ import serpapi YOUR_API_KEY = os.environ["SERPAPI_KEY"] client = serpapi.Client(api_key=YOUR_API_KEY) -results = client.search({ - "engine": "google", - "q": "coffee", -}) +results = client.search(engine="google", q="coffee") print(results) ``` @@ -98,19 +97,19 @@ import os import serpapi client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"]) -results = client.search({ - "engine": "google", - "q": "coffee", - "location": "Austin, Texas", -}) +results = client.search( + engine="google", + q="coffee", + location="Austin, Texas", +) ``` -Search parameters are still passed as a dictionary, so most request parameter usage can move over directly. The main change is the client interface. +Search parameters use the same SerpApi names, so most request parameter usage can move over directly. Prefer passing them as keyword arguments in new code; dictionaries are still accepted when migration code already has parameters in a mapping. ## Migration Checklist - Remove `google-search-results` from requirements and dependency files. - Install `serpapi` with `pip install serpapi`. - Replace `from serpapi import GoogleSearch` with `import serpapi`. -- Replace `GoogleSearch(params).get_dict()` with `serpapi.Client(api_key=...).search(params)`. +- Replace `GoogleSearch(params).get_dict()` with `serpapi.Client(api_key=...).search(engine=..., q=..., ...)`. - Keep using the [SerpApi Playground](https://serpapi.com/playground) and the [SerpApi API documentation](https://serpapi.com/search-api) to confirm engine parameters. diff --git a/docs/user_guide/20-async-search-archive.md b/docs/user_guide/20-async-search-archive.md index 3434179..b333ffc 100644 --- a/docs/user_guide/20-async-search-archive.md +++ b/docs/user_guide/20-async-search-archive.md @@ -11,12 +11,12 @@ SerpApi supports server-side async searches. This is different from Python `asyn ## Submit an Async Search ```python -submitted = client.search({ - "engine": "google", - "q": "coffee", - "location": "Austin, Texas", - "async": True, -}) +submitted = client.search( + engine="google", + q="coffee", + location="Austin, Texas", + **{"async": True}, +) search_id = submitted["search_metadata"]["id"] print(search_id) @@ -54,4 +54,3 @@ while True: Async searches are useful when you want to submit work quickly and collect results later, especially from job queues, scheduled workers, or data pipelines. Do not combine `async=true` with `no_cache=true`. Use the [SerpApi Search Archive API documentation](https://serpapi.com/search-archive-api) for retention windows and status details. - diff --git a/docs/user_guide/21-threading.md b/docs/user_guide/21-threading.md index 2f127ec..4ddf2e5 100644 --- a/docs/user_guide/21-threading.md +++ b/docs/user_guide/21-threading.md @@ -22,12 +22,12 @@ API_KEY = os.environ["SERPAPI_KEY"] def search_country(country): client = serpapi.Client(api_key=API_KEY, timeout=20) - results = client.search({ - "engine": "google", - "q": "best coffee beans", - "gl": country, - "hl": "en", - }) + results = client.search( + engine="google", + q="best coffee beans", + gl=country, + hl="en", + ) return country, results.get("organic_results", []) @@ -69,4 +69,3 @@ with ThreadPoolExecutor(max_workers=5) as executor: print("Finished:", country, len(organic_results)) ``` - diff --git a/docs/user_guide/22-multiprocessing.md b/docs/user_guide/22-multiprocessing.md index 0ea1827..f67ba9a 100644 --- a/docs/user_guide/22-multiprocessing.md +++ b/docs/user_guide/22-multiprocessing.md @@ -19,12 +19,12 @@ from concurrent.futures import ProcessPoolExecutor, as_completed def search_country(country): client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) - results = client.search({ - "engine": "google", - "q": "best coffee beans", - "gl": country, - "hl": "en", - }) + results = client.search( + engine="google", + q="best coffee beans", + gl=country, + hl="en", + ) organic_results = results.get("organic_results", []) return { @@ -61,14 +61,13 @@ For Google Search, independent `start` offsets can be distributed across workers ```python def search_offset(start): client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) - results = client.search({ - "engine": "google", - "q": "coffee", - "location": "Austin, Texas", - "start": start, - }) + results = client.search( + engine="google", + q="coffee", + location="Austin, Texas", + start=start, + ) return results.get("organic_results", []) ``` Check the relevant engine docs before parallelizing offsets because pagination parameters vary by engine. - diff --git a/docs/user_guide/23-zero-trace.md b/docs/user_guide/23-zero-trace.md index 2a197bd..8757cf2 100644 --- a/docs/user_guide/23-zero-trace.md +++ b/docs/user_guide/23-zero-trace.md @@ -13,12 +13,12 @@ SerpApi exposes request parameters for data retention and cache behavior. The Py When enabled for your account, pass `zero_trace=true` to request zero data retention behavior for a search: ```python -results = client.search({ - "engine": "google", - "q": "coffee", - "location": "Austin, Texas", - "zero_trace": True, -}) +results = client.search( + engine="google", + q="coffee", + location="Austin, Texas", + zero_trace=True, +) ``` Use this for workloads that require stricter data handling. Zero trace availability and behavior are account-dependent, so confirm details in the [SerpApi Search API documentation](https://serpapi.com/search-api) and your plan. @@ -28,11 +28,11 @@ Use this for workloads that require stricter data handling. Zero trace availabil Use `no_cache=true` when you want SerpApi to fetch fresh results instead of using cached results: ```python -results = client.search({ - "engine": "google", - "q": "coffee", - "no_cache": True, -}) +results = client.search( + engine="google", + q="coffee", + no_cache=True, +) ``` `no_cache=true` and `async=true` should not be used together. @@ -46,4 +46,3 @@ results = client.search({ | Zero data retention behavior | `zero_trace=true` when enabled for your account | See [Async Search Archive](async-search-archive.md) for async search usage. - diff --git a/docs/user_guide/24-json-restrictor.md b/docs/user_guide/24-json-restrictor.md new file mode 100644 index 0000000..a6a1470 --- /dev/null +++ b/docs/user_guide/24-json-restrictor.md @@ -0,0 +1,136 @@ +--- +title: "JSON Restrictor" +description: "Use json_restrictor to request only the JSON fields your workflow needs." +guide-section: "Advanced Usage" +--- + +# JSON Restrictor + +JSON Restrictor is a SerpApi request parameter that limits the JSON response to selected fields before the response is returned to your Python code. Use it for AI workflows, RAG ingestion, reports, and production jobs that only need a narrow subset of a large engine response. + +SerpApi supports `json_restrictor` for all engines. The Python client forwards the selector string with the rest of your search parameters. See the [SerpApi JSON Restrictor documentation](https://serpapi.com/json-restrictor) for the full selector reference. + +## Select a Top-Level Section + +Return only `organic_results`: + +```python +results = client.search( + engine="google", + q="coffee", + location="Austin, Texas", + json_restrictor="organic_results", +) + +for result in results.get("organic_results", []): + print(result.get("title")) +``` + +## Select Fields From Each Result + +Use `[]` to apply a selector to every item in an array. Use a subquery to keep multiple fields from each object: + +```python +results = client.search( + engine="google", + q="coffee", + location="Austin, Texas", + json_restrictor="organic_results[].{title, link, snippet}", +) + +for result in results.get("organic_results", []): + print(result["title"], result["link"]) +``` + +The response still uses the normal SerpApi JSON shape, but each matching result only contains the selected fields. + +## Select One Item or a Slice + +Use an array index when you only need one item: + +```python +results = client.search( + engine="google", + q="coffee", + json_restrictor="organic_results[0]", +) + +first_result = results["organic_results"][0] +print(first_result["title"]) +``` + +Use a half-open array slice when you need a fixed number of items: + +```python +results = client.search( + engine="google", + q="coffee", + json_restrictor="organic_results[0:3]", +) +``` + +`organic_results[0:3]` keeps indexes `0`, `1`, and `2`. + +## Combine Multiple Selectors + +Separate selectors with commas when your workflow needs more than one part of the response: + +```python +results = client.search( + engine="google", + q="coffee", + location="Austin, Texas", + json_restrictor="local_map, organic_results[0]", +) + +print(results.get("local_map", {}).get("gps_coordinates")) +print(results["organic_results"][0]["title"]) +``` + +## Nested Fields + +Selectors can keep nested values while dropping the surrounding fields you do not use: + +```python +results = client.search( + engine="google", + q="coffee", + json_restrictor="organic_results[].{title, sitelinks.inline[].link}", +) +``` + +This is useful when downstream code only needs a compact list of titles and related links. + +## Common Selector Patterns + +| Need | `json_restrictor` | +| --- | --- | +| One top-level section | `organic_results` | +| First organic result | `organic_results[0]` | +| First three organic results | `organic_results[0:3]` | +| One field from every result | `organic_results[].title` | +| Multiple fields from every result | `organic_results[].{title, snippet}` | +| Nested fields | `organic_results[].{title, sitelinks.inline[].link}` | +| Multiple response sections | `local_map, organic_results[0]` | + +## Practical Guidance + +- Start without `json_restrictor`, inspect the response shape, then add selectors for the fields your workflow actually needs. +- Keep selectors close to the engine response you tested. Different engines expose different result sections and field names. +- If you plan to call `next_page()` or `yield_pages()`, include `serpapi_pagination` in the selector so the client can find the next-page URL. +- If you need a search ID, status, or archive metadata, include `search_metadata`. + +For paginated collection, keep both the fields you want and the pagination metadata: + +```python +results = client.search( + engine="google", + q="coffee", + location="Austin, Texas", + json_restrictor="organic_results[].{title, link}, serpapi_pagination", +) + +for page in results.yield_pages(max_pages=3): + for result in page.get("organic_results", []): + print(result["title"], result["link"]) +``` diff --git a/docs/user_guide/25-request-options.md b/docs/user_guide/25-request-options.md new file mode 100644 index 0000000..c662feb --- /dev/null +++ b/docs/user_guide/25-request-options.md @@ -0,0 +1,134 @@ +--- +title: "Request Options" +description: "Pass requests library options such as timeout, proxies, verify, stream, and cert." +guide-section: "Advanced Usage" +--- + +# Request Options + +SerpApi Python separates SerpApi query parameters from options passed to the underlying `requests` library. + +SerpApi parameters, such as `engine`, `q`, `location`, `hl`, `gl`, `no_cache`, and `json_restrictor`, are sent to SerpApi as API request parameters. Request options, such as `timeout` and `proxies`, configure the local HTTP request made by the Python client. + +## Supported Request Options + +The client passes these keyword arguments to `requests.Session.request()`: + +| Option | Use | +| --- | --- | +| `timeout` | Override the client timeout for one request. | +| `proxies` | Send the request through HTTP or HTTPS proxies. | +| `verify` | Control TLS certificate verification. | +| `stream` | Ask `requests` to stream the HTTP response. | +| `cert` | Use a client certificate. | + +These options are supported by `search()`, `search_archive()`, `account()`, and `locations()`. + +## Per-Request Timeout + +Set a default timeout when creating the client: + +```python +client = serpapi.Client(api_key="secret_api_key", timeout=20) +``` + +Override it for a single request: + +```python +results = client.search( + engine="google", + q="coffee", + timeout=5, +) +``` + +`timeout=5` is passed to `requests`; it is not sent to SerpApi as a search parameter. + +## Proxies + +Pass a `requests`-style proxies dictionary: + +```python +results = client.search( + engine="google", + q="coffee", + proxies={ + "https": "http://proxy.example.com:8080", + }, +) +``` + +Keep proxy credentials in environment variables or your secret manager rather than hardcoding them in source files. + +## TLS Verification + +By default, `requests` verifies TLS certificates. You can pass a custom CA bundle path: + +```python +results = client.search( + engine="google", + q="coffee", + verify="/path/to/ca-bundle.pem", +) +``` + +Only disable verification for controlled local debugging: + +```python +results = client.search( + engine="google", + q="coffee", + verify=False, +) +``` + +Do not use `verify=False` in production code. + +## Client Certificates + +Pass a client certificate path, or a `(cert, key)` tuple, using `cert`: + +```python +results = client.search( + engine="google", + q="coffee", + cert=("/path/to/client-cert.pem", "/path/to/client-key.pem"), +) +``` + +## Combining Search Parameters and Request Options + +Request options can be used alongside normal SerpApi parameters: + +```python +results = client.search( + engine="google", + q="coffee", + location="Austin, Texas", + hl="en", + gl="us", + json_restrictor="organic_results[].{title, link}", + timeout=10, +) +``` + +The client handles this in two steps: + +- `timeout`, `proxies`, `verify`, `stream`, and `cert` are passed to `requests`. +- All remaining keyword arguments are sent to SerpApi as API parameters. + +If you pass a parameter dictionary and keyword arguments together, the remaining keyword arguments update the dictionary before the request is sent: + +```python +params = {"engine": "google", "q": "coffee"} +results = client.search(params, location="Austin, Texas", timeout=10) +``` + +In this example, `location` is added to the SerpApi request parameters, while `timeout` is passed to `requests`. + +When you want to keep the original dictionary unchanged, pass a copy: + +```python +params = {"engine": "google", "q": "coffee"} +results = client.search(params.copy(), location="Austin, Texas") +``` diff --git a/great-docs.yml b/great-docs.yml index cb76f05..3f7b4ce 100644 --- a/great-docs.yml +++ b/great-docs.yml @@ -20,12 +20,6 @@ authors: email: support@serpapi.com homepage: https://serpapi.com -funding: - name: SerpApi, LLC - roles: - - Copyright holder - homepage: https://serpapi.com - logo: light: assets/serpapi-logo.svg dark: assets/serpapi-logo-dark.svg @@ -48,14 +42,16 @@ sections: - title: Examples dir: docs/examples markdown_pages: true +pre_render: + - scripts/build_llms_txt.py dark_mode_toggle: true accent_color: light: "#377FEA" - dark: "#6937EA" + dark: "#38BDF8" navbar_color: light: "#FFFFFF" - dark: "#2F3030" + dark: "#111827" site: theme: flatly diff --git a/scripts/build_llms_txt.py b/scripts/build_llms_txt.py new file mode 100755 index 0000000..23ead81 --- /dev/null +++ b/scripts/build_llms_txt.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +"""Build a useful compact llms.txt for the docs site. + +Great Docs generates llms.txt from the API reference only. For SerpApi Python, +the compact LLM entry point is more useful when it includes the homepage and +getting-started flow before the reference links. +""" + +from __future__ import annotations + +import posixpath +import re +from pathlib import Path + + +def find_paths() -> tuple[Path, Path]: + script_path = Path(__file__).resolve() + + if script_path.parent.name == "scripts" and script_path.parent.parent.name == "great-docs": + build_dir = script_path.parent.parent + repo_root = build_dir.parent + else: + repo_root = script_path.parent.parent + build_dir = repo_root / "great-docs" + + return repo_root, build_dir + + +def read_text(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def strip_frontmatter(text: str) -> str: + lines = text.splitlines() + output: list[str] = [] + index = 0 + + while index < len(lines): + if lines[index].strip() == "---": + end = index + 1 + while end < len(lines) and lines[end].strip() != "---": + end += 1 + if end < len(lines): + index = end + 1 + continue + + output.append(lines[index]) + index += 1 + + return "\n".join(output).strip() + + +def clean_markdown(text: str) -> str: + text = strip_frontmatter(text) + text = re.sub(r"]*>.*?\s*", "", text, flags=re.DOTALL | re.IGNORECASE) + text = re.sub( + r'
.*?
\s*', + "", + text, + flags=re.DOTALL | re.IGNORECASE, + ) + text = re.sub(r"^:::\s*\{[^}]+\}\s*$", "", text, flags=re.MULTILINE) + text = re.sub(r"^:::\s*$", "", text, flags=re.MULTILINE) + text = re.sub(r"\n{3,}", "\n\n", text) + return text.strip() + + +def demote_headings(text: str) -> str: + return re.sub( + r"^(#{1,5})(\s+)", + lambda match: f"#{match.group(1)}{match.group(2)}", + text, + flags=re.MULTILINE, + ) + + +def site_url_from_config(config_path: Path) -> str: + if not config_path.exists(): + return "" + + match = re.search(r"^site_url:\s*(.+?)\s*$", read_text(config_path), flags=re.MULTILINE) + if not match: + return "" + + site_url = match.group(1).strip().strip('"').strip("'") + return site_url if site_url.endswith("/") else f"{site_url}/" + + +def normalize_links(text: str, *, site_url: str, base_path: str = "") -> str: + def replace(match: re.Match[str]) -> str: + target = match.group(1).strip() + if re.match(r"^(?:[a-z][a-z0-9+.-]*:|#)", target, flags=re.IGNORECASE): + return f"]({target})" + + path = target[2:] if target.startswith("./") else target + joined = posixpath.normpath(posixpath.join(base_path, path)) + if joined == ".": + joined = "" + + joined = re.sub(r"\.q?md($|#)", r".html\1", joined) + return f"]({site_url}{joined})" if site_url else f"]({joined})" + + return re.sub(r"\]\(([^)]+)\)", replace, text) + + +def api_reference_from_default(default_llms: str) -> str: + marker = "### API Reference" + if marker not in default_llms: + return "" + + api_reference = default_llms.split(marker, 1)[1].strip() + api_reference = re.sub(r"^####(\s+)", r"###\1", api_reference, flags=re.MULTILINE) + return api_reference + + +def main() -> None: + repo_root, build_dir = find_paths() + site_url = site_url_from_config(repo_root / "great-docs.yml") + + index = clean_markdown(read_text(repo_root / "docs" / "index.md")) + getting_started = clean_markdown( + read_text(repo_root / "docs" / "user_guide" / "00-getting-started.md") + ) + getting_started = re.sub(r"^#\s+Getting Started\s*", "", getting_started).strip() + + index = demote_headings(normalize_links(index, site_url=site_url)) + getting_started = demote_headings( + normalize_links(getting_started, site_url=site_url, base_path="user-guide") + ) + + api_reference = api_reference_from_default(read_text(build_dir / "llms.txt")) + + lines = [ + "# SerpApi Python", + "", + "> Official Python client for SerpApi search data in applications, AI workflows, RAG, and data pipelines.", + "", + "## Homepage", + "", + index, + "", + "## Getting Started", + "", + getting_started, + "", + ] + + if api_reference: + lines.extend(["## API Reference", "", api_reference, ""]) + + if site_url: + lines.extend( + [ + "## More LLM Context", + "", + f"- [Full LLM documentation]({site_url}llms-full.txt)", + f"- [Documentation homepage]({site_url}index.html)", + "", + ] + ) + + output = "\n".join(lines).strip() + "\n" + (build_dir / "llms.txt").write_text(output, encoding="utf-8") + print("Updated llms.txt with homepage and getting-started content") + + +if __name__ == "__main__": + main() diff --git a/serpapi/core.py b/serpapi/core.py index 007a993..6ddbc88 100644 --- a/serpapi/core.py +++ b/serpapi/core.py @@ -6,22 +6,11 @@ class Client(HTTPClient): """A class that handles API requests to SerpApi in a user-friendly manner. + Store your API key in an environment variable, then create a client with + ``serpapi.Client(api_key=os.environ["SERPAPI_KEY"])``. + :param api_key: The API Key to use for SerpApi.com. :param timeout: The default timeout to use for requests. - - Please provide ``api_key`` when instantiating this class. We recommend storing this in an environment variable, like so: - - .. code-block:: bash - - $ export SERPAPI_KEY=YOUR_API_KEY - - .. code-block:: python - - import os - import serpapi - - serpapi = serpapi.Client(api_key=os.environ["SERPAPI_KEY"]) - """ DASHBOARD_URL = "https://serpapi.com/dashboard" @@ -33,22 +22,15 @@ def __repr__(self): return "" def search(self, params: dict = None, **kwargs): - """Fetch a page of results from SerpApi. Returns a :class:`SerpResults ` object, or unicode text (*e.g.* if ``'output': 'html'`` was passed). - - Dictionary parameters and keyword parameters are both supported: - - .. code-block:: python + """Fetch a page of results from SerpApi. - >>> import types - >>> client = Client(api_key="secret_api_key") - >>> client.request = lambda method, path, params, **kwargs: types.SimpleNamespace(json=lambda: {"search_parameters": params}, text="") - >>> client.search({"engine": "google", "q": "Coffee"})["search_parameters"]["q"] - 'Coffee' - >>> client.search(engine="google", q="Coffee")["search_parameters"]["engine"] - 'google' + Returns a ``serpapi.SerpResults`` object for JSON responses, or text + when ``output="html"`` is requested. Prefer passing SerpApi engine + parameters as keyword arguments. A parameter dictionary is also accepted + when your code already has parameters in a mapping. - :param params: SerpApi search parameters such as ``engine``, ``q``, ``location``, and ``output``. + :param params: Optional mapping of SerpApi search parameters such as ``engine``, ``q``, ``location``, and ``output``. :param kwargs: Additional SerpApi parameters or request options. ``timeout``, ``proxies``, ``verify``, ``stream``, and ``cert`` are passed to the underlying HTTP request. diff --git a/serpapi/models.py b/serpapi/models.py index 6af044a..c7ca647 100644 --- a/serpapi/models.py +++ b/serpapi/models.py @@ -10,12 +10,6 @@ class SerpResults(UserDict): """A dictionary-like object that represents the results of a SerpApi request. - .. code-block:: python - - >>> search = SerpResults({"search_metadata": {"id": "abc", "status": "Success"}}, client=None) - >>> print(search["search_metadata"].keys()) - dict_keys(['id', 'status']) - An instance of this class is returned if the response is a valid JSON object. It can be used like a dictionary, but also has some additional methods. """ @@ -39,13 +33,9 @@ def __repr__(self): def as_dict(self): """Returns the data as a standard Python dictionary. - This can be useful when using ``json.dumps(search)``, for example. - - .. code-block:: python - >>> search = SerpResults({"answer": 42}, client=None) - >>> search.as_dict() - {'answer': 42} + This can be useful when passing results to libraries that expect a + plain ``dict``. """ return self.data.copy() From 2d5109713843722a228e7ed504b48cd16f7a143b Mon Sep 17 00:00:00 2001 From: Adarsh Divakaran Date: Fri, 10 Jul 2026 23:04:58 +0530 Subject: [PATCH 5/7] feat: move to static docs site using great-docs package --- assets/serpapi-theme.css | 15 ++++++++++ docs/examples/07-google-ai-overview.md | 5 ++-- docs/index.md | 23 +++++++-------- docs/user_guide/01-client-usage.md | 5 ++-- docs/user_guide/25-request-options.md | 4 +++ great-docs.yml | 39 ++++++++++++++++++++++++++ pyproject.toml | 2 +- 7 files changed, 75 insertions(+), 18 deletions(-) diff --git a/assets/serpapi-theme.css b/assets/serpapi-theme.css index 9a3138d..a405892 100644 --- a/assets/serpapi-theme.css +++ b/assets/serpapi-theme.css @@ -458,6 +458,21 @@ td code:not(.sourceCode) { text-decoration: none; } +#quarto-sidebar .sidebar-link { + min-width: 0; +} + +#quarto-sidebar .sidebar-link .menu-text { + min-inline-size: min-content; + overflow-wrap: normal; + word-break: keep-all; + hyphens: none; +} + +body:not(.gd-ref-sidebar) #quarto-sidebar .sidebar-link .menu-text wbr { + display: none; +} + .sidebar .sidebar-link:hover, .sidebar .sidebar-link:focus { background: rgba(47, 115, 217, 0.08); diff --git a/docs/examples/07-google-ai-overview.md b/docs/examples/07-google-ai-overview.md index 2cd1419..a21bc57 100644 --- a/docs/examples/07-google-ai-overview.md +++ b/docs/examples/07-google-ai-overview.md @@ -27,10 +27,11 @@ search = client.search( ai_overview = search.get("ai_overview", {}) if ai_overview.get("page_token"): - ai_overview = client.search( + response = client.search( engine="google_ai_overview", page_token=ai_overview["page_token"], ) + ai_overview = response.get("ai_overview", {}) for block in ai_overview.get("text_blocks", [])[:3]: print(block.get("snippet") or block.get("text")) @@ -38,6 +39,6 @@ for block in ai_overview.get("text_blocks", [])[:3]: ## What to Read -Use `text_blocks` for the generated answer and `references` for cited sources when present. If the first Google Search response already includes the AI Overview content, you can read it directly without the second request. +Use `ai_overview.text_blocks` for the generated answer and `ai_overview.references` for cited sources when present. If the first Google Search response already includes the AI Overview content, you can read it directly without the second request. See the [Google Search API AI Overview docs](https://serpapi.com/search-api#api-examples-results-for-ai-overview) and the [Google AI Overview API documentation](https://serpapi.com/google-ai-overview-api). Use the [SerpApi Playground](https://serpapi.com/playground) to find queries that currently return AI Overview data. diff --git a/docs/index.md b/docs/index.md index d70297c..801134d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -81,13 +81,13 @@ Choose the path that matches what you are building. Start with setup and a first - Existing `google-search-results` users: follow the [Migration Guide](user-guide/migrating-from-google-search-results.md). - Engine setup: use [Parameters and Engines](user-guide/parameters-and-engines.md) with the [SerpApi Playground](https://serpapi.com/playground). - Production workflows: review [pagination](user-guide/pagination.md), [timeouts and errors](user-guide/errors-and-timeouts.md), [request options](user-guide/request-options.md), [account/location helpers](user-guide/account-and-locations.md), [async search archive](user-guide/async-search-archive.md), [JSON Restrictor](user-guide/json-restrictor.md), and [zero trace controls](user-guide/zero-trace.md). -- [Examples](docs/examples/google-across-countries.md): browse focused recipes for search, maps, shopping, flights, finance, trends, AI Overview, news, jobs, and YouTube. +- [Examples](docs/examples/index.qmd): browse focused recipes grouped by search engines, AI answers, local/maps, shopping, travel, finance, trends, jobs, events, media, apps, and research. @@ -119,17 +119,14 @@ Choose the path that matches what you are building. Start with setup and a first ### Examples -- [Google Across Countries](docs/examples/google-across-countries.md) -- [Bing Search](docs/examples/bing-search.md) -- [Google Maps Local Business](docs/examples/google-maps-local-business.md) -- [Google Shopping Products](docs/examples/google-shopping-products.md) -- [Google Flights Travel](docs/examples/google-flights-travel.md) -- [Google Finance Market Data](docs/examples/google-finance-market-data.md) -- [Google Trends Demand](docs/examples/google-trends-demand.md) -- [Google AI Overview](docs/examples/google-ai-overview.md) -- [Google News Monitoring](docs/examples/google-news-monitoring.md) -- [Google Jobs Listings](docs/examples/google-jobs-listings.md) -- [YouTube Video Search](docs/examples/youtube-video-search.md) +- [Examples Overview](docs/examples/index.qmd) +- Search Engines: [Google](docs/examples/google-across-countries.md), [Bing](docs/examples/bing-search.md), [DuckDuckGo](docs/examples/duckduckgo-search.md), [Baidu](docs/examples/baidu-search.md) +- AI Answers: [Google AI Overview](docs/examples/google-ai-overview.md) +- Local and Maps: [Google Maps](docs/examples/google-maps-local-business.md), [Google Local Services](docs/examples/google-local-services.md) +- Shopping and Marketplaces: [Google Shopping](docs/examples/google-shopping-products.md), [Amazon](docs/examples/amazon-product-search.md), [Walmart](docs/examples/walmart-product-search.md), [eBay](docs/examples/ebay-product-listings.md), [Home Depot](docs/examples/home-depot-product-search.md) +- Travel and Hospitality: [Google Flights](docs/examples/google-flights-travel.md), [Tripadvisor](docs/examples/tripadvisor-travel-search.md) +- Finance, Trends, Jobs, and Events: [Google Finance](docs/examples/google-finance-market-data.md), [Google Trends](docs/examples/google-trends-demand.md), [Google Jobs](docs/examples/google-jobs-listings.md), [Google Events](docs/examples/google-events-discovery.md) +- Media, Apps, and Research: [YouTube](docs/examples/youtube-video-search.md), [Google Images](docs/examples/google-images-search.md), [Google Scholar](docs/examples/google-scholar-research.md), [Google Play](docs/examples/google-play-store-apps.md) ::: diff --git a/docs/user_guide/01-client-usage.md b/docs/user_guide/01-client-usage.md index f77a641..7cbd6ea 100644 --- a/docs/user_guide/01-client-usage.md +++ b/docs/user_guide/01-client-usage.md @@ -141,8 +141,9 @@ next_results = results.next_page() To iterate through all available pages, use `yield_pages()`: ```python -for page in results.yield_pages(max_pages=10): - print(page["search_metadata"].get("page_number")) +for page_number, page in enumerate(results.yield_pages(max_pages=10), start=1): + current = page.get("serpapi_pagination", {}).get("current", page_number) + print(current) ``` See [Pagination](pagination.md) for fuller pagination examples and Google `start` offsets. diff --git a/docs/user_guide/25-request-options.md b/docs/user_guide/25-request-options.md index c662feb..862954a 100644 --- a/docs/user_guide/25-request-options.md +++ b/docs/user_guide/25-request-options.md @@ -48,6 +48,7 @@ results = client.search( Pass a `requests`-style proxies dictionary: + ```python results = client.search( engine="google", @@ -64,6 +65,7 @@ Keep proxy credentials in environment variables or your secret manager rather th By default, `requests` verifies TLS certificates. You can pass a custom CA bundle path: + ```python results = client.search( engine="google", @@ -74,6 +76,7 @@ results = client.search( Only disable verification for controlled local debugging: + ```python results = client.search( engine="google", @@ -88,6 +91,7 @@ Do not use `verify=False` in production code. Pass a client certificate path, or a `(cert, key)` tuple, using `cert`: + ```python results = client.search( engine="google", diff --git a/great-docs.yml b/great-docs.yml index 3f7b4ce..65fc7ee 100644 --- a/great-docs.yml +++ b/great-docs.yml @@ -41,6 +41,45 @@ user_guide: docs/user_guide sections: - title: Examples dir: docs/examples + index: true + sidebar_groups: + - section: Search Engines + contents: + - google-across-countries.md + - bing-search.md + - duckduckgo-search.md + - baidu-search.md + - section: AI Answers + contents: + - google-ai-overview.md + - section: Local and Maps + contents: + - google-maps-local-business.md + - google-local-services.md + - section: Shopping and Marketplaces + contents: + - google-shopping-products.md + - google-shopping-price-monitoring.md + - amazon-product-search.md + - walmart-product-search.md + - ebay-product-listings.md + - home-depot-product-search.md + - section: Travel and Hospitality + contents: + - google-flights-travel.md + - tripadvisor-travel-search.md + - section: Finance, Trends, Jobs, and Events + contents: + - google-finance-market-data.md + - google-trends-demand.md + - google-jobs-listings.md + - google-events-discovery.md + - section: Media, Apps, and Research + contents: + - youtube-video-search.md + - google-images-search.md + - google-scholar-research.md + - google-play-store-apps.md markdown_pages: true pre_render: - scripts/build_llms_txt.py diff --git a/pyproject.toml b/pyproject.toml index af808fb..eee4743 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,7 @@ docs = ["great-docs>=0.12; python_version >= '3.11'"] [project.urls] Homepage = "https://github.com/serpapi/serpapi-python" Source = "https://github.com/serpapi/serpapi-python" -Documentation = "https://serpapi.github.io/serpapi-python/" +Documentation = "https://serpapi-python.readthedocs.io/en/latest/" [tool.setuptools.dynamic] version = { attr = "serpapi.__version__.__version__" } From 9ac9d4df35ecc1a217b9f06a43594c71a9fbcca8 Mon Sep 17 00:00:00 2001 From: Adarsh Divakaran Date: Fri, 10 Jul 2026 23:05:12 +0530 Subject: [PATCH 6/7] feat: move to static docs site using great-docs package --- docs/examples/11-amazon-product-search.md | 35 ++++ docs/examples/12-walmart-product-search.md | 36 ++++ docs/examples/13-tripadvisor-travel-search.md | 35 ++++ .../14-google-shopping-price-monitoring.md | 41 ++++ docs/examples/15-duckduckgo-search.md | 33 +++ docs/examples/16-baidu-search.md | 33 +++ docs/examples/17-google-local-services.md | 35 ++++ docs/examples/18-ebay-product-listings.md | 34 ++++ docs/examples/19-home-depot-product-search.md | 34 ++++ docs/examples/20-google-events-discovery.md | 34 ++++ docs/examples/21-google-images-search.md | 35 ++++ docs/examples/22-google-scholar-research.md | 34 ++++ docs/examples/23-google-play-store-apps.md | 36 ++++ docs/examples/index.qmd | 188 ++++++++++++++++++ 14 files changed, 643 insertions(+) create mode 100644 docs/examples/11-amazon-product-search.md create mode 100644 docs/examples/12-walmart-product-search.md create mode 100644 docs/examples/13-tripadvisor-travel-search.md create mode 100644 docs/examples/14-google-shopping-price-monitoring.md create mode 100644 docs/examples/15-duckduckgo-search.md create mode 100644 docs/examples/16-baidu-search.md create mode 100644 docs/examples/17-google-local-services.md create mode 100644 docs/examples/18-ebay-product-listings.md create mode 100644 docs/examples/19-home-depot-product-search.md create mode 100644 docs/examples/20-google-events-discovery.md create mode 100644 docs/examples/21-google-images-search.md create mode 100644 docs/examples/22-google-scholar-research.md create mode 100644 docs/examples/23-google-play-store-apps.md create mode 100644 docs/examples/index.qmd diff --git a/docs/examples/11-amazon-product-search.md b/docs/examples/11-amazon-product-search.md new file mode 100644 index 0000000..21b74cd --- /dev/null +++ b/docs/examples/11-amazon-product-search.md @@ -0,0 +1,35 @@ +--- +title: "Amazon Product Search" +description: "Search Amazon products and read price, rating, delivery, and product identifiers." +--- + +# Amazon Product Search + +Use the Amazon Search API when you need product listings, prices, ratings, delivery details, sponsored products, or Amazon product identifiers. The Amazon engine uses `k` for the search query. + +## Search Products + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search( + engine="amazon", + k="coffee grinder", + amazon_domain="amazon.com", +) + +for product in results.get("organic_results", [])[:5]: + print(product.get("title")) + print(product.get("price"), product.get("rating"), product.get("reviews")) + print(product.get("asin"), product.get("link_clean")) +``` + +## What to Read + +Start with `organic_results` for product cards. Useful fields include `title`, `asin`, `price`, `extracted_price`, `rating`, `reviews`, `thumbnail`, `delivery`, `prime`, `link_clean`, and `serpapi_link`. Some searches also return `product_ads`, `featured_products`, `video_results`, `filters`, and `related_searches`. + +For localization, sorting, category nodes, and Amazon-specific filters, see the [Amazon Search API documentation](https://serpapi.com/amazon-search-api). Use the [SerpApi Playground](https://serpapi.com/playground) to inspect result shapes for the product category you care about. diff --git a/docs/examples/12-walmart-product-search.md b/docs/examples/12-walmart-product-search.md new file mode 100644 index 0000000..8f24d0b --- /dev/null +++ b/docs/examples/12-walmart-product-search.md @@ -0,0 +1,36 @@ +--- +title: "Walmart Product Search" +description: "Search Walmart products and read pricing, reviews, shipping, and product IDs." +--- + +# Walmart Product Search + +Use the Walmart Search API for product search, price monitoring, catalog enrichment, and availability checks. Walmart uses `query` instead of `q`. + +## Search Products + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search( + engine="walmart", + query="coffee maker", + walmart_domain="walmart.com", +) + +for product in results.get("organic_results", [])[:5]: + offer = product.get("primary_offer", {}) + print(product.get("title")) + print(offer.get("offer_price"), product.get("rating"), product.get("reviews")) + print(product.get("us_item_id"), product.get("product_page_url")) +``` + +## What to Read + +Start with `organic_results`. Useful fields include `title`, `us_item_id`, `product_id`, `rating`, `reviews`, `seller_name`, `primary_offer.offer_price`, `price_per_unit`, `out_of_stock`, `product_page_url`, and `serpapi_product_page_url`. + +Use `sort`, `min_price`, `max_price`, `store_id`, and `facet` when you need more targeted product monitoring. See the [Walmart Search API documentation](https://serpapi.com/walmart-search-api) for supported filters and pagination behavior. diff --git a/docs/examples/13-tripadvisor-travel-search.md b/docs/examples/13-tripadvisor-travel-search.md new file mode 100644 index 0000000..c45ee96 --- /dev/null +++ b/docs/examples/13-tripadvisor-travel-search.md @@ -0,0 +1,35 @@ +--- +title: "Tripadvisor Travel Search" +description: "Search Tripadvisor places, hotels, restaurants, and attractions." +--- + +# Tripadvisor Travel Search + +Use the Tripadvisor Search API when you need destination, hotel, restaurant, attraction, or forum search results from Tripadvisor. The `ssrc` parameter narrows the search to a result type such as hotels, restaurants, or things to do. + +## Search Places + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search( + engine="tripadvisor", + q="Rome", + ssrc="h", +) + +for place in results.get("places", [])[:5]: + print(place.get("title")) + print(place.get("place_type"), place.get("location")) + print(place.get("link")) +``` + +## What to Read + +Start with `places`. Useful fields include `title`, `place_type`, `place_id`, `location`, `description`, `thumbnail`, `link`, and `serpapi_link`. + +Use `ssrc` to focus the search: `h` for hotels, `r` for restaurants, `A` for things to do, `g` for destinations, and `a` for all results. See the [Tripadvisor Search API documentation](https://serpapi.com/tripadvisor-search-api) for the current filter and pagination options. diff --git a/docs/examples/14-google-shopping-price-monitoring.md b/docs/examples/14-google-shopping-price-monitoring.md new file mode 100644 index 0000000..c330c91 --- /dev/null +++ b/docs/examples/14-google-shopping-price-monitoring.md @@ -0,0 +1,41 @@ +--- +title: "Google Shopping Price Monitoring" +description: "Filter Google Shopping results by price and read product cards for monitoring workflows." +--- + +# Google Shopping Price Monitoring + +Use the Google Shopping API when you need product cards from many merchants. For price monitoring, combine query, market, price filters, and selected result fields. + +## Search Filtered Products + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search( + engine="google_shopping", + q="espresso machine", + location="Austin, Texas", + gl="us", + hl="en", + min_price=100, + max_price=800, + sort_by=1, + json_restrictor="shopping_results[].{title, price, source, rating, reviews, link}", +) + +for product in results.get("shopping_results", [])[:5]: + print(product.get("title")) + print(product.get("price"), product.get("source")) + print(product.get("rating"), product.get("reviews")) +``` + +## What to Read + +Start with `shopping_results`. Useful fields include `title`, `product_id`, `price`, `extracted_price`, `source`, `rating`, `reviews`, `thumbnail`, `delivery`, `product_link`, and `serpapi_immersive_product_api`. + +Use `min_price`, `max_price`, `sort_by`, `free_shipping`, and `on_sale` for monitoring workflows. For pagination, prefer `serpapi_pagination.next` over hand-built offsets when it is present. See the [Google Shopping API documentation](https://serpapi.com/google-shopping-api) for the full parameter set. diff --git a/docs/examples/15-duckduckgo-search.md b/docs/examples/15-duckduckgo-search.md new file mode 100644 index 0000000..275edfe --- /dev/null +++ b/docs/examples/15-duckduckgo-search.md @@ -0,0 +1,33 @@ +--- +title: "DuckDuckGo Search" +description: "Run a DuckDuckGo search and read organic result fields." +--- + +# DuckDuckGo Search + +Use the DuckDuckGo Search API when you need another privacy-oriented web result source alongside Google and Bing. Start with a simple query, then add region and time filters when your workflow needs them. + +## Search DuckDuckGo + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search( + engine="duckduckgo", + q="coffee", +) + +for result in results.get("organic_results", [])[:5]: + print(result.get("position"), result.get("title")) + print(result.get("link")) +``` + +## What to Read + +Most workflows start with `organic_results`. Useful fields include `title`, `link`, `snippet`, `position`, and any rich result blocks returned for the query. + +See the [DuckDuckGo Search API documentation](https://serpapi.com/duckduckgo-search-api) for region, date, and safe-search options. diff --git a/docs/examples/16-baidu-search.md b/docs/examples/16-baidu-search.md new file mode 100644 index 0000000..b4f8ce1 --- /dev/null +++ b/docs/examples/16-baidu-search.md @@ -0,0 +1,33 @@ +--- +title: "Baidu Search" +description: "Search Baidu results for China-focused research workflows." +--- + +# Baidu Search + +Use the Baidu Search API when your application needs results from Baidu's web index. It is useful for China-focused SEO checks, market research, and search result monitoring. + +## Search Baidu + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search( + engine="baidu", + q="coffee", +) + +for result in results.get("organic_results", [])[:5]: + print(result.get("position"), result.get("title")) + print(result.get("link")) +``` + +## What to Read + +Start with `organic_results`, then inspect any top-level result blocks returned by the query. Baidu result shapes can vary by keyword, so store only fields you have observed in sample responses. + +See the [Baidu Search API documentation](https://serpapi.com/baidu-search-api) for supported filters. diff --git a/docs/examples/17-google-local-services.md b/docs/examples/17-google-local-services.md new file mode 100644 index 0000000..4591da3 --- /dev/null +++ b/docs/examples/17-google-local-services.md @@ -0,0 +1,35 @@ +--- +title: "Google Local Services" +description: "Read Google Local Services ad cards for a service category." +--- + +# Google Local Services + +Use the Google Local Services API when you need local services ad data for a category, provider, or market. It returns ad cards rather than general Google Maps place results. + +## Search Local Services + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search( + engine="google_local_services", + q="electrician", + data_cid="6745062158417646970", +) + +for ad in results.get("local_ads", [])[:5]: + print(ad.get("title")) + print(ad.get("rating"), ad.get("reviews")) + print(ad.get("phone")) +``` + +## What to Read + +Start with `local_ads`. Useful fields include `title`, `rating`, `reviews`, `phone`, `service_area`, `years_in_business`, and `link` when present. + +See the [Google Local Services API documentation](https://serpapi.com/google-local-services-api) for query and provider detail options. diff --git a/docs/examples/18-ebay-product-listings.md b/docs/examples/18-ebay-product-listings.md new file mode 100644 index 0000000..a58bf4b --- /dev/null +++ b/docs/examples/18-ebay-product-listings.md @@ -0,0 +1,34 @@ +--- +title: "eBay Product Listings" +description: "Search eBay listings and read product, price, and seller fields." +--- + +# eBay Product Listings + +Use the eBay Search API when you need marketplace listings, prices, condition data, or seller result pages. eBay uses `_nkw` for the search keyword. + +## Search eBay + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search( + engine="ebay", + _nkw="coffee", +) + +for listing in results.get("organic_results", [])[:5]: + print(listing.get("title")) + print(listing.get("price"), listing.get("condition")) + print(listing.get("link")) +``` + +## What to Read + +Start with `organic_results`. Useful fields include `title`, `price`, `condition`, `shipping`, `location`, `seller`, `thumbnail`, and `link`. + +See the [eBay Search API documentation](https://serpapi.com/ebay-search-api) for filters, sorting, and pagination. diff --git a/docs/examples/19-home-depot-product-search.md b/docs/examples/19-home-depot-product-search.md new file mode 100644 index 0000000..6e8b0c3 --- /dev/null +++ b/docs/examples/19-home-depot-product-search.md @@ -0,0 +1,34 @@ +--- +title: "Home Depot Product Search" +description: "Search Home Depot product cards and read price, rating, and product identifiers." +--- + +# Home Depot Product Search + +Use The Home Depot Search API for home improvement product discovery, price tracking, and catalog enrichment. Start with a broad query and then add filters once you know the category shape. + +## Search Products + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search( + engine="home_depot", + q="table", +) + +for product in results.get("products", [])[:5]: + print(product.get("title")) + print(product.get("price"), product.get("rating")) + print(product.get("product_id")) +``` + +## What to Read + +Start with `products`. Useful fields include `title`, `product_id`, `price`, `rating`, `reviews`, `brand`, `thumbnail`, and product URLs when present. + +See the [The Home Depot Search API documentation](https://serpapi.com/home-depot-search-api) for product filters and pagination. diff --git a/docs/examples/20-google-events-discovery.md b/docs/examples/20-google-events-discovery.md new file mode 100644 index 0000000..a3df9bd --- /dev/null +++ b/docs/examples/20-google-events-discovery.md @@ -0,0 +1,34 @@ +--- +title: "Google Events Discovery" +description: "Search Google Events for upcoming event cards." +--- + +# Google Events Discovery + +Use the Google Events API when you need event cards for a topic, venue, or local market. It returns event-specific metadata instead of general organic search results. + +## Search Events + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search( + engine="google_events", + q="coffee", +) + +for event in results.get("events_results", [])[:5]: + print(event.get("title")) + print(event.get("date", {}).get("when")) + print(event.get("address")) +``` + +## What to Read + +Start with `events_results`. Useful fields include `title`, `date`, `address`, `link`, `ticket_info`, `venue`, and `description` when present. + +See the [Google Events API documentation](https://serpapi.com/google-events-api) for date and location parameters. diff --git a/docs/examples/21-google-images-search.md b/docs/examples/21-google-images-search.md new file mode 100644 index 0000000..1839d71 --- /dev/null +++ b/docs/examples/21-google-images-search.md @@ -0,0 +1,35 @@ +--- +title: "Google Images Search" +description: "Search Google Images and read image result metadata." +--- + +# Google Images Search + +Use the Google Images API when you need image discovery results, thumbnails, sources, and original image links. + +## Search Images + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search( + engine="google_images", + tbm="isch", + q="coffee", +) + +for image in results.get("images_results", [])[:5]: + print(image.get("title")) + print(image.get("source")) + print(image.get("original")) +``` + +## What to Read + +Start with `images_results`. Useful fields include `title`, `source`, `link`, `thumbnail`, `original`, `original_width`, and `original_height`. + +See the [Google Images API documentation](https://serpapi.com/google-images-api) for image filters and pagination. diff --git a/docs/examples/22-google-scholar-research.md b/docs/examples/22-google-scholar-research.md new file mode 100644 index 0000000..9413ea8 --- /dev/null +++ b/docs/examples/22-google-scholar-research.md @@ -0,0 +1,34 @@ +--- +title: "Google Scholar Research" +description: "Search Google Scholar publications and read citation-focused fields." +--- + +# Google Scholar Research + +Use the Google Scholar API when you need publication search results, citation counts, author links, or research monitoring workflows. + +## Search Scholar + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search( + engine="google_scholar", + q="coffee", +) + +for result in results.get("organic_results", [])[:5]: + print(result.get("title")) + print(result.get("publication_info", {}).get("summary")) + print(result.get("inline_links", {}).get("cited_by", {}).get("total")) +``` + +## What to Read + +Start with `organic_results`. Useful fields include `title`, `link`, `publication_info`, `snippet`, `resources`, and `inline_links.cited_by`. + +See the [Google Scholar API documentation](https://serpapi.com/google-scholar-api) for author, citation, and date filters. diff --git a/docs/examples/23-google-play-store-apps.md b/docs/examples/23-google-play-store-apps.md new file mode 100644 index 0000000..5316dee --- /dev/null +++ b/docs/examples/23-google-play-store-apps.md @@ -0,0 +1,36 @@ +--- +title: "Google Play Store Apps" +description: "Search Google Play app results and read app store metadata." +--- + +# Google Play Store Apps + +Use the Google Play Store API when you need app search results, app metadata, ratings, or links into Google Play. + +## Search Apps + +```python +import os +import serpapi + + +client = serpapi.Client(api_key=os.environ["SERPAPI_KEY"], timeout=20) + +results = client.search( + engine="google_play", + q="kite", + store="apps", + max_results="2", +) + +for app in results.get("organic_results", [])[:5]: + print(app.get("title")) + print(app.get("rating"), app.get("downloads")) + print(app.get("link")) +``` + +## What to Read + +Start with `organic_results`. Useful fields include `title`, `link`, `rating`, `downloads`, `description`, `thumbnail`, and app identifiers. + +See the [Google Play Store API documentation](https://serpapi.com/google-play-api) for store, device, and localization options. diff --git a/docs/examples/index.qmd b/docs/examples/index.qmd new file mode 100644 index 0000000..3ebd1f1 --- /dev/null +++ b/docs/examples/index.qmd @@ -0,0 +1,188 @@ +--- +title: "Examples" +description: "Starter recipes grouped by API category." +--- + +# Examples + +Use these examples as starting points, then check the linked SerpApi API documentation for engine-specific parameters. The categories below follow the API list published in [SerpApi llms.txt](https://serpapi.com/llms.txt). + +## Starter Examples + +**Search Engines** + +- [Google Across Countries](google-across-countries.md) +- [Bing Search](bing-search.md) +- [DuckDuckGo Search](duckduckgo-search.md) +- [Baidu Search](baidu-search.md) + +**AI Answers** + +- [Google AI Overview](google-ai-overview.md) + +**Local and Maps** + +- [Google Maps Local Businesses](google-maps-local-business.md) +- [Google Local Services](google-local-services.md) + +**Shopping and Marketplaces** + +- [Google Shopping Products](google-shopping-products.md) +- [Google Shopping Price Monitoring](google-shopping-price-monitoring.md) +- [Amazon Product Search](amazon-product-search.md) +- [Walmart Product Search](walmart-product-search.md) +- [eBay Product Listings](ebay-product-listings.md) +- [Home Depot Product Search](home-depot-product-search.md) + +**Travel and Hospitality** + +- [Google Flights Travel](google-flights-travel.md) +- [Tripadvisor Travel Search](tripadvisor-travel-search.md) + +**Finance, Trends, Jobs, and Events** + +- [Google Finance Market Data](google-finance-market-data.md) +- [Google Trends Demand](google-trends-demand.md) +- [Google Jobs Listings](google-jobs-listings.md) +- [Google Events Discovery](google-events-discovery.md) + +**Media, Apps, and Research** + +- [YouTube Video Search](youtube-video-search.md) +- [Google Images Search](google-images-search.md) +- [Google Scholar Research](google-scholar-research.md) +- [Google Play Store Apps](google-play-store-apps.md) + +## Supported API Map + +**Search Engines** + +- [Google Search API](https://serpapi.com/search-api) +- [Google Light Search API](https://serpapi.com/google-light-api) +- [Bing Search API](https://serpapi.com/bing-search-api) +- [DuckDuckGo Search API](https://serpapi.com/duckduckgo-search-api) +- [DuckDuckGo Light API](https://serpapi.com/duckduckgo-light-api) +- [Baidu Search API](https://serpapi.com/baidu-search-api) +- [Yahoo! Search API](https://serpapi.com/yahoo-search-api) +- [Yandex Search API](https://serpapi.com/yandex-search-api) +- [Naver Search API](https://serpapi.com/naver-search-api) + +**AI Answers** + +- [Google AI Mode API](https://serpapi.com/google-ai-mode-api) +- [Google AI Overview API](https://serpapi.com/google-ai-overview-api) +- [Bing Copilot API](https://serpapi.com/bing-copilot-api) +- [Brave AI Mode API](https://serpapi.com/brave-ai-mode-api) +- [Naver AI Overview API](https://serpapi.com/naver-ai-overview-api) + +**Local and Maps** + +- [Google Local API](https://serpapi.com/google-local-api) +- [Google Local Services API](https://serpapi.com/google-local-services-api) +- [Google Maps API](https://serpapi.com/google-maps-api) +- [Google Maps Photos API](https://serpapi.com/google-maps-photos-api) +- [Google Maps Autocomplete API](https://serpapi.com/google-maps-autocomplete-api) +- [Google Maps Directions API](https://serpapi.com/google-maps-directions-api) +- [Google Maps Posts API](https://serpapi.com/google-maps-posts-api) +- [Google Maps Reviews API](https://serpapi.com/google-maps-reviews-api) +- [Google Maps Contributor Reviews API](https://serpapi.com/google-maps-contributor-reviews-api) +- [Apple Maps API](https://serpapi.com/apple-maps-api) +- [Apple Maps Places API](https://serpapi.com/apple-maps-places-api) +- [Bing Maps API](https://serpapi.com/bing-maps-api) +- [DuckDuckGo Maps API](https://serpapi.com/duckduckgo-maps-api) +- [Yelp Search API](https://serpapi.com/yelp-search-api) +- [Yelp Place API](https://serpapi.com/yelp-place) +- [Yelp Reviews API](https://serpapi.com/yelp-reviews-api) + +**Shopping and Marketplaces** + +- [Google Shopping API](https://serpapi.com/google-shopping-api) +- [Google Shopping Light API](https://serpapi.com/google-shopping-light-api) +- [Google Immersive Product API](https://serpapi.com/google-immersive-product-api) +- [Amazon Search API](https://serpapi.com/amazon-search-api) +- [Amazon Product API](https://serpapi.com/amazon-product-api) +- [Walmart Search API](https://serpapi.com/walmart-search-api) +- [Walmart Product API](https://serpapi.com/walmart-product-api) +- [Walmart Reviews API](https://serpapi.com/walmart-product-reviews-api) +- [eBay Search API](https://serpapi.com/ebay-search-api) +- [eBay Product API](https://serpapi.com/ebay-product-api) +- [The Home Depot Search API](https://serpapi.com/home-depot-search-api) +- [The Home Depot Product API](https://serpapi.com/home-depot-product) +- [The Home Depot Reviews API](https://serpapi.com/home-depot-product-reviews) +- [Bing Shopping API](https://serpapi.com/bing-shopping-api) +- [Bing Product API](https://serpapi.com/bing-product-api) +- [Yahoo! Shopping API](https://serpapi.com/yahoo-shopping-search-api) + +**Travel and Hospitality** + +- [Google Flights API](https://serpapi.com/google-flights-api) +- [Google Flights Autocomplete API](https://serpapi.com/google-flights-autocomplete-api) +- [Google Flights Deals API](https://serpapi.com/google-flights-deals-api) +- [Google Hotels API](https://serpapi.com/google-hotels-api) +- [Google Hotels Autocomplete API](https://serpapi.com/google-hotels-autocomplete-api) +- [Google Hotels Photos API](https://serpapi.com/google-hotels-photos-api) +- [Google Hotels Reviews API](https://serpapi.com/google-hotels-reviews-api) +- [Google Travel Explore API](https://serpapi.com/google-travel-explore-api) +- [Tripadvisor Search API](https://serpapi.com/tripadvisor-search-api) +- [Tripadvisor Place API](https://serpapi.com/tripadvisor-place-api) +- [Tripadvisor Reviews API](https://serpapi.com/tripadvisor-reviews-api) +- [OpenTable Reviews API](https://serpapi.com/open-table-reviews-api) + +**News, Trends, Events, and Demand** + +- [Google News API](https://serpapi.com/google-news-api) +- [Google News Light API](https://serpapi.com/google-news-light-api) +- [Bing News API](https://serpapi.com/bing-news-api) +- [DuckDuckGo News API](https://serpapi.com/duckduckgo-news-api) +- [Baidu News API](https://serpapi.com/baidu-news-api) +- [Google Trends API](https://serpapi.com/google-trends-api) +- [Google Trends Autocomplete API](https://serpapi.com/google-trends-autocomplete) +- [Google Trends Trending Now API](https://serpapi.com/google-trends-trending-now) +- [Google Events API](https://serpapi.com/google-events-api) +- [Google Sports API](https://serpapi.com/google-sports-api) +- [Google Forums API](https://serpapi.com/google-forums-api) + +**Media, Apps, and Social** + +- [Google Images API](https://serpapi.com/google-images-api) +- [Google Images Light API](https://serpapi.com/google-images-light-api) +- [Google Lens API](https://serpapi.com/google-lens-api) +- [Google Reverse Image API](https://serpapi.com/google-reverse-image) +- [Google Videos API](https://serpapi.com/google-videos-api) +- [Google Videos Light API](https://serpapi.com/google-videos-light-api) +- [Google Short Videos API](https://serpapi.com/google-short-videos-api) +- [Bing Images API](https://serpapi.com/bing-images-api) +- [Bing Reverse Image API](https://serpapi.com/bing-reverse-image-api) +- [Bing Videos API](https://serpapi.com/bing-videos-api) +- [Yahoo! Images API](https://serpapi.com/yahoo-images-api) +- [Yahoo! Videos API](https://serpapi.com/yahoo-videos-api) +- [Yandex Images API](https://serpapi.com/yandex-images-api) +- [Yandex Videos API](https://serpapi.com/yandex-videos-api) +- [YouTube Search API](https://serpapi.com/youtube-search-api) +- [YouTube Video API](https://serpapi.com/youtube-video-api) +- [YouTube Video Transcript API](https://serpapi.com/youtube-video-transcript) +- [Apple App Store API](https://serpapi.com/apple-app-store) +- [Apple App Store Reviews API](https://serpapi.com/apple-reviews) +- [Apple App Store Product API](https://serpapi.com/apple-product) +- [Google Play Store API](https://serpapi.com/google-play-api) +- [Google Play Games API](https://serpapi.com/google-play-games) +- [Google Play Movies API](https://serpapi.com/google-play-movies) +- [Google Play Books API](https://serpapi.com/google-play-books) +- [Google Play Product API](https://serpapi.com/google-play-product-api) +- [Facebook Profile API](https://serpapi.com/facebook-profile-api) +- [Instagram Profile API](https://serpapi.com/instagram-profile-api) + +**Research, Knowledge, Ads, Finance, and Jobs** + +- [Google Scholar API](https://serpapi.com/google-scholar-api) +- [Google Scholar Author API](https://serpapi.com/google-scholar-author-api) +- [Google Scholar Case Law API](https://serpapi.com/google-scholar-case-law-api) +- [Google Patents API](https://serpapi.com/google-patents-api) +- [Google Patents Details API](https://serpapi.com/google-patents-details-api) +- [Google Related Questions API](https://serpapi.com/google-related-questions-api) +- [Google Autocomplete API](https://serpapi.com/google-autocomplete-api) +- [Google Ads API](https://serpapi.com/google-ads-api) +- [Google Ads Transparency API](https://serpapi.com/google-ads-transparency-center-api) +- [Google Finance API](https://serpapi.com/google-finance-api) +- [Google Finance Markets API](https://serpapi.com/google-finance-markets) +- [Google Jobs API](https://serpapi.com/google-jobs-api) From 897d928f71cc1fa82eb9835184fb6ff59ac738ea Mon Sep 17 00:00:00 2001 From: Adarsh Divakaran Date: Fri, 10 Jul 2026 23:24:27 +0530 Subject: [PATCH 7/7] feat: move to static docs site using great-docs package --- .github/workflows/docs.yml | 34 ------------------- ...-archive.md => 07-async-search-archive.md} | 0 .../{21-threading.md => 08-threading.md} | 0 ...ltiprocessing.md => 09-multiprocessing.md} | 0 .../{23-zero-trace.md => 10-zero-trace.md} | 0 ...on-restrictor.md => 11-json-restrictor.md} | 0 ...quest-options.md => 12-request-options.md} | 0 7 files changed, 34 deletions(-) delete mode 100644 .github/workflows/docs.yml rename docs/user_guide/{20-async-search-archive.md => 07-async-search-archive.md} (100%) rename docs/user_guide/{21-threading.md => 08-threading.md} (100%) rename docs/user_guide/{22-multiprocessing.md => 09-multiprocessing.md} (100%) rename docs/user_guide/{23-zero-trace.md => 10-zero-trace.md} (100%) rename docs/user_guide/{24-json-restrictor.md => 11-json-restrictor.md} (100%) rename docs/user_guide/{25-request-options.md => 12-request-options.md} (100%) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index fc35486..0000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Docs - -on: - workflow_dispatch: - -concurrency: - group: pages-${{ github.ref }} - cancel-in-progress: true - -jobs: - build-docs: - name: Build Great Docs - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.13" - - - name: Install package and documentation dependencies - run: | - python -m pip install --upgrade pip - pip install -e ".[docs]" - - - name: Set up Quarto - uses: quarto-dev/quarto-actions/setup@v2 - - - name: Build documentation - run: great-docs build diff --git a/docs/user_guide/20-async-search-archive.md b/docs/user_guide/07-async-search-archive.md similarity index 100% rename from docs/user_guide/20-async-search-archive.md rename to docs/user_guide/07-async-search-archive.md diff --git a/docs/user_guide/21-threading.md b/docs/user_guide/08-threading.md similarity index 100% rename from docs/user_guide/21-threading.md rename to docs/user_guide/08-threading.md diff --git a/docs/user_guide/22-multiprocessing.md b/docs/user_guide/09-multiprocessing.md similarity index 100% rename from docs/user_guide/22-multiprocessing.md rename to docs/user_guide/09-multiprocessing.md diff --git a/docs/user_guide/23-zero-trace.md b/docs/user_guide/10-zero-trace.md similarity index 100% rename from docs/user_guide/23-zero-trace.md rename to docs/user_guide/10-zero-trace.md diff --git a/docs/user_guide/24-json-restrictor.md b/docs/user_guide/11-json-restrictor.md similarity index 100% rename from docs/user_guide/24-json-restrictor.md rename to docs/user_guide/11-json-restrictor.md diff --git a/docs/user_guide/25-request-options.md b/docs/user_guide/12-request-options.md similarity index 100% rename from docs/user_guide/25-request-options.md rename to docs/user_guide/12-request-options.md