SERP tracking by country
Search results differ by country. Check rankings from an exit IP in each market you track, at a steady, modest rate, and keep the history.
Proxy URL, exit IP in United Kingdom
http://USERNAME-cc-GB:PASSWORD@scale-eu.netdash.io:2000Residential exit IPs in United Kingdom
Illustrative routes, not live traffic. Exits are available in most countries; the countries shown are examples.
- Country-level targeting by ISO code
- Mobile exits on the same syntax
- Rotating sessions for independent queries
SERP tracking proxies for international rankings
A rank check that runs from your office or a cloud server shows the results for that location. If you report rankings for several countries, each query has to come from the market it measures. SERP tracking proxies give every query an exit IP in the country you name, so the results page you parse is the version served to that country, not to your data center.
With netdash, the country is part of the proxy username: USERNAME-cc-DE for Germany, USERNAME-cc-GB for the United Kingdom. One username and password work on every residential and mobile plan, so the same code covers desktop and mobile checks.
What changes between markets
Three things decide which results page you get. The exit country decides the market. The search engine’s own language and region settings, where it offers them, decide the interface language. The device type changes the layout and sometimes the order of results, so desktop and mobile rankings are tracked separately.
Targeting is country-level. You can’t pick a city or region, so city-level local results are out of scope and the results you collect reflect the country as a whole. The locations page shows both gateways, and country targeting explains how to choose the regional gateway for an exit country.
- Exit country:
-cc-plus an ISO 3166-1 alpha-2 code,GBnotUK - Interface language: the engine’s language setting and an
Accept-Languageheader - Device: a desktop client, or a mobile user agent and viewport on the Mobile plan
- Locations below country level: not available
Before you build
Many search engines restrict automated queries in their terms and offer official APIs or licensed data. Check the terms and robots.txt of each engine you query and use an official option where it fits. Keep the rate low and the volume to what your report needs. netdash does not solve CAPTCHAs: if an engine challenges your requests, slow down or stop. See how we expect netdash to be used.
A daily rank check, step by step
Step 1: Define the matrix
Keywords × markets × devices, and the domain whose position you track. Each combination is one row.
Step 2: Check the engine’s terms
Use an official API or licensed data where it covers your needs. Query results pages only where the terms allow it.
Step 3: Configure each row
Exit country via
Country targeting-cc-, the engine’s language setting, and a desktop or mobile client.Step 4: Query at a steady rate
One results page per keyword per day, spread over the day, on rotating sessions. No bursts.
Step 5: Parse and store
The position of your domain, the URL that ranks, market, device and date.
Step 6: Report movement
Day-over-day changes per market, with an alert when a tracked position falls.
Keyword × market × device matrix
One row per market and device. The examples use the Scale plan: every residential plan uses the same syntax, and you switch plans by changing the host and port. Every row uses the same proxy password from the dashboard; the proxy string format explains each part.
- Desktop, United States
scale-us.netdash.io:2000·USERNAME-cc-USRotating, through the Americas gateway.
- Desktop, Germany
scale-eu.netdash.io:2000·USERNAME-cc-DERotating, through the Europe gateway.
- Desktop, United Kingdom
scale-eu.netdash.io:2000·USERNAME-cc-GBRotating. Use
GB, notUK.- Mobile, United Kingdom
mobile-eu.netdash.io:4000·USERNAME-cc-GBRotating, with a mobile user agent and viewport set in your client.
- Session mode
Rotating: no session parameters
The exit IP isn’t pinned. Requests sent through one HTTPS tunnel share that tunnel’s exit IP.
- More markets
One row per ISO country code
To confirm availability for a specific country, contact support.
Generate the settings for one market
Change the country, plan or gateway and copy the result into your rank checker’s configuration.
Your route
Through the gateway to an exit IP in , , on the plan.
Proxy URL
http://USERNAME-cc-GB:PASSWORD@scale-eu.netdash.io:2000Replace USERNAME and PASSWORD with the credentials from your dashboard.
Scale, Europe gateway, exit IP in United Kingdom, rotating.
curl --max-time 30 -x "http://USERNAME-cc-GB:PASSWORD@scale-eu.netdash.io:2000" \
"https://api.ipify.org?format=json"import requests
proxy = "http://USERNAME-cc-GB:PASSWORD@scale-eu.netdash.io:2000"
proxies = {"http": proxy, "https": proxy} # set both, or HTTPS requests bypass the proxy
response = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=30)
response.raise_for_status()
print(response.text)import { ProxyAgent, fetch } from "undici";
const dispatcher = new ProxyAgent({
uri: "http://scale-eu.netdash.io:2000",
token: "Basic " + Buffer.from("USERNAME-cc-GB:PASSWORD").toString("base64"),
});
const res = await fetch("https://api.ipify.org?format=json", { dispatcher, signal: AbortSignal.timeout(30_000) });
console.log(await res.text());import { chromium } from "playwright";
const browser = await chromium.launch({
proxy: {
server: "http://scale-eu.netdash.io:2000",
username: "USERNAME-cc-GB",
password: "PASSWORD",
},
});
const page = await browser.newPage();
await page.goto("https://api.ipify.org?format=json");
console.log(await page.textContent("body"));
await browser.close();Example output
{"ip":"203.0.113.10"}Your output shows a different address in the country you chose.
Finding your position in a results page
One httpx.Client per query, with the exit country in the username and no session parameters, so every query stands alone. position() returns the 1-based rank and URL of the first result on your domain. The parser here only collects links in page order: replace it with one that reads the organic results of the page you query.
The search URL is an example. Point it at a source whose terms allow automated queries, or use the engine’s official API. httpx 0.28 and later take proxy=; the older proxies= argument was removed.
- A fixed pause between queries; a
403or429stops the run instead of retrying - The password is URL-encoded because it sits inside the proxy URL
- Results that need JavaScript: render them with Playwright
- More on clients in the Python guide and the Go guide
import json
import os
import time
from datetime import date
from html.parser import HTMLParser
from urllib.parse import quote, quote_plus, urlparse
import httpx
USER = os.environ["NETDASH_USERNAME"]
PASS = quote(os.environ["NETDASH_PASSWORD"], safe="") # encoded: it sits inside a URL
# Market -> gateway and Accept-Language. US pairs with the Americas gateway, DE and GB with Europe.
MARKETS = {
"US": ("scale-us.netdash.io:2000", "en-US,en;q=0.8"),
"DE": ("scale-eu.netdash.io:2000", "de-DE,de;q=0.8"),
"GB": ("scale-eu.netdash.io:2000", "en-GB,en;q=0.8"),
}
SEARCH_URL = "https://search.example/search?q={query}" # example host: use a source that allows it
DOMAIN = "yourdomain.example"
KEYWORDS = ["project management software", "invoice template"]
PAUSE_SECONDS = 90 # a steady pace; spread the daily run over the day
class ResultLinks(HTMLParser):
"""Collects absolute links in page order. Replace with a parser for your results page."""
def __init__(self):
super().__init__()
self.links = []
def handle_starttag(self, tag, attrs):
href = dict(attrs).get("href") or ""
if tag == "a" and href.startswith("http"):
self.links.append(href)
def client_for(cc):
host, lang = MARKETS[cc]
# Rotating: no session parameters, so every query stands alone.
proxy = f"http://{USER}-cc-{cc}:{PASS}@{host}"
return httpx.Client(proxy=proxy, timeout=30, headers={"Accept-Language": lang})
def position(html, domain):
"""1-based position and URL of the first result on `domain`, or (None, None)."""
parser = ResultLinks()
parser.feed(html)
for rank, link in enumerate(parser.links, start=1):
host = urlparse(link).hostname or ""
if host == domain or host.endswith("." + domain):
return rank, link
return None, None
for keyword in KEYWORDS:
for cc in MARKETS:
with client_for(cc) as client:
# A wrong username or password fails here with httpx.ProxyError (407).
response = client.get(SEARCH_URL.format(query=quote_plus(keyword)))
if response.status_code in (403, 429):
raise SystemExit(f"{cc}: HTTP {response.status_code}. Stop, then review volume and terms.")
response.raise_for_status()
rank, url = position(response.text, DOMAIN)
print(json.dumps({"date": date.today().isoformat(), "market": cc, "device": "desktop",
"keyword": keyword, "position": rank, "url": url}))
time.sleep(PAUSE_SECONDS)Check your own international SEO
The same exits work for your own site. Request your pages from each market and confirm that visitors get the right country version, that your hreflang alternates point where you expect, and that geo-redirects don’t send people who chose another country version back where they started.
For a full test setup with assertions per country, see localization testing. To check how your own ads and landing pages render per market, see ad verification.
Which product to query from
Residential proxies
Self-serveDesktop results per country. Each query is independent, so rotating sessions fit. The examples use the Scale plan.
From $1.20/GB, graduated per order
Mobile proxies
Self-serveMobile results from mobile exit IPs, paired with a mobile user agent and viewport in your client. Carrier, city and network generation can’t be selected.
From $3.50/GB, graduated per order
SERP tracking questions
No. Targeting is country-level, so the results you collect reflect the country, not a city or region.
Many restrict automated queries in their terms and offer official APIs or licensed data instead. Check the terms of each engine before you build and use an official option where it fits.
Whether you can collect a given dataset depends on the terms, the data and your jurisdiction; get legal advice for your case.
Rotating. Each query stands alone, so leave out the session parameters and the exit IP isn’t pinned. Requests sent through one HTTPS tunnel share that tunnel’s exit IP; when consecutive requests must share an exit IP, use a sticky session instead.
netdash does not solve CAPTCHAs or other challenges, and your code shouldn’t try to get past them. Slow down, reduce volume, or switch to an official data source.
Use the Mobile plan (mobile-eu.netdash.io:4000 or mobile-us.netdash.io:4000) for mobile exit IPs, and set a mobile user agent and viewport in your client. Credentials and username syntax are the same as on the residential plans.
Track your first market
Run one keyword in one market first and check the parsed position by hand, then build out the matrix. One username and password cover the Scale and Mobile plans.