Official Python client for the serp.cheap Google SERP API.
Project description
serpcheap
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da4cec644bc5446a08d0446deda9680107b788903daa97f71d0d0ab0670b94b3
|
|
| MD5 |
568ede1a3ffd7809d39e12c62fbd9232
|
|
| BLAKE2b-256 |
302f37ab013581a8f9d8502040478ab1529b83aa38daee1b032c21dc50ed1e3b
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
af1c5f1ae3f4123bb00804f72e208046e698f3f22ef5b9495b6256ed8f080639
|
|
| MD5 |
da81a93a0a0863930831eacb91938e04
|
|
| BLAKE2b-256 |
75bac7bf46a65ecb713ddb71def242f548c617d523d00291d32e32bb8f73c70e
|