🎉 Premium Proxies · 24-Hour Free TrialClaim Now
Use Cases

How to Search Google From Another Country

Search Google from another country three ways, ranked by fidelity: URL parameters, a browser behind a country proxy, and a scripted fetch you can verify.

S SparkProxy 4 22 min read
Share

If you need to search Google from another country, the old shortcut is gone: typing google.de or google.com.br now bounces you to google.com, and Google decides what "local" means from signals you mostly don't control. A marketer checking a launch in Germany, a researcher sampling Brazilian results, or an expat who wants the page their family sees all hit the same wall.

This guide ranks the three working methods by how faithfully they reproduce a local searcher's page, shows how to prove a page really is localized instead of trusting it, and finishes with a Python script that captures one query from five countries and lays the results side by side.

Why google.de no longer gives you German results

On 15 April 2025 Google published an update on its country code top-level domains. It said that since 2017 it has given everyone "the same experience with local results" regardless of which domain they typed, and that it "will begin redirecting traffic from these ccTLDs to google.com." The same post warns that you "may be prompted to re-enter some of your Search preferences."

We checked the rollout on 30 October 2026 with plain HTTP requests. Every country domain we tried answered with a 301 to the same place:

Domain requestedStatusRedirect target
`https://www.google.de/`301`https://www.google.com/`
`https://www.google.com.br/`301`https://www.google.com/`
`https://www.google.fr/`301`https://www.google.com/`
`https://www.google.co.uk/`301`https://www.google.com/`
`https://www.google.co.jp/`301`https://www.google.com/`

So the domain is no longer a location control. What's left is what Google's "Manage your location" help page lists as its inputs: your device location, a home or work address saved in your account, your past activity, and your connection's IP address, which Google admits maps to location only roughly. Of those four, only the IP is something you can swap cleanly from outside the country, and that's why two of the three methods below involve a proxy.

The three methods, ranked by fidelity

Fidelity here means one thing: how close the page you capture is to what a logged-out person sitting in that country would see.

MethodWhat it changesWhat still leaks your real locationFidelityGood for
1. URL parameters (`gl`, `hl`)Country hint and interface languageIP, device location, account, cookiesLowA quick look at language and layout
2. Browser behind a country proxyIP, plus the parametersAccount if signed in, browser locale if left at defaultHighManual review, screenshots, ad checks
3. Scripted fetch through a country exitIP, parameters, clean session every timeAlmost nothing, if verifiedHigh and repeatableMonitoring, multi-country comparisons

Methods 2 and 3 reach similar fidelity. The difference is that a script can repeat the same capture across five countries in a minute and check its own output, which a person clicking through tabs won't do reliably.

Free trial

Scraping at scale? Skip the blocks.

Fast, unblockable datacentre proxies with unlimited bandwidth.

Method 1: URL parameters only

Two parameters do the work, and Google describes both on the cse.list page of its Programmable Search developer docs. For gl, the docs say it "boosts search results whose country of origin matches the parameter value." For hl, the job is the interface language. Those definitions are written for the API, but the same parameter names are accepted on the normal results URL:

https://www.google.com/search?q=versicherung+vergleich&gl=de&hl=de
https://www.google.com/search?q=seguro+auto&gl=br&hl=pt-BR

Read the API wording carefully. gl boosts results from a country; it doesn't make Google believe you are in that country. Your IP still says where you are. In practice you get a hybrid page: interface in German, some German sites lifted, but local packs, ads and anything location-sensitive still keyed to your real network.

There's also a settings route. Google's Search settings page (google.com/preferences, checked 30 October 2026) has a "Language and region" section. It changes preferences tied to your browser or account, not your network, so it sits at the same fidelity as the parameters.

We tried Method 1 from our office connection in Bengaluru (an Indian residential ISP) using headless Chromium on 30 October 2026, with gl=de&hl=de, then gl=br&hl=pt-BR, then gl=jp&hl=ja. All three requests were redirected to Google's /sorry/index challenge page. Two details from that small test matter later:

  • The challenge page came back in the language set by hl: German, Portuguese and Japanese respectively. A check that looks only for English block text would have missed all three.
  • The final response our browser recorded for the Japanese attempt was HTTP 200. Status codes alone won't tell you that you were blocked.

Three requests from one IP isn't a block rate, and a headless browser is not a typical visitor. Treat it as a demonstration of what a failure looks like, not a measurement of how often it happens.

Method 2: A browser behind a country proxy

This is the right method when a person needs to look at the page: reviewing ad copy in Brazil, taking screenshots for a German client, or reading a local SERP feature by eye. Change the IP, then remove every other signal that points home.

Use a separate browser profile so your main cookies and Google account never touch the session. Chrome takes a proxy on the command line; the Chromium network settings document gives the form --proxy-server="foopy:8080":

:: Windows example: a throwaway profile, a German exit, German UI language
"C:\Program Files\Google\Chrome\Application\chrome.exe" ^
  --user-data-dir="C:\chrome-geo\de" ^
  --proxy-server="http://gateway.sparkproxy.io:11002" ^
  --lang=de ^
  "https://www.google.com/search?q=versicherung+vergleich&gl=de&hl=de"

Chrome will prompt for the proxy username and password, or you can whitelist your IP in the SparkProxy dashboard and skip the prompt. Full Chrome and Firefox setup, including the authentication popup and PAC files, is in our guide to configuring a proxy in Chrome and Firefox, so we won't repeat it. SparkProxy's browser extension is the no-flags alternative; the homepage pitches it as a way to "Switch countries without leaving your tab."

Country selection on the gateway is done in the username. The Premium IPs sample on sparkproxy.io shows the pattern user-country-us, where user is your username and the suffix is the ISO country code, so a German exit is user-country-de and Brazil is user-country-br. Confirm the exact format in your dashboard before you script it. For the background on how a provider maps an IP to a country, and how far to trust that mapping, see what geo-targeting means in proxies.

Port 11002 in the example is Sticky Sessions, which keeps one exit for 5 minutes. For a person paging through results, that matters: page 2 arriving from a different IP than page 1 looks like a different visitor. Our explainer on sticky session proxies covers the trade-offs.

Before you trust the first page, run this short list in the proxied window:

  1. Open an IP lookup and confirm the country.
  2. Stay signed out of Google.
  3. Accept or reject the consent banner the way a local visitor would, because the EU consent screen is itself part of the German experience.
  4. Search, then scroll to the footer and read the location line.

Method 3: A scripted fetch

When you need the same capture repeated, by country, on a schedule, script it. There are two ways to do that with SparkProxy.

The Scraping API. You pass the Google URL to https://scrape.sparkproxy.io/api/v1, authenticate with an X-API-Key request header, and add country_code. Rendering matters here more than on most targets. On 30 October 2026 we sent a plain HTTP request for a German results URL and got HTTP 200 with about 94 KB of markup and no results at all: a JavaScript stub, rendered in German, saying roughly "click here if you are not redirected in a few seconds." The API's default render_js=true (5 credits) is what gets you a real page; render_js=false (1 credit) gets you the stub.

curl -sG "https://scrape.sparkproxy.io/api/v1" \
  -H "X-API-Key: $SPARKPROXY_API_KEY" \
  --data-urlencode "url=https://www.google.com/search?q=seguro+auto&gl=br&hl=pt-BR" \
  --data-urlencode "country_code=BR" \
  --data-urlencode "json_response=true" \
  -o br.json

Setting json_response=true wraps the result in an envelope whose fields include the upstream status, the credits charged and a body field. That body is Base64-encoded, so every marker check below runs on the decoded string, never the raw field. One trap: format=json does not produce this envelope. The format parameter is a content converter, nothing more.

import base64
import os

import requests

API = "https://scrape.sparkproxy.io/api/v1"


def fetch_serp(google_url, country):
    resp = requests.get(
        API,
        params={"url": google_url, "country_code": country, "json_response": "true"},
        headers={"X-API-Key": os.environ["SPARKPROXY_API_KEY"]},
        timeout=120,
    )
    resp.raise_for_status()
    env = resp.json()
    html = base64.b64decode(env["body"]).decode("utf-8", errors="replace")
    return env["status_code"], env["credits_used"], html

Your own browser through the gateway. If you already run Playwright, point it at the gateway and pass the country in the username. Playwright's docs show the proxy set at launch with server, username and password, or per context:

import os

from playwright.sync_api import sync_playwright

USER = os.environ["SPARKPROXY_USER"]
PASSWORD = os.environ["SPARKPROXY_PASS"]


def capture(google_url, country, locale):
    proxy = {
        "server": "http://gateway.sparkproxy.io:11000",
        "username": f"{USER}-country-{country.lower()}",
        "password": PASSWORD,
    }
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        context = browser.new_context(proxy=proxy, locale=locale)
        page = context.new_page()
        page.goto(google_url, wait_until="domcontentloaded", timeout=60000)
        page.wait_for_timeout(2500)
        html, final_url = page.content(), page.url
        browser.close()
    return final_url, html

The API route is less code and gives you a credit count per capture. The gateway route costs a flat plan instead of credits but makes you own the browsers, their memory, and their fingerprints.

Five checks that prove the page is local

A capture that looks like a results page can still be wrong in two ways: it's a challenge page, or it's a real page for the wrong place. Check both, in this order, cheapest first.

#CheckPass looks likeWhat a failure means
1Final URL path`/search``/sorry/index` means a challenge, whatever the status code
2Language-neutral block markersAbsent`captcha-form` or `g-recaptcha` in the HTML means blocked
3Exit IP countryMatches the targetProxy or username wrong; nothing downstream is valid
4Footer location lineNames the target countryGoogle placed you elsewhere
5Share of local-domain resultsNoticeably above zero for most commercial queriesWeak localization, or a query that is global by nature

Checks 1 and 2 come straight from our own test pages: the German, Portuguese and Japanese challenge pages all lived at /sorry/index, and the Japanese one we saved contains both captcha-form and g-recaptcha. Matching on those beats matching on English sentences, which change with hl.

Check 4 relies on Google's help page, which says each results page ends with a line naming the location Google used and where that guess came from. Check 5 is our own heuristic, not a Google signal: count how many organic results sit on the target country's domain suffix. It's noisy for global brands, which is why it's a hint and not a verdict.

import re
from html.parser import HTMLParser
from urllib.parse import urlparse

BLOCK_MARKERS = ("/sorry/index", "captcha-form", "g-recaptcha")
LOCAL_SUFFIX = {"DE": ".de", "BR": ".br", "FR": ".fr", "JP": ".jp", "US": ".com"}
COUNTRY_WORDS = {
    "DE": ("Deutschland",), "BR": ("Brasil",), "FR": ("France",),
    "JP": ("日本",), "US": ("United States",),
}


class OrganicHosts(HTMLParser):
    """Collect hosts of links that wrap an <h3> title, in page order."""

    def __init__(self):
        super().__init__()
        self.hosts, self._href = [], None

    def handle_starttag(self, tag, attrs):
        if tag == "a":
            self._href = dict(attrs).get("href") or ""
        elif tag == "h3" and self._href and self._href.startswith("http"):
            host = urlparse(self._href).netloc.lower()
            if host and "google." not in host and host not in self.hosts:
                self.hosts.append(host)

    def handle_endtag(self, tag):
        if tag == "a":
            self._href = None


def verify(html, country, final_url=""):
    if any(m in final_url or m in html for m in BLOCK_MARKERS):
        return {"verdict": "blocked", "hosts": []}
    parser = OrganicHosts()
    parser.feed(html)
    hosts = parser.hosts[:10]
    if not hosts:  # JS stub, consent screen or a layout the parser no longer reads
        return {"verdict": "empty", "hosts": []}
    text_tail = re.sub(r"<[^>]+>", " ", html)[-25000:]
    footer_ok = any(w in text_tail for w in COUNTRY_WORDS.get(country, ()))
    suffix = LOCAL_SUFFIX.get(country, "")
    local = sum(1 for h in hosts if suffix and h.endswith(suffix))
    verdict = "local" if footer_ok else "unconfirmed"
    return {"verdict": verdict, "hosts": hosts, "local_share": local / max(len(hosts), 1)}

Google changes its markup often, so the

Picking rotation, country and concurrency

The settings are short, and most of them follow from "one capture equals one independent searcher."

SettingChoiceReason
Rotation for scriptsRandom Rotation (gateway port 11000) or the API's defaultEach capture should be a fresh visitor with no carried cookies
Rotation for peopleSticky Sessions (port 11002, 5 minutes)Pagination and clicks stay on one IP
PoolWorldwide RotatingThe USA Rotating pool is US-only; Germany and Brazil need the worldwide pool
CountrySame code as `gl`A Brazilian exit with `gl=de` sends mixed signals
Language`hl` and browser locale for the market (`de`, `pt-BR`, `ja`)Locale mismatches change the interface and sometimes the results
Concurrency1 to 3 captures per country at onceSmall jobs gain little from bursts and draw more challenges

SparkProxy sells datacenter IPs. Google doesn't publish how it treats datacenter ranges, and we haven't measured a block rate for ours on Google, so we won't quote one. Plan for retries, verify every capture, and if a country keeps failing on the standard pool, the Scraping API's proxy_type=premium is the next lever. If the job grows into thousands of keywords across several engines, read our piece on SERP scraping proxies at scale for the throughput side.

City-level targeting is a separate problem with its own request parameter and its own pitfalls. Country is enough for most marketing, research and expat questions; if you truly need a metro, start with city-targeted proxies.

The five-country side-by-side script

This script runs one query in five markets through the Scraping API, verifies each capture with the checks above, retries challenges, and writes two files: a CSV with every row and an HTML table with one column per country. It reuses fetch_serp, verify and OrganicHosts from the earlier sections, so keep them in the same file.

Decide one thing first: do you want the same string everywhere, or the same intent in each local language? A marketer comparing markets usually wants intent ("car insurance" in Germany is "Kfz-Versicherung"). The QUERIES map handles both; leave a country out and it falls back to the default string.

import csv
import html as htmlmod
import time
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime, timezone
from urllib.parse import urlencode

MARKETS = [
    # (country_code, gl, hl)
    ("DE", "de", "de"),
    ("BR", "br", "pt-BR"),
    ("FR", "fr", "fr"),
    ("JP", "jp", "ja"),
    ("US", "us", "en"),
]
DEFAULT_QUERY = "running shoes"
QUERIES = {"DE": "laufschuhe", "BR": "tênis de corrida", "FR": "chaussures de running",
           "JP": "ランニングシューズ"}


def google_url(query, gl, hl):
    return "https://www.google.com/search?" + urlencode({"q": query, "gl": gl, "hl": hl})


def capture_market(market, attempts=3):
    country, gl, hl = market
    query = QUERIES.get(country, DEFAULT_QUERY)
    url = google_url(query, gl, hl)
    result = {"verdict": "failed", "hosts": [], "local_share": 0.0}
    status, credits = None, 0
    for n in range(attempts):
        try:
            status, used, page = fetch_serp(url, country)
            credits += used
            result = verify(page, country)
        except Exception as exc:  # network error or non-2xx from the API
            result = {"verdict": "error", "hosts": [], "error": str(exc)[:200]}
        if result["verdict"] in ("local", "unconfirmed"):
            break
        time.sleep(15 * (n + 1))
    return {
        "captured_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
        "country": country, "query": query, "status_code": status,
        "credits": credits, **result,
    }


def write_outputs(rows, stem="serp-5-countries"):
    with open(stem + ".csv", "w", newline="", encoding="utf-8") as f:
        w = csv.writer(f)
        w.writerow(["captured_at", "country", "query", "verdict", "position", "host"])
        for r in rows:
            for pos, host in enumerate(r["hosts"], start=1):
                w.writerow([r["captured_at"], r["country"], r["query"], r["verdict"], pos, host])
    head = "".join(f"<th>{r['country']}<br><small>{htmlmod.escape(r['query'])} "
                   f"({r['verdict']})</small></th>" for r in rows)
    body = ""
    for i in range(10):
        cells = "".join(f"<td>{htmlmod.escape(r['hosts'][i]) if i < len(r['hosts']) else ''}</td>"
                        for r in rows)
        body += f"<tr><td>{i + 1}</td>{cells}</tr>"
    with open(stem + ".html", "w", encoding="utf-8") as f:
        f.write(f"
{head}{body}
#
") def overlap(rows): """Print how many top-10 hosts each pair of countries shares.""" for i, a in enumerate(rows): for b in rows[i + 1:]: shared = set(a["hosts"]) & set(b["hosts"]) print(f"{a['country']}-{b['country']}: {len(shared)} shared") if __name__ == "__main__": with ThreadPoolExecutor(max_workers=3) as pool: rows = list(pool.map(capture_market, MARKETS)) write_outputs(rows) overlap(rows) print("credits used:", sum(r["credits"] for r in rows))

A few design choices worth knowing about. Three workers means two countries wait briefly; that's intentional. Retries fire on blocked, empty or error, never on unconfirmed, because retrying a page Google localized differently just spends credits to get the same answer. And the overlap printout is the number marketers usually want first: if Germany and the US share 2 of 10 hosts for the same intent, a single global content plan won't hold.

Save the raw HTML too if disputes are likely. A stored page settles "that's not what I saw" faster than a CSV row.

Running it weekly, and what it costs

Put the script on a schedule and append each run to the same CSV, or load it into SQLite with the captured_at column as the key. A weekly cadence suits most market research; daily makes sense only while you're watching a launch.

# Every Monday 07:00 UTC
0 7 * * 1 cd /opt/serp-geo && /usr/bin/python3 five_countries.py >> logs/run.log 2>&1

Run all five countries in the same window. Results drift during the day, and a German capture from Monday morning compared with a Brazilian one from Wednesday night isn't a market difference, it's two different measurements.

Cost on the Scraping API. A rendered capture is 5 credits and country_code adds 5, so 10 credits each. The plan ladder, in USD per month as listed on sparkproxy.io/scraping-api when we checked on 30 October 2026, with 1,000 free credits on signup:

PlanPrice (USD/month)Credits per monthConcurrent requests
Starter$49250,00050
Growth$991,000,000100
Pro$2493,000,000200
Scale$5998,000,000400

Applied to a few assumed workloads, with 25% added for retries:

Workload (assumed)Captures per monthCredits at 10 each, +25%Fits
1 query, 5 countries, weeklyabout 22about 270Free credits
30 queries, 5 countries, weeklyabout 650about 8,100Starter ($49)
100 queries, 5 countries, dailyabout 15,000about 187,500Starter ($49)
300 queries, 8 countries, dailyabout 72,000about 900,000Growth ($99)

Per the docs, failed requests get their credits back, so real spend should land under those figures. If you switch to proxy_type=premium, the docs' parameter table lists 25 credits with rendering, plus 5 for the country, so roughly triple the figures above.

Cost on the gateway. Proxy plans are packs, not per-GB: "Starting from $75/month" for 100 threads, bandwidth unlimited, so page weight never shows up on the bill. Term discounts are 5%, 10% and 15% for 3, 6 and 12 months, and there's a 24-hour free trial with no credit card. You pay for the servers running the browsers on top.

Symptoms that the SERP is not local

Work through these when a capture passes the block checks but still looks wrong. Each row says plainly whether a proxy can fix it.

Symptom you seeLikely causeCan a proxy fix it?Fix
Page title or URL shows `/sorry/index`Challenge from GoogleYesNew exit, slower pace, retry
Footer names your home countryExit IP is not in the target countryYesCheck the username country suffix and the pool
Interface in English, results German`hl` missing or browser locale left at defaultNoSet `hl` and the browser locale
Results mix two countries`gl` disagrees with the exit countryNoMake `gl` and `country_code` match
Same results as yesterday's personal searchSigned in, or cookies from your main profileNoThrowaway profile, signed out
Consent screen instead of resultsEU consent interstitialNoClick through in the browser, or detect it in scripts
Stub page saying it will redirectPlain fetch without JavaScriptNoUse `render_js=true` or a real browser
Footer correct, local share near zeroThe query is global by natureNoUse the local-language query

If every row checks out and a colleague in Germany still sees something different, find out if they were logged into Google and searching on a phone. Personalization and device type are real, and no outside capture reproduces someone's own history. What you can stand behind is the clean, logged-out page for that country, verified.

For QA teams checking their own site rather than Google, proxies for localization testing applies the same ideas to storefronts and pricing.

Frequently asked questions

FAQ

Partly. Adding gl and hl to the URL changes the interface language and boosts results from that country, but your real IP still tells Google where you are. For a page that matches a local searcher, you need an exit IP in the target country.

No. Google announced on 15 April 2025 that it would redirect country domains to google.com, and on 30 October 2026 we saw google.de, google.com.br, google.fr, google.co.uk and google.co.jp all return a 301 to google.com. Location now comes from signals such as your IP, not the domain.

Google's Programmable Search reference defines gl as the end user's geolocation and says it boosts results whose country of origin matches. hl sets the interface language. Neither changes your IP, so they are hints rather than a location override.

Confirm the final URL is /search and not /sorry/index, check the HTML has no captcha-form or g-recaptcha, and read the location line in the footer. Counting how many top results use the country's domain suffix is a useful extra hint.

Use Random Rotation for scripted captures, so each one is a fresh visitor. Use Sticky Sessions, which hold one IP for 5 minutes, when a person is browsing and paging through results in one window.

With the SparkProxy Scraping API, a rendered country-pinned capture costs 10 credits. Thirty queries in five countries every week is about 8,100 credits a month with retries, inside the $49 Starter plan (prices read 30 October 2026), and a one-off check fits in the 1,000 free credits.

Special Discount · 20% off

Get 20% off your first month

Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.

Save up to 15% more on quarterly, half-yearly and yearly plans

Claim Discount

About the author

This guide comes from the SparkProxy Technical Team. We operate the SparkProxy network, with more than a million premium IPs in over 80 countries over HTTP, HTTPS or SOCKS5 and both Random Rotation and Sticky Sessions, and we build the SparkProxy Scraping API. Quotes about the ccTLD redirect are from Google's blog post of 15 April 2025; the four location inputs are from the "Manage your location" article in Google Search Help; the parameter wording is from the cse.list page in Google's developer docs. The redirect table, the JavaScript stub page and the localized challenge pages were observed by us on 30 October 2026 from a single connection and are described as such, not as rates. Corrections: support@sparkproxy.io.

Keep reading

Related articles