How to Scrape Google Search Results Without Blocks
Scrape Google search results at scale without getting blocked. Learn request pacing, geo-targeting, SERP parsing, and a working SparkProxy API example.

If you want to scrape Google search results at any real volume, the hard part isn't parsing the page, it's staying unblocked long enough to collect it. Google watches request cadence, IP reputation, and query patterns, and it answers suspicious traffic with a "429", an "unusual traffic" interstitial, or a reCAPTCHA. This guide is the hands-on version: how to pace requests, geo-target them, fetch the SERP through an API, and parse the result types, with working SparkProxy code you can run today. For the theory behind why proxies alone fall short, see the companion explainer on SERP scraping proxies.
Why Google blocks scrapers
Google doesn't block you for scraping one page. It blocks you for looking like automation across many pages. Three signals do most of the work.
Rate and cadence. A single IP firing 60 identical queries a minute at fixed intervals is the clearest bot tell there is. Humans pause, retype, and vary their timing. Uniform cadence gets flagged faster than a high raw count.
IP reputation. Requests from cloud ASNs (AWS, GCP, Azure) start with a worse reputation than residential ISP addresses. Datacenter IPs without careful header work hit "unusual traffic" screens quickly, while residential and mobile IPs last longer because they look like real subscribers.
Request fingerprint. The TLS handshake (JA3/JA4), HTTP/2 header order, and the Sec-CH-UA client hints all have to match a real Chrome build. A bare requests.get() sends a fingerprint no browser produces, so the IP barely matters once that mismatch is visible.
When enough signals stack up, Google returns one of three things: HTTP "429 Too Many Requests", a "Our systems have detected unusual traffic from your computer network" page (HTTP "200" with no results), or a reCAPTCHA challenge. All three mean the same thing: slow down and look more human. The full detection surface and how to counter each signal is covered in how to avoid getting your proxy blocked.
Scrape the public SERP, ethically
Scraping the public search results page sits in contested territory, so set your own guardrails before you write a line of code.
- Only collect public SERP data. Query results as an anonymous user sees them. Don't scrape logged-in, personalized, or paywalled surfaces.
- Respect rate limits and back off. If Google returns a "429" with a
Retry-Afterheader, honor it. Ignoring back-off signals is both rude and self-defeating; it accelerates the block. - Know the Terms of Service. Google's ToS restrict automated access outside its own interface. Scraping public data is not a criminal act in most jurisdictions, but it can breach the contract, so weigh the risk for your use case.
- Prefer the official API when it fits. Google's Programmable Search Engine JSON API gives 100 free queries a day, then $5 per 1,000 up to 10,000/day. It's a real google search results api, just narrower and pricier at scale than scraping the live SERP. For your own site's ranking data, Search Console API carries zero ToS risk.
If your goal is rank tracking or market research, keep volume proportional to need and cache aggressively. Two teams pulling the same 5,000 keywords hourly is wasteful; most rank data only needs a daily or weekly refresh.
Scraping at scale? Skip the blocks.
Fast, unblockable datacentre proxies with unlimited bandwidth.
Build the Google search URL correctly
The SERP is just a URL with query parameters. Get these right and you control language, region, device, and pagination without any hidden state.
| Parameter | Purpose | Example |
|---|---|---|
| `q` | The search query (URL-encoded) | `q=running+shoes` |
| `hl` | Interface language | `hl=en` |
| `gl` | Country of the results (ISO alpha-2) | `gl=us` |
| `num` | Results per page (see the caveat below) | `num=10` |
| `start` | Pagination offset | `start=10` (page 2) |
| `uule` | Precise location encoding | canonical-name encoded |
| `tbm` | Vertical: `isch` images, `nws` news, `shop` | `tbm=nws` |
| `lr` | Restrict to a language | `lr=lang_de` |
A complete URL for US English results looks like this:
https://www.google.com/search?q=running+shoes&hl=en&gl=us&num=10
The num=100 caveat (this is where old tutorials break). For years, scrapers set num=100 to pull 100 organic results in one request and cut request count by 10x. In September 2025 Google stopped honoring num=100 reliably; it now caps most responses near 10 results regardless of the parameter. If a guide tells you to grab 100 results per page, it predates that change. Plan for 10 results per request and paginate with start (0, 10, 20, ...) instead. Budget your credits and pacing around that reality, because it roughly 10x'd the request count for deep-SERP collection overnight.
Pace requests so you look human
Cadence is the single biggest lever you control. The goal is variance, not just delay.
import random
import time
def human_delay(base=25, jitter=0.5):
"""Sleep base seconds ± 50%, drawn uniformly. Never a fixed interval."""
low = base * (1 - jitter)
high = base * (1 + jitter)
time.sleep(random.uniform(low, high))
A fixed time.sleep(25) between queries is itself a fingerprint: real users never hit the millisecond-perfect interval. The random.uniform version above spreads each gap between roughly 12 and 38 seconds, which reads far more like a person refining searches.
Two more pacing rules that matter in production:
- Cap queries per IP. For residential IPs, keep it near 100 to 200 queries per IP per day spread across hours. Above that, CAPTCHA rates climb steeply.
- Randomize order and refinements. Don't send 500 keywords in perfect alphabetical order at a steady beat. Shuffle the queue and vary
startdepth so no two sessions look identical.
If you're managing your own proxy pool, you own all of this. If you use a scraping API, you offload IP rotation and cadence-per-IP to the provider and only control your own concurrency and retry logic, which is most of why teams switch. That trade is broken down in web scraping API vs self-managed proxies.
Geo-target for accurate local results
Google localizes results heavily. A query for "plumber" or "best bank" returns completely different SERPs in London versus New York. If your data has to reflect a specific market, the request has to originate from that market, and the URL has to declare it.
Two layers work together:
- Exit IP location. Route the request through a proxy in the target country so Google sees local traffic. Without this,
glalone gives you an approximation, not the real local SERP. - URL geo parameters. Set
glto the country and, for city-level precision, encode auulelocation string. Keephlconsistent with the market's language.
Mismatched layers produce garbage: a UK exit IP sending gl=us returns a blend that matches neither market. Keep the IP country, gl, and hl aligned. This is exactly the setup rank trackers rely on, covered in datacenter proxies for SEO rank tracking.
Fetch the SERP with the SparkProxy Scraping API
Instead of assembling proxies, headers, TLS fingerprints, and a headless browser yourself, you can send one request to the SparkProxy Scraping API. It rotates the proxy, renders the page, and returns the SERP HTML. You pass the Google search URL as the url parameter and authenticate with the X-API-Key header.
A minimal cURL request for a geo-targeted, residential-routed SERP:
curl -G "https://scrape.sparkproxy.io/api/v1" \
-H "X-API-Key: YOUR_API_KEY" \
--data-urlencode "url=https://www.google.com/search?q=running+shoes&hl=en&gl=us&num=10" \
--data-urlencode "render_js=true" \
--data-urlencode "country_code=us" \
--data-urlencode "premium_proxy=true"
The same call in Python:
import requests
import urllib.parse
query = "running shoes"
serp_url = "https://www.google.com/search?" + urllib.parse.urlencode({
"q": query,
"hl": "en",
"gl": "us",
"num": 10,
})
resp = requests.get(
"https://scrape.sparkproxy.io/api/v1",
headers={"X-API-Key": "YOUR_API_KEY"},
params={
"url": serp_url,
"render_js": "true", # run a real browser so SERP features hydrate
"country_code": "us", # exit through a US IP
"premium_proxy": "true", # residential pool, harder to block
},
timeout=90,
)
resp.raise_for_status()
html = resp.text
Each parameter maps to one of the blocking signals from earlier. render_js=true runs Chromium so JavaScript-driven SERP features load and the request carries a real browser fingerprint. country_code=us fixes the exit location. premium_proxy=true routes through residential IPs that survive Google's reputation checks. On the toughest days, add stealth=true, which layers a homepage pre-warm, a forced Google referrer, and longer idle delays on top.
Watch the credit cost, because these stack: premium_proxy with render_js is 25 credits, country_code adds 5, and stealth adds another 5, so a fully loaded SERP request runs about 35 credits. Turn off what a given target doesn't need. Many keyword-only queries succeed without stealth.
For deep SERPs, loop over start with pacing between requests:
def scrape_serp_pages(query, pages=3, gl="us", hl="en"):
results_html = []
for page in range(pages):
serp_url = "https://www.google.com/search?" + urllib.parse.urlencode({
"q": query, "hl": hl, "gl": gl, "num": 10, "start": page * 10,
})
r = requests.get(
"https://scrape.sparkproxy.io/api/v1",
headers={"X-API-Key": "YOUR_API_KEY"},
params={
"url": serp_url,
"render_js": "true",
"country_code": gl,
"premium_proxy": "true",
},
timeout=90,
)
if r.status_code == 200:
results_html.append(r.text)
human_delay(base=25) # from the pacing section
return results_html
Parse the SERP result types
A modern Google SERP is not a list of ten blue links. It's a mixed layout of organic results, ads, and features, and each needs its own extraction logic.
The honest caveat first: Google's CSS class names are obfuscated and rotate often (.g, .tF2Cxc, .yuRUbf and friends change without notice). Don't hard-code brittle class chains. Prefer structural anchors (the result container that holds an
SERP field reference table
Here's what a Google SERP can contain and how to recognize each block. Not every query returns every type; the mix depends on intent (informational, commercial, local).
| Result type | What it is | How to detect it | Notes |
|---|---|---|---|
| Organic result | Standard "blue link" listing | `Handle CAPTCHAs, 429s, and empty pagesEven a well-behaved scraper hits blocks. Detect them explicitly instead of trusting the status code alone, because Google often returns "200" on a block page.
When
An empty results page with a "200" and no organic containers usually means a soft block or a Scale it: pacing math and batchingPut the numbers together before you launch a big job. Say you track 5,000 keywords daily and need the top 30 results for each. After the Do the arithmetic that keeps you unblocked:
This same discipline (pace, geo-batch, cache) is what separates a scraper that runs quietly for months from one that gets throttled in an afternoon. The market-research angle, including how to turn SERP data into competitive insight, is in using proxies for market research and data collection. Frequently asked questionsFAQScraping publicly visible search results is generally not a criminal act in most jurisdictions, and the hiQ v. LinkedIn ruling narrowed the US Computer Fraud and Abuse Act as applied to public data. It can still breach Google's Terms of Service, which restrict automated access outside Google's own interface, so it's a contractual risk rather than a criminal one. Collect only public data, respect rate limits, and check the rules for your jurisdiction and use case. Because IP rotation is only one signal. Google also fingerprints your TLS handshake and HTTP header order, and it flags uniform request timing. A residential IP sending 60 identical queries a minute from a non-browser client gets blocked faster than a slower, browser-shaped request. Fix cadence and fingerprint, not just the IP. Yes. The Programmable Search Engine JSON API returns structured results with 100 free queries a day, then $5 per 1,000 up to 10,000/day. It's cleaner than scraping but narrower and more expensive at scale, and it doesn't return the full live SERP layout. For your own site's rankings, the Search Console API carries no Terms of Service risk. There's no published limit, and it depends on IP type. As a conservative starting point, keep residential IPs near 100 to 200 queries per day each with randomized timing, which works out to well under a query per minute per IP. To go faster, spread the load across many IPs (a proxy pool or a scraping API) rather than pushing one address harder. For basic organic results, often no; they're in the initial HTML. For features that load or expand client-side, such as People Also Ask and some carousels, yes. Rendering also produces a real browser fingerprint, which lowers block rates. With the SparkProxy API you enable it with Google stopped reliably honoring the Limited-time · 50% off Get 50% off your first purchase Premium datacentre proxies with unlimited bandwidth. Use the code at checkout. Offer ends soon — claim it before it's gone CodeSPARK50 Claim Discount Keep reading Related articles![]() Bypass Cloudflare Web Scraping: An Ethical 2026 GuideBypass Cloudflare web scraping blocks the ethical way: TLS/JA3 fingerprints, JS challenges, Turnstile, plus real-browser and SparkProxy Scraping API fixes. SparkProxy·Guides ![]() How to Use Proxies with PuppeteerUse proxies with Puppeteer: set --proxy-server launch args, per-context proxies, page.authenticate for auth, rotation, stealth tips, and error fixes. SparkProxy·Guides ![]() How to Rotate Proxies in Node.jsRotate proxies in Node.js with round-robin, random, weighted, and sticky strategies, plus working axios, got, and node-fetch code and a reusable pool class. SparkProxy·Guides |



