httpx proxy: cấu hình proxy=, mounts và các lỗi khi nâng cấp
httpx proxy là cách cho thư viện HTTP httpx của Python gửi request qua máy trung gian, dùng được cả kiểu đồng bộ lẫn bất đồng bộ. ProxyVN chạy thật httpx 0.28.1 qua proxy HTTP, SOCKS5 tự dựng có mật khẩu để ghi lại nguyên văn từng thông báo lỗi. Bài hướng dẫn cấu hình đúng cho bản mới và chỉ ra các bẫy hay gặp khi nâng cấp từ bản cũ.
httpx proxy dùng trong tình huống nào
httpx khác requests ở đâu
httpx là thư viện HTTP cho Python có giao diện gần giống requests, thêm hai thứ requests không có: chạy bất đồng bộ với AsyncClient và hỗ trợ HTTP/2. Nhiều dự án thu thập dữ liệu, gọi API hàng loạt và các thư viện SDK của bên thứ ba dùng httpx bên dưới. Vì vậy dù anh em không trực tiếp viết httpx, proxy của chương trình vẫn có thể đang đi qua nó.
Cách khai báo proxy của httpx khác requests. requests nhận một từ điển proxies theo giao thức. httpx bản mới nhận một tham số proxy duy nhất, và định tuyến chi tiết qua mounts. Chép mã từ hướng dẫn requests sang httpx là nguồn lỗi đầu tiên. Bài cài đặt Python proxy từ thư viện requests mô tả cách làm bên requests để đối chiếu.
Khi nào cần proxy với httpx
- Gửi nhiều request tới một website và cần chia tải qua nhiều IP.
- Kiểm nội dung theo vị trí, cần IP ở một quốc gia cụ thể.
- Máy chạy trong mạng công ty chỉ cho ra Internet qua proxy.
- Chương trình bất đồng bộ gửi hàng trăm request song song, mỗi luồng một proxy.
Giới hạn của bài này
Mình chạy trên Windows 10, Python 3.12, httpx 0.28.1 kèm httpcore và certifi. Máy không cài socksio và h2, hai gói phụ cho SOCKS và HTTP/2, nên các lỗi liên quan được ghi nguyên văn khi thiếu gói. Proxy thử chạy ngay trên máy, ghi nhật ký mọi kết nối. Đích là example.com thật và một trang thử nội bộ. Kết quả có thể khác ở bản httpx cũ hơn 0.26.
Phiên bản httpx và thông tin cần chuẩn bị cho httpx proxy
Ba mốc phiên bản cần biết
Theo nhật ký thay đổi của httpx, bản 0.26.0 ngày 20/12/2023 thêm tham số proxy và đánh dấu proxies là cũ. Bản 0.28.0 ngày 28/11/2024 xóa hẳn tham số proxies và bắt đầu nhận socks5h là giao thức proxy hợp lệ. Bản 0.28.1 ngày 6/12/2024 là bản mình thử.
Kiểm bản đang dùng bằng lệnh pip show httpx hoặc in httpx.__version__. Lệnh pip show httpx còn cho biết các gói phụ thuộc đang có: trên máy thử là anyio, certifi, httpcore và idna, không có socksio hay h2. Nhìn dòng này là biết ngay chương trình có dùng được SOCKS và HTTP/2 hay chưa, trước khi chạy lô hàng nghìn request rồi mới gặp lỗi thiếu gói. Nhiều dự án ghim httpx qua thư viện khác, nên một lần cập nhật gói phụ thuộc có thể kéo httpx lên 0.28 mà anh em không để ý, và mã dùng proxies bỗng lỗi.
Gói phụ cần cài thêm
- SOCKS: cài httpx[socks], thực chất là gói socksio.
- HTTP/2: cài httpx[http2], thực chất là gói h2.
- Proxy HTTP thường không cần gì thêm.
Thông tin proxy cần có
Loại proxy HTTP hay SOCKS5, địa chỉ, cổng, tên đăng nhập, mật khẩu hoặc IP đã đưa vào danh sách cho phép. Bài định dạng proxy hướng dẫn đổi chuỗi ip:port:user:pass của nhà cung cấp sang dạng URL mà httpx cần. Bài proxy HTTP và SOCKS5 giúp chọn loại.

proxy= cho một proxy, mounts để định tuyến
Cấu hình httpx proxy với proxy= và mounts
Một proxy cho mọi request
Cách đơn giản nhất là tham số proxy khi tạo Client. Mọi request http và https đi qua proxy đó:
import httpx
PROXY = "http://user1:p%40ss%232026@127.0.0.1:8902"
with httpx.Client(proxy=PROXY, timeout=15) as client:
r = client.get("https://example.com/")
print(r.status_code)
Mình chạy đoạn này, kết quả 200 với cả đích https và đích http. Nhật ký proxy ghi lệnh CONNECT tới example.com cổng 443 kèm tài khoản. Hàm cấp cao cũng nhận tham số này: httpx.get(url, proxy=PROXY).
Proxy cho AsyncClient
AsyncClient nhận đúng tham số proxy như Client. Mình chạy thử, kết quả 200:
import asyncio, httpx
async def main():
async with httpx.AsyncClient(proxy=PROXY, timeout=15) as client:
r = await client.get("https://example.com/")
print(r.status_code)
asyncio.run(main())
Một chỗ hay sai với AsyncClient là dùng chung một Client giữa nhiều vòng lặp sự kiện, ví dụ tạo Client ở cấp module rồi gọi asyncio.run nhiều lần. Mỗi lần asyncio.run tạo vòng lặp mới, kết nối cũ gắn với vòng lặp đã đóng sẽ lỗi. Tạo Client bên trong hàm async chính như ví dụ trên là an toàn nhất. Tạo một AsyncClient và dùng lại cho nhiều request, không tạo mới cho mỗi request. Mỗi client giữ một nhóm kết nối tới proxy, tạo mới liên tục làm mất lợi ích giữ kết nối và dễ chạm giới hạn kết nối đồng thời của nhà cung cấp.
Định tuyến bằng mounts
Muốn mỗi nhóm địa chỉ một proxy, dùng mounts với các HTTPTransport riêng. Trang proxy trong tài liệu httpx đưa ví dụ tách theo giao thức. Mẫu khóa còn nhận tên miền có dấu sao. Mình thử khóa all://*.test trỏ tới proxy: request tới site.test đi qua proxy, request tới example.com đi thẳng.
mounts = {
"all://*.test": httpx.HTTPTransport(proxy=PROXY),
"https://api.example.com": httpx.HTTPTransport(proxy="http://203.0.113.20:8080"),
}
client = httpx.Client(mounts=mounts)
Địa chỉ không khớp khóa nào đi thẳng. Muốn mặc định đi một proxy rồi tách vài ngoại lệ, đặt thêm khóa "all://" cho proxy mặc định, và khóa cụ thể với giá trị None cho địa chỉ cần đi thẳng.
Proxy HTTP cho đích HTTPS vẫn ghi http://
Tài liệu httpx nhấn mạnh: với khóa https://, địa chỉ proxy thường vẫn bắt đầu bằng http://. Client nói chuyện với proxy bằng HTTP rồi mở đường hầm CONNECT tới đích HTTPS. Ghi https:// cho proxy nghĩa là đòi bắt tay TLS với chính proxy, phần lớn proxy không hỗ trợ.
Lỗi httpx proxy khi nâng cấp lên 0.28
proxies= bị xóa
Đoạn mã cũ httpx.Client(proxies=PROXY) chạy trên httpx 0.28.1 cho ra:
TypeError: Client.__init__() got an unexpected keyword argument 'proxies'
Lỗi không nhắc gì tới proxy hay phiên bản, chỉ báo tham số lạ. Sửa bằng cách đổi proxies thành proxy nếu chỉ có một proxy. Nếu mã cũ dùng từ điển như {"http://": P1, "https://": P2}, chuyển sang mounts với HTTPTransport cho từng khóa như phần trên.
Từ điển kiểu requests không chạy
Mã chép từ requests thường có dạng proxies={"http": P, "https": P}, khóa không có dấu hai chấm và gạch chéo. Trên httpx 0.28, đoạn này lỗi TypeError như trên vì tham số proxies không còn. Khi chuyển sang mounts, khóa phải viết đầy đủ dạng "http://", "https://" hoặc "all://". Với trường hợp phổ biến nhất là cùng một proxy cho cả hai giao thức, chỉ cần proxy=P, gọn hơn mounts. Mình thử mounts với khóa "all://" trỏ tới proxy và khóa "all://example.com" có giá trị None: example.com đi thẳng, site.test đi qua proxy. Kết quả này cho thấy khóa cụ thể hơn được ưu tiên hơn khóa chung, không phụ thuộc thứ tự viết trong từ điển. Nhờ vậy có thể khai một proxy mặc định rồi thêm bao nhiêu ngoại lệ cũng được, không lo ngoại lệ bị khóa chung đè mất.
Thư viện bên thứ ba còn truyền proxies
Nhiều SDK nhận tham số proxies rồi chuyển thẳng cho httpx. Khi httpx lên 0.28 mà SDK chưa cập nhật, lỗi TypeError xuất hiện bên trong SDK. Hai cách: cập nhật SDK lên bản hỗ trợ httpx mới, hoặc ghim httpx dưới 0.28 trong requirements cho tới khi SDK cập nhật. Đọc dòng cuối của traceback để biết lỗi xuất phát từ mã của mình hay của thư viện.

proxies= đã bị xóa từ bản 0.28
Lỗi httpx proxy 407, sai địa chỉ và sai giao thức
407 với đích HTTPS là exception, với đích HTTP là phản hồi
Mình thử mật khẩu sai. Với đích https, httpx ném httpx.ProxyError: 407 Proxy Authentication Required, vì proxy từ chối mở đường hầm. Với đích http, không có exception, httpx trả về đối tượng phản hồi có status_code là 407. Mã chỉ bắt exception mà không kiểm mã trạng thái sẽ coi 407 là thành công và đọc nhầm nội dung trang lỗi của proxy.
Kiểm cả hai: bắt httpx.ProxyError, và gọi r.raise_for_status() hoặc kiểm r.status_code == 407 sau mỗi request.
Gom hai kiểu 407 về một exception
Để khỏi nhớ kiểm mã trạng thái ở mọi chỗ, dùng event_hooks của httpx: một hàm chạy sau mỗi phản hồi, gặp 407 thì ném ProxyError giống trường hợp https.
import httpx
def chan_407(response):
if response.status_code == 407:
raise httpx.ProxyError("Proxy tu choi tai khoan (407)", request=response.request)
client = httpx.Client(proxy="http://user1:sai@127.0.0.1:8902",
event_hooks={"response": [chan_407]})
for url in ("http://site.test:8901/", "https://example.com/"):
try:
print(url, client.get(url).status_code)
except httpx.ProxyError as e:
print(url, "ProxyError:", e)
Mình chạy với mật khẩu sai, cả đích http lẫn https đều ra httpx.ProxyError. Phần còn lại của chương trình chỉ cần bắt một loại lỗi. Hàm hook cũng là chỗ tiện để ghi nhật ký proxy nào vừa lỗi, phục vụ việc loại proxy hỏng khỏi danh sách xoay. Bài requests.exceptions.ProxyError mô tả lỗi tương tự ở requests.
getaddrinfo failed: mật khẩu có ký tự đặc biệt
Mật khẩu thử là p@ss#2026. Ghi thẳng http://user1:p@ss#[email protected]:8902, httpx báo httpx.ConnectError: [Errno 11001] getaddrinfo failed. Dấu @ đầu tiên kết thúc phần tài khoản, phần còn lại bị hiểu sai thành tên máy. Mã hóa phần trăm: @ thành %40, # thành %23. Dùng urllib.parse.quote(matkhau, safe="") để mã hóa trong mã.
WinError 10061: sai cổng
Cổng sai cho ra httpx.ConnectError: [WinError 10061] No connection could be made because the target machine actively refused it. Máy proxy không nghe ở cổng đó. Trên Linux là Connection refused, cùng ý nghĩa.
ConnectTimeout: ghi https:// cho proxy HTTP
Ghi proxy="https://..." cho proxy HTTP thường, httpx cố bắt tay TLS với proxy, chờ rồi báo httpx.ConnectTimeout: _ssl.c:993: The handshake operation timed out. Lỗi trông như proxy chậm, thực ra là sai giao thức. Đổi về http://.
ValueError: thiếu giao thức
Ghi proxy="127.0.0.1:8902" không có tiền tố, httpx báo ValueError: Unknown scheme for proxy URL. Thêm http:// hoặc socks5://.
Thông báo |
Nguyên nhân |
Cách sửa |
|---|---|---|
unexpected keyword argument 'proxies' |
Mã cũ trên httpx 0.28 |
Đổi sang proxy= hoặc mounts |
httpx.ProxyError: 407 |
Sai hoặc thiếu tài khoản, đích https |
Kiểm tài khoản, mã hóa ký tự đặc biệt |
status_code 407, không exception |
Sai tài khoản, đích http |
Kiểm mã trạng thái |
ConnectError getaddrinfo failed |
Mật khẩu có @ hoặc # chưa mã hóa |
Mã hóa phần trăm |
ConnectError WinError 10061 |
Sai cổng hoặc proxy tắt |
Kiểm cổng |
ConnectTimeout handshake timed out |
Ghi https:// cho proxy HTTP |
Đổi về http:// |
ValueError Unknown scheme |
Thiếu tiền tố |
Thêm http:// |
ReadTimeout |
Proxy chậm sau khi đã kết nối |
Tăng timeout hoặc đổi proxy |
Lỗi httpx proxy với SOCKS5 và HTTP/2
Thiếu socksio
Ghi proxy="socks5://..." khi chưa cài gói phụ, httpx báo ngay:
ImportError: Using SOCKS proxy, but the 'socksio' package is not installed. Make sure to install httpx using `pip install httpx[socks]`.
Lỗi xảy ra lúc tạo Client, chưa gửi request nào. Cài bằng pip install "httpx[socks]", có dấu ngoặc kép trên một số shell để dấu ngoặc vuông không bị hiểu sai.
socks5 hay socks5h
httpx 0.28 nhận cả socks5:// và socks5h://. Theo quy ước chung, socks5h để proxy tra tên miền, socks5 để máy tự tra. Khi đích chỉ tra được từ phía proxy, hoặc anh em không muốn lộ truy vấn DNS ở mạng đang dùng, chọn socks5h. Máy thử không có socksio nên mình không đo được khác biệt này trên httpx, chỉ ghi theo nhật ký thay đổi.
HTTP/2 qua proxy
Bật http2=True khi chưa cài h2 cho ra ImportError tương tự, gợi ý pip install httpx[http2]. Có h2 rồi, HTTP/2 chạy bên trong đường hầm CONNECT, proxy HTTP không cần hiểu HTTP/2. Nhật ký thay đổi bản 0.27.0 ghi httpx tôn trọng tham số http1 khi cấu hình transport cho proxy, nghĩa là có thể tắt HTTP/1.1 hoặc HTTP/2 rõ ràng cho từng transport khi cần thử.
ReadTimeout khi proxy chậm
Mình dựng một proxy trả lời chậm 5 giây, đặt timeout 2 giây. httpx báo httpx.ReadTimeout chứ không phải ConnectTimeout. Kết nối tới proxy mở được ngay, chỉ là proxy chậm trả lời. Phân biệt hai lỗi giúp biết proxy chết hay chỉ quá tải. Đặt timeout riêng từng phần bằng httpx.Timeout(10, connect=5) để kết nối hỏng thất bại nhanh mà đọc dữ liệu vẫn có thời gian. Với danh sách proxy dài, kết nối hỏng thất bại nhanh giúp chương trình chuyển sang proxy khác thay vì treo hàng chục giây trên một proxy đã chết.

NO_PROXY: cách ghi nào chạy
Lỗi httpx proxy do biến môi trường
httpx đọc biến môi trường mặc định
Trang biến môi trường của httpx ghi HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY được đọc mặc định, tắt bằng trust_env=False. Mình thử: HTTP_PROXY viết hoa và http_proxy viết thường đều được đọc. Với trust_env=False, request bỏ qua proxy trong biến môi trường. Khi truyền proxy= trong mã, proxy đó thắng biến môi trường: đặt biến với mật khẩu sai và proxy= với mật khẩu đúng, request vẫn thành công.
Chỉ có HTTPS_PROXY thì đích http đi thẳng
Đặt riêng HTTPS_PROXY, request tới địa chỉ http không đi proxy. Trong mạng chỉ cho ra ngoài qua proxy, request http lỗi kết nối trong khi https chạy. Đặt cả HTTP_PROXY, hoặc dùng ALL_PROXY.
ALL_PROXY kiểu SOCKS làm hỏng mọi Client
Đây là bẫy nặng nhất mình gặp. Đặt ALL_PROXY=socks5://... trên máy chưa có socksio, lệnh httpx.Client() không tham số cũng lỗi ImportError ngay khi khởi tạo. Mọi chương trình và thư viện dùng httpx trên máy đó đều hỏng, kể cả những thứ không cần proxy. Biến ALL_PROXY thường được đặt cho công cụ khác như curl, rồi quên mất. Gặp lỗi socksio ở chương trình không hề dùng SOCKS, kiểm ngay biến ALL_PROXY.
NO_PROXY: cách ghi nào chạy
Giá trị NO_PROXY |
Đích thử |
Kết quả |
|---|---|---|
site.test |
http://site.test |
Đi thẳng |
.test |
http://site.test |
Đi thẳng |
*.test |
http://site.test |
Vẫn đi proxy |
* |
http://site.test |
Đi thẳng |
127.0.0.1 |
http://127.0.0.1 |
Đi thẳng |
127.0.0.0/8 |
http://127.0.0.1 |
Vẫn đi proxy |
localhost |
http://127.0.0.1 |
Vẫn đi proxy |
Ba dòng "vẫn đi proxy" là các bẫy: httpx không nhận mẫu có dấu sao đứng trước, không nhận dải CIDR, và localhost không bao gồm 127.0.0.1. Một điểm khác: httpx đưa cả 127.0.0.1 qua proxy nếu không liệt kê, nên dịch vụ cục bộ như máy chủ phát triển sẽ lỗi khi đặt HTTP_PROXY. Thêm 127.0.0.1 và localhost vào NO_PROXY.
Kiểm httpx proxy có đi đúng đường không bằng nhật ký
Bật nhật ký của httpcore
Khi không chắc request có đi qua proxy hay không, đừng đoán qua IP trả về. httpx dựa trên thư viện httpcore, cả hai ghi nhật ký qua module logging chuẩn của Python. Bật mức DEBUG cho đúng các logger cần thiết:
import logging, httpx
logging.basicConfig(format="%(name)s %(message)s", level=logging.DEBUG)
for n in ("httpx", "httpcore.connection", "httpcore.proxy"):
logging.getLogger(n).setLevel(logging.DEBUG)
logging.getLogger("httpcore.http11").setLevel(logging.WARNING)
with httpx.Client(proxy="http://user1:p%40ss%232026@127.0.0.1:8902") as c:
c.get("https://example.com/")
Đọc nhật ký
Mình chạy đoạn trên, các dòng quan trọng là:
- httpcore.connection connect_tcp.started host='127.0.0.1' port=8902: kết nối TCP mở tới máy proxy, không phải tới example.com. Đây là bằng chứng request đi qua proxy.
- httpcore.proxy start_tls.started server_hostname='example.com': sau khi đường hầm mở, bắt tay TLS với example.com bên trong đường hầm.
- httpx HTTP Request: GET https://example.com/ "HTTP/1.1 200 OK": kết quả cuối.
Nếu dòng connect_tcp ghi host là tên website đích, request đang đi thẳng, proxy không được dùng. Lỗi hay gặp nhất là proxy khai trong một Client nhưng request lại gửi qua hàm cấp cao httpx.get, vốn tạo Client mới không có proxy.
Thời gian chờ mặc định chỉ 5 giây
Cùng dòng nhật ký có timeout=5.0. httpx đặt thời gian chờ mặc định 5 giây cho mỗi giai đoạn, khác requests vốn không giới hạn. Proxy dân cư hay proxy ở xa đôi khi mất hơn 5 giây để mở đường hầm, nên chuyển từ requests sang httpx bỗng gặp ConnectTimeout hay ReadTimeout dù proxy vẫn chạy. Đặt timeout rõ ràng khi tạo Client, ví dụ httpx.Timeout(20, connect=8), phù hợp với chất lượng proxy đang dùng.
Xoay httpx proxy với AsyncClient
Mẫu mã chạy được
Cách gọn để chia request qua nhiều proxy là tạo sẵn một AsyncClient cho mỗi proxy rồi lần lượt dùng. Mình dựng hai proxy thử ở hai cổng và chạy đoạn sau với sáu request:
import asyncio, itertools, httpx
PROXIES = [
"http://user1:p%40ss%232026@127.0.0.1:8902",
"http://user1:p%40ss%232026@127.0.0.1:8942",
]
async def main(urls):
clients = [httpx.AsyncClient(proxy=p, timeout=15) for p in PROXIES]
vong = itertools.cycle(clients)
gioi_han = asyncio.Semaphore(4)
async def lay(url):
client = next(vong)
async with gioi_han:
try:
r = await client.get(url)
return url, r.status_code
except httpx.HTTPError as e:
return url, type(e).__name__
try:
return await asyncio.gather(*(lay(u) for u in urls))
finally:
for c in clients:
await c.aclose()
urls = [f"http://site.test:8901/trang-{i}" for i in range(6)]
for url, kq in asyncio.run(main(urls)):
print(url, kq)
Kết quả sáu request đều 200, nhật ký hai proxy mỗi bên ghi ba request, đúng kiểu chia lần lượt. Semaphore giới hạn bốn request cùng lúc, tránh vượt giới hạn kết nối đồng thời của nhà cung cấp.
Vì sao không đổi proxy trên một Client
Một Client giữ nhóm kết nối tới proxy đã cấu hình lúc tạo. Không có cách đổi proxy cho Client đang chạy. Tạo Client mới cho mỗi request thì đổi được proxy nhưng mất phần giữ kết nối, mỗi request phải mở lại đường hầm và bắt tay TLS, chậm hơn rõ. Vài Client cố định, mỗi cái một proxy, là cân bằng tốt.
Đóng Client đúng cách
Mẫu mã đóng mọi Client trong khối finally bằng aclose. Không đóng thì kết nối tới proxy treo lại tới khi chương trình thoát, dễ chạm giới hạn kết nối của proxy khi chạy lâu. Với Client đồng bộ, dùng khối with hoặc gọi close.
Lỗi httpx proxy về chứng chỉ khi proxy công ty giải mã HTTPS
Vì sao lỗi
httpx dùng bộ chứng chỉ của gói certifi đi kèm, không dùng kho chứng chỉ của Windows. Proxy công ty giải mã HTTPS rồi ký lại bằng chứng chỉ riêng, certifi không biết chứng chỉ đó, request báo lỗi xác minh chứng chỉ. Trình duyệt và pip trên cùng máy có thể vẫn chạy vì tin kho chứng chỉ hệ điều hành.
Cách sửa
Trang biến môi trường của httpx ghi biến SSL_CERT_FILE trỏ tới file chứng chỉ, httpx sẽ nạp chứng chỉ gốc từ file đó thay vì vị trí mặc định. Cách thứ hai là truyền verify với một ssl.SSLContext đã nạp chứng chỉ công ty. Nhật ký thay đổi 0.28.0 ghi truyền verify dạng chuỗi đường dẫn đã bị đánh dấu cũ. Mình thử truyền chuỗi, httpx phát cảnh báo verify=<str> is deprecated, gợi ý dùng ssl.create_default_context(cafile=...). Cách đúng:
import ssl, httpx
ctx = ssl.create_default_context(cafile="C:/certs/congty-ca.pem")
client = httpx.Client(proxy=PROXY, verify=ctx)
Không dùng verify=False để vượt lỗi. Tắt xác minh cho phép kẻ đứng giữa đọc và sửa dữ liệu, kể cả tài khoản gửi trong request.
So sánh httpx proxy với requests và aiohttp
Khác nhau ở đâu khi chuyển thư viện
Ba thư viện cùng làm một việc nhưng khai báo proxy, xử lý lỗi và thời gian chờ khác nhau. Chuyển mã từ thư viện này sang thư viện kia mà giữ nguyên thói quen cũ là nguồn lỗi lớn.
Tiêu chí |
httpx 0.28 |
requests |
aiohttp |
|---|---|---|---|
Khai báo proxy |
proxy= trên Client, mounts để định tuyến |
Từ điển proxies theo giao thức |
proxy= trên từng request |
Đồng bộ, bất đồng bộ |
Cả hai |
Chỉ đồng bộ |
Chỉ bất đồng bộ |
SOCKS |
Cần socksio |
Cần PySocks |
Cần gói phụ riêng |
Thời gian chờ mặc định |
5 giây |
Không giới hạn |
Có giới hạn tổng |
Đọc biến môi trường |
Mặc định có |
Mặc định có |
Phải bật trust_env |
Chứng chỉ |
certifi |
certifi |
Theo ssl mặc định |
Chọn thư viện theo việc
Chương trình nhỏ chạy tuần tự, requests vẫn ổn và có nhiều ví dụ. Chương trình gửi hàng trăm request song song nên dùng httpx AsyncClient hoặc aiohttp. httpx có lợi thế dùng chung một cách viết cho cả đồng bộ và bất đồng bộ, đổi từ Client sang AsyncClient gần như chỉ thêm await. Cần HTTP/2 thì httpx là lựa chọn trong ba thư viện này.
Mã cũ của requests chạy sai lặng lẽ trên httpx
Một điểm dễ bỏ qua: với requests, request http bị proxy trả 407 cũng không ném lỗi, giống httpx. Nhưng thói quen viết requests.get(url, proxies=...) mà không đặt timeout, khi chuyển sang httpx lại gặp giới hạn 5 giây. Mã cũ chạy ổn với requests có thể bắt đầu báo hết giờ hàng loạt sau khi chuyển, dù proxy không đổi.
httpx proxy trong thư viện và SDK dùng httpx
Thư viện cho truyền Client riêng
Nhiều thư viện gọi API dùng httpx bên dưới và cho phép truyền vào một Client tự tạo, thường qua tham số tên http_client hay client. Đây là cách sạch nhất để đặt proxy: tạo httpx.Client(proxy=...) với timeout phù hợp, truyền vào thư viện. Mọi request của thư viện đi qua Client đó, dùng đúng proxy, đúng chứng chỉ, đúng thời gian chờ anh em đặt. Đọc tài liệu của thư viện để biết tên tham số chính xác.
Thư viện không cho truyền Client
Nếu thư viện tự tạo Client bên trong và không cho truyền vào, biến môi trường là cách còn lại, vì httpx đọc HTTPS_PROXY mặc định. Đặt biến ngay trong chương trình trước khi import thư viện, hoặc trong môi trường chạy. Nhớ các bẫy đã thử ở phần biến môi trường: đặt cả HTTP_PROXY, không dùng *.domain trong NO_PROXY, và kiểm ALL_PROXY.
Kiểm thư viện có tôn trọng proxy không
Bật nhật ký httpcore như phần trên rồi gọi một hàm của thư viện. Dòng connect_tcp.started cho biết thư viện đang kết nối tới proxy hay tới thẳng máy chủ của nó. Một số thư viện tạo Client với trust_env=False, khi đó biến môi trường không có tác dụng, chỉ còn cách truyền Client riêng.
Script kiểm httpx proxy trước khi chạy lô
Mã nguồn
Đoạn dưới kiểm một proxy theo đúng thứ tự lỗi trong bài, in nguyên văn lỗi của httpx:
import sys, httpx
proxy = sys.argv[1]
for dich in ("http://httpbin.org/ip", "https://httpbin.org/ip"):
try:
with httpx.Client(proxy=proxy, timeout=httpx.Timeout(15, connect=5), trust_env=False) as c:
r = c.get(dich)
print(dich, "->", r.status_code, r.text.strip()[:80])
except httpx.ProxyError as e:
print(dich, "-> loi proxy:", e)
except httpx.TimeoutException as e:
print(dich, "-> het gio:", type(e).__name__)
except httpx.HTTPError as e:
print(dich, "-> loi ket noi:", type(e).__name__, e)
Đọc kết quả
Chạy python kiem_httpx.py "http://user:pass@host:port". Tham số trust_env=False tách riêng proxy đang kiểm khỏi biến môi trường. Hai dòng 200 kèm IP proxy là proxy dùng được cho cả http và https. Dòng http trả 407 mà dòng https báo lỗi proxy là sai tài khoản. ConnectTimeout ở cả hai là sai giao thức hoặc proxy chết. Bài công cụ kiểm tra proxy có thêm cách kiểm độ trễ.
Câu hỏi thường gặp về httpx proxy
Xoay proxy với httpx thế nào. Tạo sẵn vài Client, mỗi Client một proxy, rồi chọn lần lượt cho từng request. Không đổi proxy trên một Client đang dùng. Với proxy xoay của nhà cung cấp, chỉ cần một Client trỏ tới cổng xoay, IP đổi phía sau.
AsyncClient gửi song song bao nhiêu request qua một proxy. httpx mặc định giới hạn số kết nối trong nhóm, chỉnh bằng httpx.Limits. Nhà cung cấp proxy cũng giới hạn kết nối đồng thời. Đặt giới hạn của httpx thấp hơn giới hạn của proxy để tránh lỗi bị từ chối.
Có cần ghim phiên bản httpx không. Nên ghim trong requirements, ví dụ httpx>=0.28,<0.29, để tránh lỗi tham số khi bản lớn tiếp theo thay đổi giao diện.
httpx có tự thử lại khi proxy lỗi không. Mặc định không thử lại request. HTTPTransport có tham số retries chỉ áp dụng cho lỗi kết nối. Lỗi 407 hay 503 từ proxy cần tự xử lý trong mã.
Request qua proxy chậm hơn hẳn, có phải do httpx không. Thường không. Mỗi Client mới phải mở đường hầm CONNECT và bắt tay TLS lại từ đầu, tốn vài trăm mili giây qua proxy ở xa. Dùng lại một Client cho nhiều request để giữ kết nối, tốc độ cải thiện rõ.
Proxy có mật khẩu chứa dấu hai chấm thì ghi thế nào. Dấu hai chấm trong mật khẩu cũng phải mã hóa thành %3A. quote(matkhau, safe="") lo hết các ký tự đặc biệt, kể cả dấu cách và dấu gạch chéo.
httpx có hỗ trợ proxy HTTPS, tức kết nối TLS tới chính proxy không. Có, ghi https:// cho địa chỉ proxy khi nhà cung cấp nói rõ proxy nhận TLS. Phần lớn proxy thương mại không như vậy, ghi nhầm sẽ gặp lỗi bắt tay hết giờ như đã thử.
Chạy trong Jupyter thì có gì khác. Jupyter đã có sẵn một vòng lặp sự kiện, nên gọi asyncio.run báo lỗi vòng lặp đang chạy. Trong ô Jupyter, dùng thẳng await main(urls) thay vì asyncio.run. Proxy và Client cấu hình không đổi.
Có nên để mật khẩu proxy trong mã không. Không. Đọc từ biến môi trường riêng của chương trình, ví dụ PROXY_URL, rồi truyền vào proxy=. Cách này giữ mật khẩu ngoài kho mã, và tránh đặt HTTPS_PROXY chung cho cả máy làm ảnh hưởng chương trình khác.
So với aiohttp thì sao. aiohttp chỉ có bất đồng bộ và khai báo proxy theo từng request. httpx có cả hai kiểu và khai báo theo Client. Bài aiohttp proxy mô tả cách bên đó.
Khi cần proxy HTTP ổn định cho chương trình Python chạy lô, anh em có thể mua proxy Việt Nam tại trang chủ ProxyVN rồi chạy script trên trước khi đưa vào sản xuất.

Kiểm httpx proxy trước khi chạy lô
httpx proxy và những điều cần nhớ
httpx proxy trên bản 0.28 dùng proxy= hoặc mounts, proxies đã bị xóa. Đích https sai tài khoản ném ProxyError, đích http trả 407. SOCKS cần socksio, ALL_PROXY kiểu SOCKS làm hỏng mọi Client. NO_PROXY không nhận *.domain hay CIDR. Cần proxy ổn định, liên hệ Proxy.vn - Nhà cung cấp dịch vụ proxy chất lượng hàng đầu Việt Nam.