
Best residential proxies for scraping: why 200 OK is not enough
Last month I spent a day and a half (i know!) debugging a scraper that wasn't broken. The logs said 200 OK all the way down; the output had a German page priced in dollars, a "student price" that was actually retail after a silent bounce, and a 99,90 € "price" that was the monthly financing installment, not the laptop. Every request "succeeded." No row was usable though.
A status code only proves the server said something. Pages redirect silently, geos fall back, currencies switch, prices render client-side into a valid empty shell, all under a cheerful 200. So I validate content, not connections. For this I reached for residential proxies for geo-testing from Proxy-Seller, partly because they were already set up, but mostly because localized pages only behave honestly when the IP looks like a real local visitor.
Picking a target
First idea: Apple. But apple.com kills a plain requestsclient at the TLS handshake: no status, no HTML, nothing to parse. microsoft.com, same story with better manners. Not the proxies' fault, the same IPs fetched everything else instantly. Big storefronts fingerprint the handshake itself, and Python's doesn't look like Chrome's, whatever the User-Agent claims. Getting past that means TLS-fingerprint impersonation tooling, and I didn't want to go deeper, cuz I'd rather be a good citizen and pick a site that doesn't mind. :D
Lenovo doesn't: public store pages served whole to a script, plus a student storefront everywhere: us/en/d/deals/student/, and d/student-laptops/shop-by-grade/ under gb/en, de/de, fr/fr (also pt/pt and friends).
The test
One product, the ThinkPad X1 Carbon, same /p/... path in every country store, through proxies in 4 countries, on the regular store and the student store, where silent redirects and almost-prices hide. Each response is checked for status and final URL, block markers, the expected currency, the right product with a machine-readable price (from Lenovo's schema.org JSON-LD buy-box offer, not a money-shaped regex match), and, on the edu channel, proof it's an education page with a usable price, not a 1,00€ placeholder or /mo. installment. Only then: valid_scrape: true.
The core of the validation, trimmed to the essential lines:
# price from schema.org JSON-LD buy-box offer, not a money-shaped regex
def ld_offer(raw):
for m in LDJSON.finditer(raw):
data = json.loads(m.group(1))
for it in (data if isinstance(data, list) else [data]):
if it.get("@type") == "Product" and (it.get("offers") or {}).get("price"):
o = it["offers"]
return it.get("name", ""), float(o["price"]), o.get("priceCurrency", "")
return "", None, ""
r = s.get(url, headers=UA, timeout=60) # s.proxies set per country
name, price, iso = ld_offer(r.text)
row = {
"status": r.status_code,
"final_url": r.url, # catch silent redirects
"currency_ok": iso == cfg["iso"], # right currency for the country
"price": price or "", # empty when the page has none
}
row["valid_scrape"] = (row["status"] == 200 and row["currency_ok"]
and bool(row["price"]))
* Trimmed for readability, full runnable script here.
What came back
So, was 200 enough?
Nope. Two flavors of failure.
The loud one. The US store returned 403 Access Denied on the product page, while the same IP fetched the US student page seconds later. The IP isn't the problem, residential Spectrum, Alabama, as legit as traffic gets; /p/ pages just run a stricter bot policy than /d/, judging client, not address. At least a 403 shows in logs.
The quiet one. All four student pages returned 200, say "student" in the right language, show the right currency, weigh up to 1.2 MB, and contain zero prices. Student pricing renders client-side; the HTML on the wire is a valid, offer-free shell.
Check only "200 + currency symbol" and you log eight successes tonight and an empty half-dataset at report time. The sole price-shaped string in the German page: 1,00€, a placeholder begging a sloppy regex to call it a student price.
The passing rows are the payoff: the same ThinkPad costs £2,560.00 in the UK, €2,345.49 in Germany, €2,528.10 in France, a €180 spread between two eurozone stores. That signal only exists if every request truly exits in the right country, and the proxies were flawless: correct geo, sub-second warm responses (0.21 s from Germany!), full pages wherever scripts were welcome. Every failure above is content or site policy, never the network.
The fix isn't clever, just specific: check the final URL, demand the expected currency, prefer JSON-LD offers over regex matches, make an edu page prove it, and let "no price in the HTML" be a result your pipeline can express. Five cheap assertions turn invisible failures into loud ones.
And also, picking the right residential proxies matters just as much as the validation itself. On that front, my proxies kept the network layer boring enough that every failure I caught was a content problem, not a block. After enough runs like this, I'd say Proxy-Seller offers some of the best residential proxies for scraping I've used. It's still my go-to for this kind of work.
So, tell me, do you validate beyond the status code? My dumbest "200 OK but useless" story is now a 1.2-megabyte student page without a single price in it. What's yours?