Skip to content

New AI assistant answers: ChatGPT, Gemini, Copilot and AI Mode by API

SerpKite
Get API key
Docs menu / Google Search API in Python

Google Search API in Python

Get Google search results in Python with the serpkite package: first query, country and city, paging, asyncio, CSV export, Markdown for LLMs and error handling.

View as Markdown

This guide gets Google results into a Python program, from the first query to concurrent searches with error handling. Every snippet runs as is with Python 3.9+ and the official serpkite package. A runnable copy of the examples lives in SerpKite/google-search-api on GitHub.

Each search costs 1 credit, from $0.60 per 1,000 on the largest pack. Failed, blocked and empty searches are free.

pip install serpkite
export SERPKITE_API_KEY=skt_live_...   # from app.serpkite.com/keys
from serpkite import SerpKite

sk = SerpKite()  # reads SERPKITE_API_KEY
res = sk.search("best espresso machine", country="us", language="en")

for r in res.results[:5]:
    print(r.position, r.title, r.link)
print("credits used:", res.meta.credits_used)

res.results holds the organic results in page order. Each one has position, title, link (the final URL, after Google’s redirects), domain and snippet. The models are pydantic, so res.model_dump() gives you a plain dict.

Choose the market: country, language and city

Google shows different results in different places. Pass country and language as two-letter codes, and location for city-level results:

res = sk.search("coffee roasters", country="de", language="de", location="Berlin,Germany", num=20)

if res.knowledge_graph:
    print(res.knowledge_graph.title)
for q in res.people_also_ask or []:
    print("People also ask:", q.question)
for s in res.related_searches or []:
    print("Related:", s.query)

num asks for up to 100 results in one call. Besides results, the response carries the other parts of the page when Google shows them: knowledge_graph, answer_box, people_also_ask, related_searches, top_stories and places. See Localization for the codes and Search filters for date and domain filters.

Page through results

page walks the result pages. Stop when a page comes back empty, and drop repeats, because Google sometimes shows a URL on two pages:

seen, rows = set(), []
for page in range(1, 4):
    res = sk.search("python web scraping tutorial", page=page)
    if not res.results:
        break
    for r in res.results:
        if r.link not in seen:
            seen.add(r.link)
            rows.append(r)
print(len(rows), "unique results")

Each page is a separate search. Pagination and depth explains when one call with a larger num is cheaper than several pages.

Run many queries at once with asyncio

AsyncSerpKite has the same methods as SerpKite, so you can send queries concurrently:

import asyncio
from serpkite import AsyncSerpKite

QUERIES = ["serp api", "google search api", "rank tracker api", "news api"]

async def main():
    async with AsyncSerpKite() as sk:
        responses = await asyncio.gather(*(sk.search(q, num=10) for q in QUERIES))
    for q, res in zip(QUERIES, responses):
        top = res.results[0] if res.results else None
        print(f"{q!r}: {top.domain if top else 'no results'}")

asyncio.run(main())

For thousands of queries that don’t need an answer right away, batches cost half as much and call a webhook when they finish.

Save results to CSV

import csv

res = sk.search("project management software", num=20)

with open("results.csv", "w", newline="") as f:
    w = csv.writer(f)
    w.writerow(["position", "title", "link", "domain", "snippet"])
    for r in res.results:
        w.writerow([r.position, r.title, r.link, r.domain, r.snippet])

With pandas installed, pd.DataFrame([r.model_dump() for r in res.results]) gives you a DataFrame instead.

Markdown for LLMs

If the results go into a prompt, ask for Markdown. It keeps titles, links and snippets and uses far fewer tokens than JSON:

md = sk.search("how does retrieval augmented generation work", format="markdown")
print(md[:400])

format="markdown" returns a str. To put the text of the top pages in the same response, add include_content (see Page content).

Handle errors

The client retries rate limits, server errors and dropped connections on its own (max_retries, 2 by default). What’s left to handle are errors a retry won’t fix:

from serpkite import SerpKite, SerpKiteError, InsufficientCreditsError, RateLimitError

sk = SerpKite(max_retries=3)
try:
    res = sk.search("best espresso machine", num=500)
except InsufficientCreditsError:
    print("Out of credits: top up or raise the spend cap")
except RateLimitError as e:
    print("Too many requests:", e.message)
except SerpKiteError as e:
    print(e.status, e.code, e.message, e.request_id)

This example prints 400 invalid_request num must be between 1 and 100 …, because num is limited to 100. Every error carries a request_id to quote to support. The codes are listed in Errors.

Without the SDK

The API is plain HTTPS and JSON, so requests works too:

import os
import requests

resp = requests.post(
    "https://api.serpkite.com/v1/search",
    headers={"Authorization": f"Bearer {os.environ['SERPKITE_API_KEY']}"},
    json={"q": "best espresso machine", "country": "us", "num": 10},
    timeout=60,
)
resp.raise_for_status()
for r in resp.json()["results"]:
    print(r["position"], r["title"], r["link"])

Coming from Google’s Custom Search JSON API

Google closed the Custom Search JSON API to new customers and shuts it down on January 1, 2027. If your Python code calls googleapis.com/customsearch/v1, you don’t have to rewrite it for the SDK: SerpKite’s /customsearch/v1 takes the same parameters and returns the same items[], so only the host and key change. See Migrate from Google CSE and the before-and-after scripts in SerpKite/custom-search-api-migration.

Next steps

Last updated: 2026-10-25