Amazon Scraper API vs Proxy: Choose a Product Data Source
Compare seller APIs, licensed Amazon product data and proxy page checks by record, access scope and reuse rights.
Read guideSet an aiohttp HTTP or SOCKS5 proxy with authentication, then prove it works with a content check and an exit IP. Tested scripts for both routes.

For an HTTP proxy, pass proxy= to each aiohttp request and send credentials with proxy_headers. For a SOCKS5 proxy, aiohttp's proxy= does not work: attach a ProxyConnector from aiohttp-socks to the ClientSession. In both cases, keep trust_env=False and accept a response only when the target body contains an expected marker and an IP check through the same session returns an address.
Both scripts below were run through local HTTP and SOCKS5 test proxies, with and without authentication, and printed the accepted target and the observed address.
Audience: Python developers who can run an asynchronous script. The commands use Bash or Zsh on macOS or Linux.
Warning: Proxy requests can consume paid traffic, and credentials can grant account access. Start with a sandbox allocation and an authorized test target. Keep credentials out of commands, source, and logs; stop the worker and rotate credentials if they appear in output.
| Proxy endpoint | aiohttp setting | Scope |
|---|---|---|
http:// proxy |
proxy= plus proxy_headers= on each request |
Per request; HTTPS targets use CONNECT |
| SOCKS5 proxy | ProxyConnector from aiohttp-socks on the session |
Every request in that session |
socks5:// URL in proxy= |
Not supported | aiohttp speaks HTTP to the SOCKS port and fails |
A local test showed what the last row looks like: session.get(url, proxy="socks5://...") ended with ServerDisconnectedError, not a clear configuration error. That error usually means the scheme, not the network, is wrong.
With an http:// proxy, Basic proxy credentials cross the client-to-proxy hop without encryption. HTTPS on the target does not protect that hop, so use a trusted network between the client and the proxy.
Tested with Python 3.12.0 from the official downloads page and aiohttp 3.14.3:
python3 -m venv .venv
.venv/bin/python -m pip install 'aiohttp==3.14.3'
Set a credential-free HTTP proxy URL and, when required, the proxy username. Replace <PROXY_HOST>, <PROXY_PORT>, and <PROXY_USERNAME> with values from the proxy operator:
export PROXY_URL='http://<PROXY_HOST>:<PROXY_PORT>'
export PROXY_USER='<PROXY_USERNAME>'
Load PROXY_PASSWORD from an approved secret manager before starting the worker. For an endpoint without authentication, leave both PROXY_USER and PROXY_PASSWORD unset. Save this program as aiohttp-proxy-check.py:
import asyncio
import ipaddress
import json
import math
import os
from urllib.parse import urlsplit
import aiohttp
from aiohttp import encode_basic_auth
TARGET_URL = "https://example.com/"
EXPECTED_MARKER = "Example Domain"
IP_CHECK_URL = "https://api.ipify.org?format=json"
MAX_ATTEMPTS = 3
MAX_BACKOFF_SECONDS = 2.0
RETRY_STATUSES = {502, 503, 504}
RETRY_ERRORS = (aiohttp.ClientConnectorError, aiohttp.ServerDisconnectedError, asyncio.TimeoutError)
def proxy_settings() -> tuple[str, dict[str, str] | None]:
proxy_url = os.environ["PROXY_URL"]
endpoint = urlsplit(proxy_url)
if (
endpoint.scheme != "http"
or not endpoint.hostname
or not endpoint.port
or endpoint.username is not None
or endpoint.password is not None
or endpoint.path not in {"", "/"}
or endpoint.query
or endpoint.fragment
):
raise ValueError("PROXY_URL must be a credential-free http:// URL")
username = os.environ.get("PROXY_USER")
password = os.environ.get("PROXY_PASSWORD")
if bool(username) != bool(password):
raise ValueError("Set both proxy username and password, or neither")
headers = None
if username and password:
headers = {"Proxy-Authorization": encode_basic_auth(username, password)}
return proxy_url, headers
async def get_with_retries(
session: aiohttp.ClientSession,
url: str,
proxy_url: str,
proxy_headers: dict[str, str] | None,
) -> tuple[int, str]:
for attempt in range(MAX_ATTEMPTS):
try:
async with session.get(
url,
proxy=proxy_url,
proxy_headers=proxy_headers,
allow_redirects=False,
) as response:
body = await response.text()
if response.status in RETRY_STATUSES and attempt + 1 < MAX_ATTEMPTS:
value = response.headers.get("Retry-After")
try:
server_delay = float(value) if value is not None else 0.0
except ValueError:
return response.status, body
if (
not math.isfinite(server_delay)
or server_delay < 0
or server_delay > MAX_BACKOFF_SECONDS
):
return response.status, body
delay = min(max(server_delay, 0.5 * (2**attempt)), MAX_BACKOFF_SECONDS)
await asyncio.sleep(delay)
continue
return response.status, body
except RETRY_ERRORS:
if attempt + 1 == MAX_ATTEMPTS:
raise RuntimeError("request exhausted its bounded connection retries") from None
await asyncio.sleep(min(0.5 * (2**attempt), MAX_BACKOFF_SECONDS))
raise RuntimeError("request ended without a response")
async def main() -> None:
proxy_url, proxy_headers = proxy_settings()
timeout = aiohttp.ClientTimeout(total=15, connect=5, sock_read=10)
connector = aiohttp.TCPConnector(limit=8, limit_per_host=4)
async with aiohttp.ClientSession(
connector=connector,
timeout=timeout,
trust_env=False,
) as session:
status, body = await get_with_retries(session, TARGET_URL, proxy_url, proxy_headers)
if status != 200 or EXPECTED_MARKER not in body:
raise RuntimeError(f"target check failed with HTTP {status}")
print(f"Target accepted: HTTP {status}; marker found")
status, body = await get_with_retries(session, IP_CHECK_URL, proxy_url, proxy_headers)
if status != 200:
raise RuntimeError(f"IP check failed with HTTP {status}")
ip_data = json.loads(body)
ip_value = ip_data.get("ip") if isinstance(ip_data, dict) else None
if not isinstance(ip_value, str):
raise ValueError("IP endpoint must return a JSON object with a string ip")
address = ipaddress.ip_address(ip_value)
print(f"Observed exit address: {address}")
if __name__ == "__main__":
try:
asyncio.run(main())
except (aiohttp.ClientError, asyncio.TimeoutError, KeyError, ValueError, RuntimeError):
print("Check failed; inspect the redacted failure phase and proxy configuration")
raise SystemExit(1) from None
Run it in the environment that holds the proxy values:
.venv/bin/python aiohttp-proxy-check.py
Expected output. The address is a documentation example, not a captured exit:
Target accepted: HTTP 200; marker found
Observed exit address: 203.0.113.10
The address shows what the IP service saw on this route. It does not establish location, anonymity, or acceptance by the target.
Install the SOCKS connector. Tested with aiohttp-socks 0.12.0, which installed python-socks 3.1.1:
.venv/bin/python -m pip install 'aiohttp-socks==0.12.0'
Set the SOCKS5 host and port. Add PROXY_USER and PROXY_PASSWORD through the secret manager when the endpoint requires them:
export SOCKS_HOST='<PROXY_HOST>'
export SOCKS_PORT='<PROXY_PORT>'
Save this program as aiohttp-socks-check.py. rdns=True sends the target hostname to the proxy for resolution, the same behavior as a socks5h:// URL; the SOCKS5 vs SOCKS5h guide explains when that matters.
import asyncio
import ipaddress
import os
import aiohttp
from aiohttp_socks import (
ProxyConnectionError,
ProxyConnector,
ProxyError,
ProxyTimeoutError,
ProxyType,
)
TARGET_URL = "https://example.com/"
EXPECTED_MARKER = "Example Domain"
IP_CHECK_URL = "https://api.ipify.org?format=json"
def socks_connector() -> ProxyConnector:
# rdns=True sends the target hostname to the proxy (SOCKS5h behavior).
return ProxyConnector(
proxy_type=ProxyType.SOCKS5,
host=os.environ["SOCKS_HOST"],
port=int(os.environ["SOCKS_PORT"]),
username=os.environ.get("PROXY_USER"),
password=os.environ.get("PROXY_PASSWORD"),
rdns=True,
)
async def main() -> None:
timeout = aiohttp.ClientTimeout(total=15, connect=5, sock_read=10)
async with aiohttp.ClientSession(
connector=socks_connector(), timeout=timeout, trust_env=False
) as session:
async with session.get(TARGET_URL, allow_redirects=False) as response:
body = await response.text()
if response.status != 200 or EXPECTED_MARKER not in body:
raise RuntimeError(f"target check failed with HTTP {response.status}")
print(f"Target accepted: HTTP {response.status}; marker found")
async with session.get(IP_CHECK_URL, allow_redirects=False) as response:
if response.status != 200:
raise RuntimeError(f"IP check failed with HTTP {response.status}")
data = await response.json()
address = ipaddress.ip_address(data["ip"])
print(f"Observed exit address: {address}")
if __name__ == "__main__":
try:
asyncio.run(main())
except (
aiohttp.ClientError,
ProxyError,
ProxyConnectionError,
ProxyTimeoutError,
asyncio.TimeoutError,
asyncio.IncompleteReadError,
KeyError,
ValueError,
RuntimeError,
):
print("Check failed; inspect the SOCKS settings and target")
raise SystemExit(1) from None
.venv/bin/python aiohttp-socks-check.py
A successful run prints the same two lines as the HTTP check. A closed port or rejected password prints the short failure line and exits with status 1. The connector applies to the whole session, so create a separate session for each route or rotation lifecycle.
The HTTP helper makes at most 3 attempts per URL for connector errors, server disconnects, timeouts, and HTTP 502, 503, or 504. It caps backoff at 2 seconds and honors numeric Retry-After values only within that cap. It never retries HTTP 407, authorization failures, redirects, or rate-limit responses.
The two checks can make up to 6 proxy requests. Use the helper only for idempotent GET requests. A session timeout applies to each request, not the whole job, so add an outer task deadline for a wall-clock limit. The connector caps the pool at 8 simultaneous connections and 4 per host.
Both programs buffer each body in memory without a byte cap. Use a small authorized test target; for larger responses, switch to streamed reads with an explicit size limit.
trust_env=False keeps environment proxy settings and .netrc from changing the route or credentials. In aiohttp 3.14, send HTTP proxy credentials through proxy_headers with encode_basic_auth(); the older proxy_auth parameter is deprecated.
| Symptom | First check |
|---|---|
| Configuration error | PROXY_URL uses http:// and contains no credentials |
| HTTP 407 | Proxy username, password, and authentication scheme; do not retry the challenge |
ServerDisconnectedError right away |
A SOCKS endpoint passed to proxy=; use the SOCKS5 connector |
| Connector error or timeout | Host, port, DNS, firewall, and timeouts |
| HTTP 3xx, 401, 403, or 429 | Redirect policy, origin authorization, target rules, and rate limits |
| HTTP 200 without the marker | Inspect the body safely; a challenge or empty page is not valid data |
| Invalid IP value | The endpoint must return JSON with a valid ip address |
Keep TLS certificate verification enabled. For a trusted intercepting proxy, configure its approved CA certificate instead of disabling verification.
The aiohttp proxy reference documents route and authentication options, and the client reference covers timeouts and connectors. The aiohttp-socks 0.12.0 source documents the connector. For a synchronous client, compare the HTTPX proxy guide and the Python Requests proxy guide.
Scale your workflows with the tools you already use. Rotating or sticky sessions up to 72 hours.
Compare seller APIs, licensed Amazon product data and proxy page checks by record, access scope and reuse rights.
Read guideChoose the right anti-detect browser proxy guide, map the shared verification steps, and keep account access separate from the network route.
Read guideOur picks for market research: ProxyLane for small, intermittent projects, plus options for regional tracking, monthly collection and managed extraction.
Read guide