How to Capture Google Search Results from a Specific City Using a Proxy
A proxy can change where a Google request appears to come from, but it cannot guarantee a city-specific SERP. Learn how to capture, validate, and record results.
Short answer: route your browser or HTTP request through a proxy whose exit location is appropriate to your target city, then verify the location Google reports on the results page. A proxy changes one part of the request context; it does not guarantee that Google will return the exact results a person in that city sees. Google estimates location from multiple signals, including device location and account home or work addresses, and lets searchers add a location to a query. Google explains how search location is estimated.
For repeatable collection, use a SERP provider that documents city-level targeting, or run a controlled browser capture through a proxy and validate each result. Keep city, country, language, device, query, proxy exit, and capture time in your records. Do not label a result as a city’s SERP unless its location cues support that label.
1. Choose the right capture method
There are two different goals that are easy to confuse:
- Collect structured search results: use a SERP API that returns fields such as result titles, links, and snippets. Confirm that its current documentation supports the exact city you need.
- Capture what a browser displays: use a browser routed through a proxy and save a screenshot or page content. This is useful for visual review, but may require more manual validation and can encounter Google consent pages, bot checks, or other interruptions.
A city name added to the query, such as dentist in Austin, can localize the search intent. It does not reproduce the unmodified search experience of someone physically in Austin. Keep these two test types separate in reports.
2. Set up a proxy-backed browser capture
The following example uses Playwright for Node.js. It opens a Google results URL through a proxy, saves a screenshot and HTML, and prints the final URL and page title. Replace the proxy placeholder with the connection URL supplied by your proxy provider. The example is a generic browser workflow; it does not claim that a proxy setting alone selects a precise Google city.
npm install playwright
npx playwright install chromium
// capture.mjs
import { chromium } from 'playwright';
const query = process.env.QUERY ?? 'coffee shops';
const proxyServer = process.env.PROXY_SERVER;
if (!proxyServer) {
throw new Error('Set PROXY_SERVER to your proxy provider connection URL');
}
const browser = await chromium.launch({
headless: true,
proxy: {
server: proxyServer,
...(process.env.PROXY_USERNAME ? { username: process.env.PROXY_USERNAME } : {}),
...(process.env.PROXY_PASSWORD ? { password: process.env.PROXY_PASSWORD } : {}),
},
});
try {
const context = await browser.newContext({
locale: process.env.LOCALE ?? 'en-US',
viewport: { width: 1365, height: 900 },
});
const page = await context.newPage();
const searchUrl = new URL('https://www.google.com/search');
searchUrl.searchParams.set('q', query);
const response = await page.goto(searchUrl.toString(), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
await page.waitForTimeout(1500);
await page.screenshot({ path: 'google-results.png', fullPage: true });
await page.locator('body').evaluate((body) => {
document.documentElement.dataset.captureHtml = body.innerHTML;
});
const html = await page.content();
await import('node:fs/promises').then((fs) => fs.writeFile('google-results.html', html));
console.log(JSON.stringify({
requestedQuery: query,
finalUrl: page.url(),
title: await page.title(),
httpStatus: response?.status() ?? null,
screenshot: 'google-results.png',
html: 'google-results.html',
}, null, 2));
} finally {
await browser.close();
}
Run it with the proxy endpoint and the query you want to capture:
PROXY_SERVER='http://proxy.example:8080' \
PROXY_USERNAME='your-username' \
PROXY_PASSWORD='your-password' \
QUERY='coffee shops' \
LOCALE='en-US' \
node capture.mjs
Proxy URL schemes and authentication requirements depend on the provider. Use the endpoint and credential format it documents. If your provider offers city-specific exits, select the desired city there. A country-level exit is not evidence of a particular city.
3. Validate that the page reflects the target city
- Open the saved screenshot or HTML and check for Google’s displayed location information, commonly shown near the bottom of a results page.
- Compare a query with recognizable local intent or results. Treat that as a cross-check, not proof by itself.
- If the displayed location does not match, record the result as unverified and investigate the proxy exit and request context before using it.
- Repeat the capture if needed, keeping the query and other settings constant so changes are interpretable.
Google says the results page can indicate how location was estimated. It also notes that location can use signals beyond the apparent network origin. A proxy can affect those signals, but the cited Google help material does not say that a proxy alone guarantees an exact city.
4. Use a SERP API when you need city controls
A hosted SERP API may expose location controls that a plain browser request does not. For example, SerpApi documents a city-level location value or a Google-encoded uule value, along with country (gl) and language (hl) controls. Its documentation says location and uule cannot be used together, and warns that Google may still consider the proxy’s country when only location is set. Check its current parameter rules, including coordinate pairing constraints, before building a request. These are provider-specific behaviors, not universal Google URL parameters. See the SerpApi Google Search API reference.
When evaluating a provider, verify that it supports your exact city rather than only a country, distinguishes city, country, and language settings, and returns the representation you need: structured results, raw HTML, or both. The available research does not establish a performance, accuracy, or price winner.
Do not treat gl as a city selector. Google’s Custom Search documentation describes it as a country-level signal that boosts results associated with that country. That is documentation for the Custom Search API, not proof that gl chooses a city on ordinary Google Search pages. See the Google Custom Search API reference.
5. Make captures reproducible
Store the context beside every capture. A useful record includes:
- Exact query text and capture timestamp, including timezone.
- Target city and country, plus the provider’s city setting or UULE when applicable.
- Proxy provider and exit location observed for the request, if available.
- Language, country setting, browser locale, device or viewport, and whether the query includes a location phrase.
- Final URL, response status, screenshot or response file, and any location label shown by Google.
These notes improve repeatability; they do not guarantee identical results. Search results can vary with request context and over time.
6. cURL, Python, and Node.js request examples
These examples send an HTTP request through a proxy and save the returned response. They demonstrate network routing and basic capture only. They do not configure a Google city parameter, render a browser page, or establish that the response represents the target city. Check the response for redirects, consent content, or an access challenge before treating it as search results.
cURL
curl --proxy "$PROXY_URL" \
--get 'https://www.google.com/search' \
--data-urlencode 'q=coffee shops' \
--output google-results.html \
--write-out 'HTTP %{http_code}\nFinal URL: %{url_effective}\n'
Set PROXY_URL to the proxy endpoint format supported by your provider. If it requires authentication, follow its documented cURL syntax and avoid saving credentials in shell history or source control.
Python
import os
import requests
proxy_url = os.environ['PROXY_URL']
proxies = {'http': proxy_url, 'https': proxy_url}
response = requests.get(
'https://www.google.com/search',
params={'q': 'coffee shops'},
proxies=proxies,
headers={'Accept-Language': 'en-US,en;q=0.9'},
timeout=(15, 60),
)
response.raise_for_status()
with open('google-results.html', 'wb') as output:
output.write(response.content)
print('HTTP status:', response.status_code)
print('Final URL:', response.url)
Node.js
Node’s built-in fetch does not take a proxy URL option directly. This example uses the https-proxy-agent package to route the request:
npm install https-proxy-agent
// fetch-results.mjs
import { writeFile } from 'node:fs/promises';
import { HttpsProxyAgent } from 'https-proxy-agent';
const proxyUrl = process.env.PROXY_URL;
if (!proxyUrl) throw new Error('Set PROXY_URL');
const agent = new HttpsProxyAgent(proxyUrl);
const url = new URL('https://www.google.com/search');
url.searchParams.set('q', 'coffee shops');
const response = await fetch(url, {
dispatcher: agent,
headers: { 'accept-language': 'en-US,en;q=0.9' },
signal: AbortSignal.timeout(60000),
});
const body = await response.arrayBuffer();
await writeFile('google-results.html', Buffer.from(body));
console.log('HTTP status:', response.status);
console.log('Final URL:', response.url);
Runtime note: the dispatcher option shown above is not accepted by every Node.js fetch implementation. If your runtime rejects it, use a fetch client that supports proxy dispatch or use the Playwright browser workflow above. Confirm the proxy agent’s current Node compatibility in its documentation before relying on this transport example.
7. Common errors and fixes
| Symptom | Likely cause | What to check |
|---|---|---|
| Results show the wrong city | The exit location is not the target city, or other location signals affect Google’s estimate. | Check the provider’s actual exit location and Google’s displayed location. With an API, confirm its documented city setting and proxy-country behavior. |
| A consent page appears | The request needs a consent interaction or session context. | Inspect the returned page. For browser capture, complete the consent flow only where appropriate, then preserve the resulting session context for comparable captures. |
| CAPTCHA or access challenge | Google is challenging the request or browser session. | Do not classify the challenge page as a SERP. Check that your workflow complies with applicable service terms and use a documented SERP API if you need a collection interface. |
| Proxy connection or authentication error | Incorrect endpoint scheme, credentials, or unsupported proxy protocol. | Use the exact endpoint format and authentication method provided by your proxy service; test connectivity independently. |
| Browser navigation times out | Slow proxy response, stalled navigation, or a page that continues loading resources. | Use a bounded timeout, wait for domcontentloaded rather than full network idle, and record the timeout as a failed capture rather than a result. |
Node rejects dispatcher |
The active fetch implementation does not support the proxy agent option. | Use a compatible Node fetch client or the Playwright method, and check the current agent documentation. |
| HTML file is not a results page | The request returned a redirect, consent screen, challenge, or other interstitial. | Inspect HTTP status, final URL, page title, and body before parsing or archiving it as a SERP. |
8. Performance, reliability, and cost
Direct proxy capture involves a network hop and browser rendering when you need a screenshot, so it can take longer and fail at more points than a simple local page load. Use explicit timeouts, save the status and final URL, and distinguish a valid results page from a challenge or blank response. Avoid unlimited retries; retries can waste time and may reproduce the same location or access problem.
For reliable comparisons, keep the device, language, query, and capture procedure fixed, and record the proxy exit and location cues. A city parameter from a SERP provider is a provider control, not a guarantee that Google will always show identical results. The research for this guide does not establish provider prices, capture speed, or location accuracy, so compare current documentation and validate the results you receive.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. It can capture a URL as an image or PDF, but it does not set a Google search city or guarantee city-targeted search results. Use the URL you want captured and separately verify Google’s displayed location. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://www.google.com/search?q=coffee%20shops -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://www.google.com/search?q=coffee%20shops"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://www.google.com/search?q=coffee%20shops' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000 screenshots. Sign up for 1,000 free screenshots a month.
FAQ
Does a proxy alone make Google show a specific city’s results?
No reliable guarantee is established. Google uses multiple location signals; validate the location shown for each capture.
Is adding the city to the query equivalent to searching from there?
No. It expresses local intent in the query, but does not recreate the unmodified search context of a person in that city.
Does gl select a city?
No. In the cited Google Custom Search documentation, gl is a country-level signal. Do not treat it as a city selector for ordinary Google Search.
Can UULE be trusted to override the proxy location?
The research found conflicting third-party claims about direct requests. SerpApi documents UULE and location controls and notes proxy-country influence; Searlo reports tests where results followed request origin. Validate rather than assume. See Searlo’s documentation alongside the SerpApi reference.


