Thư viện httpx đã trở thành tiêu chuẩn cho các yêu cầu HTTP bất đồng bộ trong Python, nhưng khi làm việc với proxy, nó có những điểm cần lưu ý có thể làm hỏng các trình phân tích trong sản xuất. Chúng ta sẽ phân tích cách cấu hình xoay vòng proxy bất đồng bộ, bật HTTP/2 và lý do tại sao tập lệnh của bạn lại gặp sự cố sau mỗi 50-100 yêu cầu.
Tại sao httpx, không phải requests hoặc aiohttp
requests là đồng bộ và không hỗ trợ HTTP/2 ngay từ đầu. aiohttp là bất đồng bộ, nhưng cấu hình proxy ở đó kém thân thiện hơn, và hỗ trợ HTTP/2 hoàn toàn không có. httpx giải quyết cả hai vấn đề: khách hàng đồng bộ và bất đồng bộ với API duy nhất, hỗ trợ natively HTTP/2 thông qua mở rộng h2, và làm việc linh hoạt với proxy ở cấp độ giao thức.
Đối với các nhiệm vụ phân tích từ các thị trường (Wildberries, Ozon, Avito), thu thập dữ liệu từ API mạng xã hội hoặc giám sát giá cả của đối thủ, tính bất đồng bộ là rất quan trọng — nó mang lại tốc độ tăng vọt so với các yêu cầu tuần tự. Nhưng nếu không có xoay vòng proxy hợp lý và quản lý kết nối, tính bất đồng bộ trở thành nguồn gốc của những vấn đề mới: cạn kiệt nhóm kết nối, các coroutine bị treo, rò rỉ mô tả tệp.
Điểm quan trọng thứ hai — HTTP/2 có khả năng đa phân bổ nhiều yêu cầu trong một kết nối TCP, điều này giảm tải cho máy chủ mục tiêu và giúp vượt qua một số hệ thống chống bot đơn giản, những hệ thống này nhìn vào số lượng kết nối TCP từ một IP.
Cấu hình cơ bản cho proxy trong httpx
Từ phiên bản httpx 0.26+, cú pháp cấu hình proxy đã thay đổi — thay vì từ điển proxies, sử dụng tham số proxy. Đây là nguồn gốc thường gặp của sự nhầm lẫn nếu bạn xem các hướng dẫn cũ.
import httpx
import asyncio
PROXY_URL = "http://user:[email protected]:8000"
async def fetch(url: str):
async with httpx.AsyncClient(proxy=PROXY_URL, timeout=15.0) as client:
response = await client.get(url)
return response.status_code, response.text[:200]
asyncio.run(fetch("https://httpbin.org/ip"))
Nếu cần phân chia proxy cho lưu lượng HTTP và HTTPS hoặc cấu hình các proxy khác nhau cho các miền khác nhau, hãy sử dụng httpx.Client(mounts=...) — các giao thức được gắn theo mẫu URL:
mounts = {
"http://": httpx.AsyncHTTPTransport(proxy="http://user:[email protected]:8000"),
"https://": httpx.AsyncHTTPTransport(proxy="http://user:[email protected]:8000"),
}
async with httpx.AsyncClient(mounts=mounts) as client:
response = await client.get("https://example.com")
Đối với xác thực bằng tên đăng nhập và mật khẩu, định dạng http://user:pass@host:port hoạt động ổn định cho proxy HTTP. Đối với SOCKS5, bạn cần cài đặt thêm httpx[socks], nếu không bạn sẽ gặp lỗi ImportError trong thời gian chạy, chứ không phải ở giai đoạn viết mã — đây là cái bẫy mà nhiều người gặp phải.
Xoay vòng proxy bất đồng bộ: mã hoạt động
Lỗi chính trong xoay vòng proxy là tạo một AsyncClient mới cho mỗi yêu cầu. Điều này làm giảm hiệu suất, vì khách hàng giữ một nhóm kết nối, và việc tái tạo nó làm mất hết lợi ích của các kết nối keep-alive. Cách tiếp cận đúng là tạo một nhóm khách hàng trước, một cho mỗi proxy, và phân phối nhiệm vụ giữa chúng.
import httpx
import asyncio
import itertools
import random
PROXIES = [
"http://user:[email protected]:8000",
"http://user:[email protected]:8000",
"http://user:[email protected]:8000",
]
class ProxyPool:
def __init__(self, proxies: list[str]):
self.clients = [
httpx.AsyncClient(proxy=p, timeout=httpx.Timeout(15.0, connect=5.0))
for p in proxies
]
self._cycle = itertools.cycle(self.clients)
def get_client(self) -> httpx.AsyncClient:
return next(self._cycle) # round-robin
# để chọn ngẫu nhiên: random.choice(self.clients)
async def close_all(self):
for client in self.clients:
await client.aclose()
async def fetch_with_retry(pool: ProxyPool, url: str, retries: int = 3):
for attempt in range(retries):
client = pool.get_client()
try:
response = await client.get(url)
if response.status_code == 200:
return response.text
except (httpx.ConnectTimeout, httpx.ProxyError, httpx.ReadTimeout):
await asyncio.sleep(0.5 * (attempt + 1))
continue
return None
async def main():
pool = ProxyPool(PROXIES)
urls = [f"https://example.com/page/{i}" for i in range(100)]
sem = asyncio.Semaphore(20) # giới hạn độ song song
async def bound_fetch(url):
async with sem:
return await fetch_with_retry(pool, url)
results = await asyncio.gather(*(bound_fetch(u) for u in urls))
await pool.close_all()
return results
asyncio.run(main())
Mẫu này giải quyết ba vấn đề ngay lập tức: tái sử dụng các kết nối TCP thông qua keep-alive, phân phối tải cho các proxy theo cách round-robin và giới hạn độ song song thông qua semaphore, để không làm quá tải máy chủ mục tiêu và không gây ra cấm theo giới hạn tỷ lệ.
Đối với các trình phân tích sản xuất làm việc với Wildberries, Ozon hoặc Instagram API, điều quan trọng là nhóm proxy phải đủ lớn và đa dạng về địa lý — nếu không, việc xoay vòng trong cùng một mạng con sẽ không cứu được bạn khỏi bị chặn. Ở đây, các proxy dân cư rất phù hợp, vì chúng có IP của người dùng thực và ít bị lọc bởi các hệ thống chống bot, những hệ thống này phân tích ASN và danh tiếng của dải IP.
HTTP/2 trong httpx: tại sao và cách bật
Mặc định httpx sử dụng HTTP/1.1. Để bật HTTP/2, bạn cần cài đặt mở rộng và truyền rõ ràng cờ:
# pip install httpx[http2]
async with httpx.AsyncClient(http2=True, proxy=PROXY_URL) as client:
response = await client.get("https://example.com")
print(response.http_version) # mong đợi "HTTP/2"
Một điểm quan trọng: không phải tất cả các proxy đều hỗ trợ tunneling HTTP/2 một cách chính xác. Một số proxy từ các trung tâm dữ liệu giá rẻ chỉ hoạt động như proxy HTTP/1.1 ngay cả khi máy chủ cuối hỗ trợ HTTP/2 — trong trường hợp đó, httpx sẽ lặng lẽ quay lại HTTP/1.1 thông qua tunnel CONNECT, và bạn không phải lúc nào cũng nhận ra điều này mà không kiểm tra rõ ràng response.http_version.
HTTP/2 mang lại lợi ích thực sự trong ba kịch bản: khi bạn thực hiện nhiều yêu cầu đến một miền (đa phân bổ trong một kết nối giảm độ trễ), khi quan trọng để giảm độ nhận diện của các yêu cầu cho bảo vệ chống bot (ít kết nối TCP từ một IP trông tự nhiên hơn), và khi trang web mục tiêu yêu cầu HTTP/2 và trả về lỗi hoặc nội dung bị cắt cho các khách hàng trên HTTP/1.1 — điều này đôi khi xảy ra với các trang web trên Cloudflare.
Đối với các nhiệm vụ mà tốc độ và độ ổn định của các kết nối HTTP/2 qua proxy là quan trọng, các proxy từ trung tâm dữ liệu là lựa chọn tốt hơn — chúng nhanh hơn các proxy dân cư và thường hỗ trợ chính xác việc tunneling các giao thức hiện đại.
5 lỗi điển hình với proxy trong httpx
1. Tạo lại khách hàng cho mỗi yêu cầu
Mã dạng async with httpx.AsyncClient(...) as client: await client.get(url) bên trong vòng lặp — đây là mẫu phản. Mỗi lần tạo khách hàng — đó là một kết nối TCP mới, một TLS handshake mới, hoàn toàn bỏ qua việc quản lý kết nối. Đối với 1000 yêu cầu, sự khác biệt về tốc độ giữa cách tiếp cận đúng và sai có thể lên đến 5-10 lần.
2. Thiếu timeout hoặc timeout quá lớn theo mặc định
Mặc định httpx đặt timeout là 5 giây cho tất cả — kết nối, đọc, ghi, nhóm. Khi làm việc với các proxy không ổn định, điều này là không đủ cho kết nối, nhưng quá nhiều cho việc đọc, nếu proxy chỉ "treo". Hãy phân chia rõ ràng các timeout:
timeout = httpx.Timeout(
connect=5.0,
read=10.0,
write=10.0,
pool=5.0
)
client = httpx.AsyncClient(proxy=PROXY_URL, timeout=timeout)
3. Bỏ qua giới hạn của nhóm kết nối
Khi có nhiều nhiệm vụ song song (gather trên 500+ coroutine mà không có semaphore), httpx bắt đầu ném ra PoolTimeout, vì giới hạn kết nối trong nhóm (mặc định là 100) đã hết. Hãy cấu hình giới hạn một cách rõ ràng thông qua httpx.Limits(max_connections=200, max_keepalive_connections=50) và luôn sử dụng semaphore để kiểm soát độ song song.
4. Tắt xác minh mà không hiểu hậu quả
Nhiều người sao chép verify=False từ Stack Overflow để loại bỏ lỗi SSL khi làm việc qua proxy với chứng chỉ MITM. Điều này mở đường cho các cuộc tấn công "người đàn ông ở giữa" và có thể dẫn đến rò rỉ dữ liệu qua proxy bị xâm phạm. Nếu proxy yêu cầu chứng chỉ đặc biệt — hãy thêm nó một cách rõ ràng thông qua verify="/path/to/cert.pem", thay vì tắt hoàn toàn việc xác minh.
5. Không đóng khách hàng và rò rỉ mô tả tệp
Nếu bạn tạo một nhóm khách hàng bằng tay (như trong ví dụ trên), đừng quên gọi await client.aclose() cho mỗi khách hàng khi kết thúc công việc. Các tập lệnh chạy lâu mà không đóng rõ ràng các kết nối sẽ gặp giới hạn mô tả tệp mở của hệ điều hành (thường là 1024 trên Linux theo mặc định), và trình phân tích sẽ gặp lỗi bí ẩn Quá nhiều tệp mở sau vài giờ hoạt động.
Loại proxy nào nên chọn cho việc phân tích
Việc chọn proxy ảnh hưởng trực tiếp đến số lượng lỗi từ danh sách trên mà bạn sẽ thấy — các proxy không ổn định gây ra timeout và ngắt kết nối, ngay cả khi mã được viết hoàn hảo.
| Loại proxy | Tốc độ | Rủi ro bị chặn | Khi nào sử dụng |
|---|---|---|---|
| Trung tâm dữ liệu | Cao | Cao trên các trang web bảo mật | Open API, bảo vệ chống bot yếu |
| Dân cư | Trung bình | Thấp | Wildberries, Ozon, mạng xã hội |
| Di động | Trung bình-thấp | Tối thiểu | Instagram, TikTok API, bảo vệ nghiêm ngặt |
Đối với việc phân tích bất đồng bộ với tốc độ yêu cầu cao đến API với bảo vệ vừa phải, proxy từ trung tâm dữ liệu vẫn là lựa chọn kinh tế — điều quan trọng là phân phối tải đều trong nhóm, như đã trình bày trong mã ở trên.
Danh sách kiểm tra trước khi sản xuất
- Các khách hàng httpx được tạo một lần và tái sử dụng, không phải cho mỗi yêu cầu
- Các timeout được phân chia cho connect/read/write/pool, không sử dụng mặc định
- Độ song song được giới hạn thông qua
asyncio.Semaphore - Các giới hạn của nhóm kết nối được cấu hình theo tải thực tế
- Có logic thử lại với xử lý ProxyError, ConnectTimeout, ReadTimeout
- Tất cả các khách hàng được đóng rõ ràng thông qua
aclose()khi hoàn thành - verify=False không được sử dụng nếu không cần thiết
- HTTP/2 được bật một cách có ý thức và được kiểm tra qua response.http_version
Kết luận
httpx cung cấp công cụ mạnh mẽ cho việc làm việc bất đồng bộ với proxy và HTTP/2, nhưng yêu cầu cấu hình cẩn thận cho nhóm khách hàng, timeout và giới hạn kết nối. Hầu hết các sự cố của các trình phân tích trong sản xuất không liên quan đến thư viện, mà là do các lỗi kiến trúc: tái tạo khách hàng, thiếu giới hạn độ song song và xử lý ngoại lệ proxy không đúng cách.
Nếu bạn đang xây dựng một trình phân tích cho các thị trường hoặc API mạng xã hội, những hệ thống này theo dõi hoạt động nghi ngờ, độ ổn định của hạ tầng proxy quan trọng không kém gì mã. Đối với những nhiệm vụ như vậy, nên xem xét các proxy dân cư — chúng giảm tỷ lệ lỗi ProxyError và ConnectTimeout do danh tiếng cao hơn của các địa chỉ IP so với các nhóm từ trung tâm dữ liệu giá rẻ.