# Proxy dân cư xoay vòng trong Python: requests, httpx và aiohttp

> Proxy dân cư xoay vòng trong Python: requests với retry, httpx đồng bộ và không đồng bộ, aiohttp, phiên sticky và đếm byte. Mã đã kiểm tra cú pháp.

- URL: https://crawlrelay.com/vi/docs/python/
- Cập nhật lần cuối: 2026-10-13
- Publisher: CrawlRelay (https://crawlrelay.com)

Các mẫu production, không phải dòng lệnh đơn: retry tôn trọng Retry-After, IP mới cho mỗi request hoặc một IP cho mỗi flow, concurrency theo host trong asyncio, và byte thực sự tiêu tốn mỗi trang.

**Trả lời nhanh — Làm sao để dùng proxy dân cư xoay vòng trong Python?**

Truyền `http://USERNAME:PASSWORD@gw.crawlrelay.com:7777` làm proxy cho cả `http` và `https` trong requests, làm `proxy=` trong httpx 0.26+, hoặc `proxy=` cho mỗi request trong aiohttp. **CrawlRelay** proxy dân cư xoay vòng trên mỗi kết nối mới và giữ sticky với `-session-<id>` trong username. Giá là 1,50 US$ cho 1 GB và 0,50 US$/GB từ 50 GB. Thanh toán bằng crypto, không cần KYC.

## Cài đặt Python xem qua

| Thuộc tính | Giá trị |
| --- | --- |
| requests | `proxies={"http": url, "https": url}`; retry qua urllib3 `Retry` |
| httpx | `httpx.Client(proxy=url)`; tham số `proxies=` cũ đã bị loại bỏ trong httpx 0.28 |
| aiohttp | `session.get(url, proxy="http://USER:PASS@host:port")`, thông tin xác thực trong URL proxy |
| SOCKS5 | `pip install "requests[socks]"` hoặc `httpx[socks]`, rồi `socks5h://` trên cổng 7778 |
| IP mới mỗi yêu cầu | Tắt keep-alive: `Connection: close`, `max_keepalive_connections=0` hoặc `force_close=True` |
| Cùng IP cho một flow | `-session-<id>-ttl-<minutes>` trong username, tối đa 30 phút |
| Scheme URL proxy | Luôn dùng `http://` cho cổng HTTP, ngay cả với trang web HTTPS |

## Làm sao để dùng proxy với requests cùng retry tôn trọng Retry-After?

Gắn một `HTTPAdapter` với urllib3 `Retry` trên một `Session`. Nó retry lỗi kết nối và các trạng thái bạn liệt kê, đợi theo hàm mũ, và tuân thủ header `Retry-After` mà site gửi cùng 429 và 503. Đặt `trust_env = False` để biến `HTTPS_PROXY` thừa không ghi đè proxy của bạn.

*requests + urllib3 Retry*

```python
import os

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

USER = os.environ.get("CRAWLRELAY_USER", "USERNAME-country-us")
PASSWORD = os.environ.get("CRAWLRELAY_PASSWORD", "PASSWORD")
PROXY = "http://%s:%s@gw.crawlrelay.com:7777" % (USER, PASSWORD)

retry = Retry(
    total=4,
    connect=2,
    backoff_factor=1.0,  # waits of 0 s, 2 s, 4 s, 8 s (Retry-After wins on 429/503)
    status_forcelist=(429, 502, 503, 504),
    allowed_methods=frozenset({"GET", "HEAD"}),
    respect_retry_after_header=True,
)

session = requests.Session()
session.trust_env = False  # ignore HTTP(S)_PROXY from the environment
session.mount("http://", HTTPAdapter(max_retries=retry))
session.mount("https://", HTTPAdapter(max_retries=retry))
session.proxies = {"http": PROXY, "https": PROXY}
session.headers["User-Agent"] = "ExampleCrawler/1.0 (+https://example.com/crawler-info)"
session.headers["Connection"] = "close"  # new connection per request, so usually a new exit IP

r = session.get("https://api.ipify.org", timeout=(10, 30))
print(r.status_code, r.text)
```

## Làm sao để tạo URL proxy an toàn nhắm mục tiêu?

Tạo username trong một hàm và kiểm tra. Lỗi gõ mã quốc gia hoặc ký tự không mã hóa trong mật khẩu dẫn tới lỗi [407](https://crawlrelay.com/vi/docs/errors/407-proxy-authentication-required/) trông như lỗi xác thực. Trợ giúp dưới đây giữ đúng thứ tự tài liệu (`USERNAME-country-{cc}-state-{state}-city-{city}-zip-{zip}-asn-{asn}-session-{id}-ttl-{minutes}`), từ chối key lạ và mã hóa URL cả hai thông tin xác thực.

*Trình tạo username và URL proxy*

```python
import re
from urllib.parse import quote

ORDER = ("country", "state", "city", "zip", "asn", "session", "ttl")
VALUE = re.compile(r"^[a-z0-9_]+$")


def build_username(base, **params):
    """build_username("USERNAME", country="us", city="new_york", session="job1", ttl=30)"""
    parts = [base]
    for key in ORDER:
        value = params.pop(key, None)
        if value is None:
            continue
        value = str(value).lower()
        if not VALUE.match(value):
            raise ValueError("bad value for %s: %r" % (key, value))
        if key == "ttl" and not 1 <= int(value) <= 30:
            raise ValueError("ttl must be 1 to 30 minutes")
        parts.append("%s-%s" % (key, value))
    if params:
        raise ValueError("unknown parameters: %s" % ", ".join(sorted(params)))
    return "-".join(parts)


def proxy_url(username, password, socks=False):
    scheme, port = ("socks5h", 7778) if socks else ("http", 7777)
    return "%s://%s:%s@gw.crawlrelay.com:%d" % (
        scheme, quote(username, safe=""), quote(password, safe=""), port)


print(proxy_url(build_username("USERNAME", country="us", state="ny", city="new_york", session="job1", ttl=20), "p@ss:word"))
```

## Làm sao để có IP mới mỗi request với httpx?

Tắt keep-alive. httpx mặc định gom nhóm kết nối, và kết nối tái sử dụng giữ IP thoát cũ. Với `max_keepalive_connections=0`, mỗi request mở kết nối mới tới gateway và thường thoát ra IP dân cư khác. `num_bytes_downloaded` trả byte nhận được trên đường truyền, gần giống với cách tính cước traffic.

*httpx: client xoay vòng*

```python
import httpx

PROXY = "http://USERNAME-country-gb:PASSWORD@gw.crawlrelay.com:7777"

# No keep-alive: every request opens a new connection, so it usually gets a new exit IP
with httpx.Client(
    proxy=PROXY,
    timeout=httpx.Timeout(30.0, connect=10.0),
    limits=httpx.Limits(max_keepalive_connections=0),
    follow_redirects=True,
) as client:
    for _ in range(3):
        r = client.get("https://api.ipify.org")
        print(r.text, r.num_bytes_downloaded, "bytes on the wire")
```

## Làm sao để crawl đồng thời với asyncio mà không tấn công cùng một site?

Giới hạn concurrency **theo host**, không chỉ tổng thể. Giới hạn tổng 50 cho phép 50 request cùng một site nếu danh sách URL sắp theo domain. Semaphore theo hostname giữ mỗi site ở mức lịch sự trong khi toàn bộ crawl vẫn nhanh. Bắt đầu 2 đến 4 mỗi host và xem [hướng dẫn concurrency](https://crawlrelay.com/vi/docs/concurrency/) trước khi tăng.

*httpx.AsyncClient với semaphore theo host*

```python
import asyncio
from collections import defaultdict
from urllib.parse import urlsplit

import httpx

PROXY = "http://USERNAME-country-us:PASSWORD@gw.crawlrelay.com:7777"
PER_HOST = 4  # parallel requests per target host
host_slots = defaultdict(lambda: asyncio.Semaphore(PER_HOST))


async def fetch(client, url):
    async with host_slots[urlsplit(url).hostname]:
        try:
            r = await client.get(url)
            return url, r.status_code, r.num_bytes_downloaded
        except httpx.HTTPError as exc:
            return url, type(exc).__name__, 0


async def main(urls):
    limits = httpx.Limits(max_connections=50, max_keepalive_connections=0)
    timeout = httpx.Timeout(30.0, connect=10.0)
    async with httpx.AsyncClient(proxy=PROXY, limits=limits, timeout=timeout) as client:
        for result in await asyncio.gather(*(fetch(client, u) for u in urls)):
            print(*result)


asyncio.run(main(["https://example.com/", "https://example.org/", "https://example.net/"]))
```

## Làm sao để dùng proxy với aiohttp?

Truyền gateway làm `proxy=` trên mỗi request, với thông tin xác thực trong URL proxy. Cách này hoạt động với mọi bản aiohttp 3.x mà không cảnh báo; `BasicAuth` và tham số `proxy_auth=` đã deprecated từ aiohttp 3.14 và sẽ bị bỏ ở 4.0. Một `TCPConnector(force_close=True)` đóng mọi kết nối sau phản hồi, mỗi request thường có IP mới. Nếu không, aiohttp tái sử dụng kết nối và bạn giữ cùng IP một thời gian.

*aiohttp với thông tin xác thực trong URL proxy*

```python
import asyncio

import aiohttp

# Credentials go in the proxy URL; percent-encode a password that contains @ or :
PROXY = "http://USERNAME-country-fr:PASSWORD@gw.crawlrelay.com:7777"


async def main():
    # force_close=True: one connection per request, so each request usually gets a new exit IP
    connector = aiohttp.TCPConnector(limit=20, force_close=True)
    timeout = aiohttp.ClientTimeout(total=60, sock_connect=10)
    async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
        for _ in range(3):
            async with session.get("https://api.ipify.org", proxy=PROXY) as resp:
                print(resp.status, await resp.text())


asyncio.run(main())
```

## Làm sao giữ một IP cho flow nhiều bước?

Tạo một session ID cho mỗi flow rồi đặt trong username với TTL dài hơn flow. Keep-alive tốt ở đây: bạn muốn cùng IP thoát. Nếu thiết bị dân cư sau session offline, IP có thể đổi trước TTL hết, nên làm flow có thể khởi động lại từ checkpoint cuối.

*Sticky session với requests*

```python
import uuid

import requests

session_id = uuid.uuid4().hex[:10]  # one ID per flow, lowercase letters and digits
user = "USERNAME-country-us-state-ca-session-%s-ttl-20" % session_id
proxy = "http://%s:PASSWORD@gw.crawlrelay.com:7777" % user

with requests.Session() as s:
    s.proxies = {"http": proxy, "https": proxy}
    first = s.get("https://api.ipify.org", timeout=(10, 30)).text
    second = s.get("https://api.ipify.org", timeout=(10, 30)).text
    print(first, second, "same exit" if first == second else "exit changed")
```

## Một trình scraper Python dùng bao nhiêu traffic mỗi trang?

Đo trên 100 trang thật trước khi mua dung lượng. Ghi lại `num_bytes_downloaded` trong httpx, hoặc độ dài body nén trong middleware bạn tự làm, rồi nhân với số trang. Yêu cầu HTML thường tốn vài chục KB mỗi trang; [hướng dẫn băng thông](https://crawlrelay.com/vi/guides/bandwidth-calculator/) cho số liệu và chi phí cho 1.000 trang với giá CrawlRelay.

Ba thói quen giảm traffic mà không thay kết quả:

- Gửi `Accept-Encoding: gzip, br` (httpx và requests mặc định gửi gzip) để trang nén khi truyền.
- Lấy endpoint JSON mà trang tải khi site cho phép hoặc có tài liệu, thay vì HTML đầy đủ.
- Bỏ qua trang không đổi với `If-None-Match` hoặc `If-Modified-Since`: một `304 Not Modified` chỉ tốn vài trăm byte.

## Python và proxy dân cư: các câu hỏi

### requests, httpx hay aiohttp: tôi nên dùng cái nào để thu thập dữ liệu?

Dùng requests cho các script đơn giản và crawl nhỏ. Dùng httpx khi bạn muốn cùng một API cho code đồng bộ và bất đồng bộ, và hỗ trợ HTTP/2 qua `httpx[http2]`. Dùng aiohttp cho độ đồng thời rất cao trong asyncio thuần túy. Với crawl có lập lịch, loại trùng và pipeline, dùng [Scrapy](https://crawlrelay.com/vi/docs/scrapy/) thay vì tự viết các phần đó.

### Tại sao tôi lại nhận lỗi SSL: WRONG_VERSION_NUMBER khi dùng proxy trong Python?

Có thể bạn đã viết `https://` trước địa chỉ proxy. Cổng 7777 của gateway dùng HTTP thuần và mở tunnel CONNECT cho các site HTTPS, nên URL proxy phải bắt đầu bằng `http://` trong cả khóa `http` và `https`. Xem [trang lỗi](https://crawlrelay.com/vi/docs/errors/ssl-wrong-version-number/).

### Làm sao để dùng proxy SOCKS5 trong Python?

Cài thêm gói (`pip install "requests[socks]"` hoặc `pip install "httpx[socks]"`), rồi dùng `socks5h://USERNAME:PASSWORD@gw.crawlrelay.com:7778`. `h` gửi hostname đến proxy, DNS phân giải ở cửa ra nên bạn sẽ nhận được kết quả vùng miền giống như người dùng địa phương.

### Tại sao proxy di động 4G quay vòng của tôi trả lại IP trùng lặp?

Client của bạn đã tái sử dụng kết nối giữ sống, trong khi quay vòng xảy ra trên mỗi kết nối mới. Gửi `Connection: close` trong requests, đặt `max_keepalive_connections=0` trong httpx hoặc dùng `force_close=True` trong aiohttp. Cũng kiểm tra xem username có phần `session-` không, phần này cố định một IP.

### Làm thế nào để thiết lập timeout cho các yêu cầu qua proxy?

Thiết lập rõ ràng vì requests không có timeout mặc định. Timeout kết nối khoảng 10 giây và timeout đọc 30 giây phù hợp cho trang HTML; các exit di động và mục tiêu chậm cần từ 45 đến 60 giây. Thử lại 1 lần khi timeout trên kết nối mới thay vì chờ lâu hơn. Xem [cách timeout đọc](https://crawlrelay.com/vi/docs/errors/read-timeout/).

### Tôi có thể dùng các proxy này trong Jupyter hoặc với pandas không?

Có thể. Bất cứ thứ gì dùng requests hoặc httpx bên dưới đều dùng được proxy dân cư. Với `pandas.read_html` hoặc `read_csv` trên URL, tải nội dung bằng requests qua proxy trước, rồi truyền text hoặc bytes sang pandas.

**Chạy scraper Python của bạn qua IP dân cư** 1,50 US$ cho 1 GB, 0,50 US$/GB từ 50 GB. [Xem giá](https://crawlrelay.com/vi/pricing/) · [Hướng dẫn Scrapy](https://crawlrelay.com/vi/docs/scrapy/)

---

CrawlRelay: CrawlRelay là dịch vụ proxy dân cư xoay vòng và proxy di động 4G/5G dành cho nhà phát triển, hỗ trợ scrapers và pipeline dữ liệu AI: traffic trả tiền theo nhu cầu không hết hạn từ 0,50 US$/GB, thanh toán bằng crypto, không cần tải lên ID. Không cần KYC — không cần ID, không chụp selfie, không giấy tờ.
