Amazon Scraper API vs Proxy: Choose a Product Data Source
Distinguish authorized Amazon APIs, licensed product data and proxy-based page checks by record quality and access rights.
Read guideConfigure an HTTPX proxy explicitly, keep environment surprises out of a worker, validate the target response, and retry only bounded transient failures.
HTTPX makes a proxy look like one client option, but a production request has several separate decisions: which proxy wins, how long a connection may wait, whether a response is useful, and which failures are safe to retry. The current httpx.Client(proxy=...) option makes the route explicit. Make these decisions before you measure a request as successful.
This guide uses one idempotent GET to https://example.com/. Replace that URL with a permitted target and change the validator to match the record your worker actually needs. A 200 from the target is transport evidence; it is not proof that the page contains an accepted result.
HTTPX's current client API accepts proxy=. It also supports mounts when different schemes need different proxy transports. For one route, an explicit client is easier to audit than relying on ambient settings. trust_env=False prevents HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and related environment settings from silently changing this probe.
Keep the endpoint in a secret binding. The example reads PROXY_URL, never prints it, and never puts a password in a shell command or process argument:
import os
from urllib.parse import urlsplit
import httpx
TARGET = "https://example.com/"
proxy_url = os.environ["PROXY_URL"]
try:
parts = urlsplit(proxy_url)
proxy_port = parts.port
except ValueError:
raise ValueError("PROXY_URL must be a proxy endpoint with a host and port") from None
if (
parts.scheme not in {"http", "https"}
or not parts.hostname
or not proxy_port
or parts.path not in {"", "/"}
or parts.query
or parts.fragment
):
raise ValueError("PROXY_URL must be a proxy endpoint with a host and port")
timeout = httpx.Timeout(15.0, connect=5.0)
limits = httpx.Limits(max_connections=8, max_keepalive_connections=4)
with httpx.Client(
proxy=proxy_url,
trust_env=False,
timeout=timeout,
limits=limits,
follow_redirects=False,
) as client:
try:
response = client.get(TARGET)
except httpx.HTTPError:
raise RuntimeError("initial proxy request failed") from None
if response.status_code != 200:
raise RuntimeError(f"target returned HTTP {response.status_code}")
if "Example Domain" not in response.text:
raise RuntimeError("accepted status, but the expected marker was absent")
print({"status": response.status_code, "response_body_bytes": len(response.content)})
The URL may contain credentials when your deployment injects it from a secret store, but the value must stay out of logs, traces, exception messages, and command history. If the proxy rejects authentication, record a redacted phase such as proxy_auth, not the URL or a raw exception string. An HTTP proxy can carry an HTTPS target through CONNECT; that does not turn a proxy response into a target response.
HTTPX also documents SOCKS support as an optional extra. Install and configure that separately when your provider requires it; do not change a SOCKS endpoint to http://, and do not infer that a particular SOCKS authentication method is available from the HTTP proxy API.
HTTPX applies timeouts for network inactivity by default and exposes separate connect, read, write, and pool controls. The example uses a 15-second inactivity default with a five-second connection budget. That is not a total wall-clock deadline; put an outer deadline around the worker when the job itself has a hard cutoff. The pool limits keep a worker from opening an unplanned number of proxy connections when several tasks share the client.
Tune these values from observed target and proxy behavior. A timeout is a work budget, not a guarantee that the remote server stopped processing. For a write operation, retry only after you have checked that repeating it is safe; the example is deliberately a GET.
HTTPX exposes ConnectError, timeout classes, and ProxyError separately. A bounded retry loop should not treat every exception as transient. In particular, a proxy authentication or CONNECT rejection can surface as a proxy error and will not be repaired by repeating the same credentials. Retry selected connection and read timeouts, plus temporary gateway statuses, and return HTTP 407 for an authentication decision.
import time
RETRY_STATUSES = {502, 503, 504}
RETRY_EXCEPTIONS = (httpx.ConnectError, httpx.ConnectTimeout, httpx.ReadTimeout)
def get_once(client: httpx.Client, url: str, max_attempts: int = 3) -> httpx.Response:
for attempt in range(max_attempts):
try:
response = client.get(url)
except RETRY_EXCEPTIONS:
if attempt + 1 == max_attempts:
raise RuntimeError("request exhausted its bounded transport retries") from None
time.sleep(min(0.5 * (2**attempt), 2.0))
continue
except httpx.ProxyError:
raise RuntimeError("proxy negotiation failed; check endpoint or credentials") from None
if response.status_code in RETRY_STATUSES and attempt + 1 < max_attempts:
retry_after = response.headers.get("retry-after")
if retry_after:
try:
server_delay = float(retry_after)
except ValueError:
return response
if server_delay < 0 or server_delay > 2.0:
return response
else:
server_delay = 0.0
response.close()
time.sleep(min(max(server_delay, 0.5 * (2**attempt)), 2.0))
continue
return response
raise RuntimeError("request did not produce a response") from None
Use the function inside the same with httpx.Client(...) block. Close a response before retrying a status so the pool can reuse its connection cleanly. Keep the maximum attempts and sleep cap visible in configuration. Do not use retries to push through a target rate limit, and do not rotate exits to evade a target's policy. A 429 needs a worker policy that respects the service's delay and job deadline rather than an automatic loop that sleeps without a bound. If a 503 includes Retry-After, the example retries only a numeric delay within its two-second cap; a longer or date-form value is returned to the outer worker policy.
Treat these observations separately:
| Phase | Evidence | Decision |
|---|---|---|
| Route selection | Explicit proxy= and trust_env=False |
The intended client route was selected |
| Proxy handshake | No proxy exception; a 407 is still possible | Check proxy credentials and protocol |
| Target response | Status, final URL, and body marker | Decide whether the target result is usable |
| Worker result | Parsed fields and stable key | Count one accepted record, or record a parser miss |
HTTPX's documented mounts form is useful when HTTP and HTTPS targets require separate routes, but it does not remove the need to validate each target response. For an established proxy route, ProxyLane offers HTTP/SOCKS5 traffic from $2.50/GB, with country, city, ISP, rotating, and sticky options. Traffic does not expire; registration is free and the team follows up about access. Payment is handled separately. Request proxy access after you have a permitted workload and an acceptance check ready.
For a broader shell-level baseline, compare the same target with the curl proxy guide. The client APIs differ, but the useful evidence stays the same: an explicit route, a bounded request, a phase-aware failure, and an accepted output.
Distinguish authorized Amazon APIs, licensed product data and proxy-based page checks by record quality and access rights.
Read guideConnect a buyer-owned proxy to an Apify Actor, keep the session boundary clear, and validate records instead of counting requests.
Read guideSeparate Australian egress from en-AU content, AUD pricing, GST display, postcode validation and the state delivery context.
Read guide