Proxies: use the cheapest tier that answers
Direct first, then datacenter, then residential. Residential traffic is billed by the gigabyte, so the ladder is a cost control as much as a technique.
A proxy is not a switch you turn on. It is a bill. Residential proxies in particular are charged by traffic, and a browser run that loads every image and font on every page can spend more on bandwidth than the data is worth.
The ladder#
We try the cheapest tier first and climb only when the page does not come back.
import httpx
LADDER = ("direct", "datacenter", "residential") # proxies["direct"] is None
async def fetch_with_ladder(url, proxies):
for tier in LADDER:
async with httpx.AsyncClient(proxy=proxies.get(tier), timeout=30, headers={"User-Agent": UA}) as client:
resp = await client.get(url)
if resp.status_code == 200 and not looks_blocked(resp.text):
return tier, resp
if resp.status_code == 404:
return tier, None # the page is gone, a better proxy will not bring it back
raise Blocked(f"{url}: no tier got a real page")Two details carry the weight:
- A 404 ends the climb. A better proxy does not bring back a deleted page. Without that line, every dead link in a catalogue would be tried on the most expensive tier.
- "No tier got a real page" raises
Blocked. That is the same exception the wall check uses (see robots.txt first). The run stops and reports; it does not keep trying.
Cheaper than a better proxy: not downloading things#
For browser runs, the biggest saving is blocking images, media and fonts. The data is in the document. The pictures are the bandwidth. Blocking them is a few lines of configuration, and it cuts traffic on residential tiers by more than any proxy negotiation will.
What the platform adds#
Residential proxy is a paid-plan feature on Apify and is billed by traffic. That is one more reason the default input of every actor runs direct: a first run with no proxy should work, and a buyer should turn proxies on on purpose.
robots.txt readers disagree, and the built-in one is the odd one out
Python's built-in reader takes the first matching rule and ignores wildcards. The standard takes the longest match. We ran both on 12,000 random files and listed where they split.
The price is a string: reading money off a web page
Shops print the same number a dozen ways. A careful parser reads what is clear, leaves the rest empty and never fills a gap with a plausible guess.
A timestamp without a zone is a guess
Scraped dates arrive as "2026-03-08 02:30" with nothing else. Some of those times never happened, some happened twice, and the gap between two cities moves during the year.