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/.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..a6893de 100644 --- a/README.md +++ b/README.md @@ -380,4 +380,4 @@ Bug reports and pull requests are welcome on GitHub. Once dependencies are insta ``` 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/assets/serpapi-icon.png b/assets/serpapi-icon.png new file mode 100644 index 0000000..e3754a2 Binary files /dev/null and b/assets/serpapi-icon.png differ 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..a405892 --- /dev/null +++ b/assets/serpapi-theme.css @@ -0,0 +1,912 @@ +: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: #58606d; + --serpapi-footer: #2f3030; + --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; + --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; +} + +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-link); +} + +a:hover, +a:focus { + color: var(--serpapi-blue); + text-decoration-color: var(--serpapi-accent); +} + +.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: 0; + width: auto !important; +} + +.navbar .nav-link:hover, +.navbar .nav-link:focus { + color: var(--serpapi-link) !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-link); +} + +.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, #215fb7 0%, #0d837f 100%) !important; + border-color: var(--serpapi-blue) !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-blue) !important; + color: #ffffff !important; +} + +pre, +div.sourceCode { + border: 1px solid var(--serpapi-code-border); + border-radius: 6px; +} + +pre, +code.sourceCode, +.sourceCode, +pre.sourceCode, +div.sourceCode { + background-color: var(--serpapi-code-bg) !important; + 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, +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; +} + +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 28px rgba(24, 33, 43, 0.06); +} + +.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: var(--serpapi-surface-raised); + border: 1px solid var(--serpapi-border); + border-radius: 8px; + box-shadow: 0 10px 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); +} + +.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; + gap: 0.75rem; + 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-link); + 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 { + 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; +} + +#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); + 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) { + .docs-home-nav { + grid-template-columns: 1fr; + } + + .docs-home-nav__sidebar { + position: static; + } +} + +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-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: #111827 !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, +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, #111827 0%, #151c26 100%) !important; + border-bottom-color: var(--serpapi-border); + 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: 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-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: 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: 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/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 4245bb6..0000000 Binary files a/docs/_static/serpapi-python.png and /dev/null differ 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..7c1ae4c --- /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..83d2b01 --- /dev/null +++ b/docs/examples/01-bing-search.md @@ -0,0 +1,38 @@ +--- +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..642a627 --- /dev/null +++ b/docs/examples/02-google-maps-local-business.md @@ -0,0 +1,36 @@ +--- +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..3ff97b9 --- /dev/null +++ b/docs/examples/03-google-shopping-products.md @@ -0,0 +1,37 @@ +--- +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..bdfab71 --- /dev/null +++ b/docs/examples/04-google-flights-travel.md @@ -0,0 +1,42 @@ +--- +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..35d9c73 --- /dev/null +++ b/docs/examples/05-google-finance-market-data.md @@ -0,0 +1,38 @@ +--- +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..fa86551 --- /dev/null +++ b/docs/examples/06-google-trends-demand.md @@ -0,0 +1,39 @@ +--- +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..a21bc57 --- /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"): + 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")) +``` + +## What to Read + +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/examples/08-google-news-monitoring.md b/docs/examples/08-google-news-monitoring.md new file mode 100644 index 0000000..0ebc9b3 --- /dev/null +++ b/docs/examples/08-google-news-monitoring.md @@ -0,0 +1,36 @@ +--- +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..3ce45e7 --- /dev/null +++ b/docs/examples/09-google-jobs-listings.md @@ -0,0 +1,37 @@ +--- +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..c932372 --- /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/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) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..801134d --- /dev/null +++ b/docs/index.md @@ -0,0 +1,133 @@ +--- +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. + +## Install + +:::{.panel-tabset} + +## pip + +```bash +pip install serpapi +``` + +## uv + +```bash +uv add serpapi +``` + +::: + +Python 3.6 or newer is required by the package. + +## First Request + +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", +) + +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. 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} + +## Documentation Map + +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/index.qmd): browse focused recipes grouped by search engines, AI answers, local/maps, shopping, travel, finance, trends, jobs, events, media, apps, and research. + +
+ Getting Started + Client Usage + Migration Guide + Examples + SerpApi Playground +
+ +::: + +:::{.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) +- [JSON Restrictor](user-guide/json-restrictor.md) +- [Request Options](user-guide/request-options.md) + +### Examples + +- [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/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..63e1a43 --- /dev/null +++ b/docs/user_guide/00-getting-started.md @@ -0,0 +1,121 @@ +--- +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 + +:::{.panel-tabset} + +## pip + +```bash +pip install serpapi +``` + +## uv project + +```bash +uv add serpapi +``` + +## uv environment + +```bash +uv pip install serpapi +``` + +::: + +Python 3.6 or newer is required by the package. + +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" +``` + +## Windows + +```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 and test a request before moving it 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..7cbd6ea --- /dev/null +++ b/docs/user_guide/01-client-usage.md @@ -0,0 +1,168 @@ +--- +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 engine parameters as keyword arguments: + +```python +results = client.search(engine="google", q="coffee") +``` + +You can also pass a dictionary when parameters are already stored in one: + +```python +params = { + "engine": "google", + "q": "coffee", +} +results = client.search(params) +``` + +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"} +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, +) +``` + +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: + +```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_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. + +## 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..07af0cd --- /dev/null +++ b/docs/user_guide/02-parameters-and-engines.md @@ -0,0 +1,81 @@ +--- +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 keyword arguments: + +```python +results = client.search( + engine="google", + q="coffee", + location="Austin, Texas", + hl="en", + gl="us", +) +``` + +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. + +| 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. | +| `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). + +## 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..4c80bac --- /dev/null +++ b/docs/user_guide/03-pagination.md @@ -0,0 +1,85 @@ +--- +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..b6c66f5 --- /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..94fdc5c --- /dev/null +++ b/docs/user_guide/05-account-and-locations.md @@ -0,0 +1,48 @@ +--- +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..0b62740 --- /dev/null +++ b/docs/user_guide/06-migrating-from-google-search-results.md @@ -0,0 +1,115 @@ +--- +title: "Migrating from google-search-results" +description: "Move from the deprecated google-search-results SDK to the recommended serpapi package." +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 + +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 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(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/07-async-search-archive.md b/docs/user_guide/07-async-search-archive.md new file mode 100644 index 0000000..b333ffc --- /dev/null +++ b/docs/user_guide/07-async-search-archive.md @@ -0,0 +1,56 @@ +--- +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/08-threading.md b/docs/user_guide/08-threading.md new file mode 100644 index 0000000..4ddf2e5 --- /dev/null +++ b/docs/user_guide/08-threading.md @@ -0,0 +1,71 @@ +--- +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/09-multiprocessing.md b/docs/user_guide/09-multiprocessing.md new file mode 100644 index 0000000..f67ba9a --- /dev/null +++ b/docs/user_guide/09-multiprocessing.md @@ -0,0 +1,73 @@ +--- +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/10-zero-trace.md b/docs/user_guide/10-zero-trace.md new file mode 100644 index 0000000..8757cf2 --- /dev/null +++ b/docs/user_guide/10-zero-trace.md @@ -0,0 +1,48 @@ +--- +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/docs/user_guide/11-json-restrictor.md b/docs/user_guide/11-json-restrictor.md new file mode 100644 index 0000000..a6a1470 --- /dev/null +++ b/docs/user_guide/11-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/12-request-options.md b/docs/user_guide/12-request-options.md new file mode 100644 index 0000000..862954a --- /dev/null +++ b/docs/user_guide/12-request-options.md @@ -0,0 +1,138 @@ +--- +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 new file mode 100644 index 0000000..65fc7ee --- /dev/null +++ b/great-docs.yml @@ -0,0 +1,150 @@ +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 + +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 + 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 +dark_mode_toggle: true + +accent_color: + light: "#377FEA" + dark: "#38BDF8" +navbar_color: + light: "#FFFFFF" + dark: "#111827" + +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..eee4743 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ 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" @@ -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/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 7d4454a..6ddbc88 100644 --- a/serpapi/core.py +++ b/serpapi/core.py @@ -4,23 +4,13 @@ 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. - - 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"]) + 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. """ DASHBOARD_URL = "https://serpapi.com/dashboard" @@ -32,30 +22,16 @@ 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). - - The following three calls are equivalent: - - .. 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 + """Fetch a page of results from SerpApi. - >>> params = {"q": "Coffee", "location": "Austin, Texas, United States"} - >>> s = serpapi.search(params) + 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 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: 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. **Learn more**: https://serpapi.com/search-api @@ -79,10 +55,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 +86,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 +115,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..c7ca647 100644 --- a/serpapi/models.py +++ b/serpapi/models.py @@ -10,13 +10,6 @@ class SerpResults(UserDict): """A dictionary-like object that represents the results of a SerpApi request. - .. code-block:: python - - >>> search = serpapi.search(q="Coffee", location="Austin, Texas, United States") - - >>> print(search["search_metadata"].keys()) - dict_keys(['id', 'status', 'json_endpoint', 'created_at', 'processed_at', 'google_url', 'raw_html_file', 'total_time_taken']) - 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 +33,10 @@ 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 passing results to libraries that expect a + plain ``dict``. + """ return self.data.copy() @@ -85,7 +81,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)