Skip to main content

Official Python client for the serp.cheap Google SERP API.

Project description

serpcheap

PyPI Python versions License: MIT

Official Python client for the serp.cheap Google Search API — real-time Google SERP data (organic results, ads, knowledge graph, page scraping, rank tracking).

The cheapest Google Search API we know of: $0.0003 per cached search, $0.0006 fresh, no monthly minimum (~10× cheaper than SerpApi).

A thin, zero-dependency, synchronous client built on the standard library.

Install

pip install serpcheap

Quickstart

from serpcheap import SerpCheap

client = SerpCheap("KEY")
r = client.search(q="best running shoes", gl="us")
print(r.organic[0].title)

Get an API key at app.serp.cheap.

Search parameters

client.search(
    q="best running shoes",  # required
    gl="us",                 # country, default "us"
    hl="en",                 # UI language (optional)
    tbs="qdr:d",             # time filter: qdr:h / qdr:d / qdr:w (optional)
    page=1,                  # 1-indexed page, default 1
)

The response is a SearchResponse dataclass. JSON camelCase fields are exposed as snake_case attributes:

r.search            # the query
r.page              # page number
r.organic           # list[OrganicResult] (always a list)
                    #   each: .content / .screenshot_url / .scrape_error when scraped
r.ads               # list[Ad] | None
r.knowledge_graph   # KnowledgeGraph | None
r.people_also_ask   # list[str] | None
r.related_searches  # list[RelatedSearch] | None
r.stats             # SearchStats(balance, cost, cached) | None

Scraping page content

Pass scrape to fetch the page content of the top organic results alongside the SERP. Omit it (the default) for a search-only request. Each scraped page is billed on top of the search.

from serpcheap import SerpCheap, ScrapeOptions

client = SerpCheap("KEY")
r = client.search(
    q="best running shoes",
    scrape=ScrapeOptions(
        render_js=False,   # render with a headless browser first (JS-heavy sites)
        screenshot=False,  # capture a full-page screenshot (48h presigned URL)
        top_n=5,           # how many top organic results to scrape (1..20)
        wait_for=None,     # CSS selector to wait for (render_js only)
        wait_ms=None,      # extra settle time in ms, 0..5000 (render_js only)
        screenshot_width=None,   # screenshot viewport width in px (default 1920, max 1920)
        screenshot_height=None,  # screenshot viewport height in px (default 1080, max 1920)
    ),
)

for result in r.organic:
    if result.scrape_error:
        print(result.position, "failed:", result.scrape_error)
    else:
        print(result.position, result.content)  # markdown
        print(result.screenshot_url)             # set when screenshot=True

Scraping a single page

scrape fetches and extracts one URL as markdown, independent of a search:

from serpcheap import SerpCheap

client = SerpCheap("KEY")
r = client.scrape(
    "https://example.com",
    render_js=False,          # render with a headless browser first (JS-heavy sites)
    screenshot=False,         # capture a screenshot (1920x1080 by default, 48h presigned URL)
    wait_for=None,            # CSS selector to wait for (render_js only)
    wait_ms=None,             # extra settle time in ms, 0..5000 (render_js only)
    screenshot_width=None,    # screenshot width in px (default 1920, max 1920)
    screenshot_height=None,   # screenshot height in px (default 1080, max 1920)
)

print(r.url)             # the requested URL
print(r.status)          # upstream HTTP status | None
print(r.title)           # page title | None
print(r.content)         # markdown | None
print(r.content_text)    # plain text | None
print(r.screenshot_url)  # 48h presigned URL, set when screenshot=True
r.stats                  # ScrapeStats(balance, cost) | None

Rank tracking

rank scans Google result pages for a keyword and reports where a domain or URL ranks:

from serpcheap import SerpCheap

client = SerpCheap("KEY")
r = client.rank(
    "example.com",            # domain or full URL to locate
    "best running shoes",     # the keyword
    gl="us",                  # country, default "us"
    hl=None,                  # UI language (optional)
    tbs=None,                 # time filter: qdr:h / qdr:d / qdr:w (optional)
    pages=1,                  # how many result pages to scan (1..10), billed per page
    match_type="domain",      # "domain" (any result on the domain) or "exact" (identical URL)
)

print(r.found)            # bool
print(r.rank)             # absolute 1-based rank | None
for m in r.matches:       # list[RankMatch]
    print(m.rank, m.page, m.position_on_page, m.link)
r.organic                 # list[OrganicResult] across scanned pages
r.pages_scanned           # int
r.partial                 # True when some pages failed
r.pages_failed            # list[int]
r.stats                   # RankStats(balance, cost, pages_cached, pages_fresh) | None

Paginating

search_pages lazily yields pages and stops on the first empty page:

for page in client.search_pages(q="best running shoes", from_=1, to=5):
    for result in page.organic:
        print(result.position, result.title)

Error handling

Every failure raises a typed SerpCheapError:

from serpcheap import SerpCheap, SerpCheapError

client = SerpCheap("KEY")
try:
    r = client.search(q="best running shoes")
except SerpCheapError as e:
    print(e.code)            # e.g. "insufficient_credits", "rate_limited"
    print(e.status)          # HTTP status, if any
    print(e.retry_after_ms)  # set on rate_limited
    print(e.retryable)       # True for transient errors

Retries & timeouts

client = SerpCheap("KEY", timeout_ms=5000, max_retries=2)

Transient errors (rate_limited, too_many_concurrent_requests, service_temporarily_unavailable, result_timeout, client_timeout, network_error) are retried automatically up to max_retries. The backoff honors a server-provided retry_after_ms, otherwise it grows exponentially (capped at 2s). Each request is bounded by timeout_ms.

Development

Run the test suite with enforced coverage (no install needed; tests put src on sys.path):

python3 -m coverage run --source=src/serpcheap -m unittest discover -s tests -v
python3 -m coverage report --fail-under=95

The parity suite (tests/test_parity.py) cross-checks the client against the shared contract/mockserver (requires node); the unit suites use an in-process http.server and have no external dependencies.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

serpcheap-0.2.2.tar.gz (15.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

serpcheap-0.2.2-py3-none-any.whl (10.2 kB view details)

Uploaded Python 3

File details

Details for the file serpcheap-0.2.2.tar.gz.

File metadata

  • Download URL: serpcheap-0.2.2.tar.gz
  • Upload date:
  • Size: 15.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for serpcheap-0.2.2.tar.gz
Algorithm Hash digest
SHA256 da4cec644bc5446a08d0446deda9680107b788903daa97f71d0d0ab0670b94b3
MD5 568ede1a3ffd7809d39e12c62fbd9232
BLAKE2b-256 302f37ab013581a8f9d8502040478ab1529b83aa38daee1b032c21dc50ed1e3b

See more details on using hashes here.

File details

Details for the file serpcheap-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: serpcheap-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 10.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for serpcheap-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 af1c5f1ae3f4123bb00804f72e208046e698f3f22ef5b9495b6256ed8f080639
MD5 da81a93a0a0863930831eacb91938e04
BLAKE2b-256 75bac7bf46a65ecb713ddb71def242f548c617d523d00291d32e32bb8f73c70e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page