Proxy

Python Requests Proxy: Cài Đặt, Xác Thực và Xoay IP

24 thg 8, 202615 phút đọc

Cài đặt một python requests proxy là bước thực tế đầu tiên trong hầu hết mọi công việc scraping hay giám sát, và nó là một trong những tác vụ trông có vẻ đơn giản cho đến khi một request lặng lẽ trả về chính IP của bạn hoặc một lỗi xác thực mà bạn không thể lý giải. Hướng dẫn này sẽ khắc phục điều đó. Đến cuối bài bạn sẽ có mã sẵn để sao chép nhằm định tuyến thư viện requests qua một proxy trên HTTP, HTTPS và SOCKS5, thêm xác thực, tái sử dụng kết nối bằng một session, đặt timeout và retry, xoay vòng qua một pool, kiểm chứng IP có thực sự thay đổi hay không, và xử lý các lỗi bạn sẽ gặp trong thực tế. Trường hợp sử dụng trung thực xuyên suốt bài là công việc chính đáng - thu thập dữ liệu công khai, theo dõi giá và SERP, cùng nghiên cứu SEO - chứ không phải bất cứ điều gì đằng sau một trang đăng nhập.

Cập nhật lần cuối: ngày 24 tháng 8 năm 2026. Bài viết này chứa các liên kết tiếp thị liên kết - xem tuyên bố về tiếp thị liên kết của chúng tôi. Các gói và endpoint của nhà cung cấp có thể thay đổi, nên hãy xác minh mọi con số trên chính trang của nhà cung cấp trước khi tin dùng.

Cách thiết lập proxy trong Python requests

Ví dụ hoạt động tối giản là một dictionary với các key httphttps, được truyền vào tham số proxies của bất kỳ phương thức request nào:

import requests

proxies = {
    "http": "http://198.51.100.10:8080",
    "https": "http://198.51.100.10:8080",
}

response = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(response.json())

Có hai điều khiến mọi người vấp ở đây. Thứ nhất, giá trị là một URL proxy đầy đủ, không phải một IP trần: nó cần scheme (http://), host, và cổng. Thứ hai, các key của dictionary không phải là giao thức của proxy - chúng chọn proxy nào sẽ dùng dựa trên scheme của URL đích. Một request tới một trang https:// sẽ dùng key https. Sự phân biệt đó trở nên quan trọng ở phần tiếp theo, và nó là lý do phổ biến nhất khiến một proxy "không hoạt động".

Luôn truyền một timeout, như ở trên. Một proxy nằm giữa bạn và mục tiêu, nên một proxy chết hoặc chậm sẽ treo script của bạn mãi mãi nếu thiếu nó.

Proxy HTTP, HTTPS và SOCKS5

Các key trong dict proxies định tuyến theo scheme đích, trong khi scheme của giá trị mới là thứ mà bản thân proxy nói. Vì vậy một proxy HTTP đơn lẻ có thể tunnel lưu lượng HTTPS được viết với một giá trị http:// dưới cả hai key - đúng như bản chất của hầu hết proxy HTTP datacenter và dân cư. Bạn không cần một giá trị https:// chỉ vì bạn đang scraping các trang HTTPS.

SOCKS5 thì khác. Nó cần một dependency bổ sung:

pip install "requests[socks]"

Sau đó dùng scheme socks5h://. Chữ h ở cuối rất quan trọng: nó gửi việc phân giải DNS qua proxy thay vì phân giải hostname tại máy cục bộ, đó là điều bạn gần như luôn muốn khi scraping để DNS của máy bạn không làm lộ mục tiêu.

proxies = {
    "http": "socks5h://user:pass@proxy-host:1080",
    "https": "socks5h://user:pass@proxy-host:1080",
}

response = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(response.json())
Tiền tố schemeNó làm gì
http://Định tuyến qua một proxy HTTP; tunnel các mục tiêu HTTPS qua CONNECT. Mặc định cho hầu hết proxy.
socks5://Định tuyến qua SOCKS5, nhưng phân giải DNS cục bộ trên máy bạn.
socks5h://Định tuyến qua SOCKS5 và phân giải DNS tại proxy (chữ h). Được ưu tiên cho scraping.

Xác thực proxy trong Python

Hầu hết proxy trả phí đều yêu cầu username và password. Với requests, thông tin đăng nhập đi thẳng vào URL proxy dưới dạng http://user:pass@host:port:

proxy_url = "http://my-username:my-password@proxy-host:8080"
proxies = {"http": proxy_url, "https": proxy_url}

response = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
print(response.json())

Đây là lỗi khiến người ta mất cả buổi chiều: nếu password của bạn chứa các ký tự như @, :, # hay /, chúng làm hỏng việc phân tích URL và việc xác thực thất bại mà không rõ lý do. Hãy URL-encode username và password bằng urllib.parse.quote trước khi dựng URL:

from urllib.parse import quote

user = "my-username"
password = "p@ss:w#rd"       # contains reserved characters
host, port = "proxy-host", 8080

proxy_url = f"http://{quote(user, safe='')}:{quote(password, safe='')}@{host}:{port}"
proxies = {"http": proxy_url, "https": proxy_url}

Tham số safe='' yêu cầu quote mã hóa mọi ký tự dành riêng thay vì bỏ sót một số. Làm điều này ngay từ đầu thì bạn sẽ không bao giờ phải đuổi theo lỗi ma "xác thực thất bại không lý do". Nếu proxy vẫn từ chối bạn, mã trạng thái cần chú ý là 407 Proxy Authentication Required - được nói đến ở phần lỗi bên dưới.

Tái sử dụng kết nối với requests.Session

Với bất cứ thứ gì vượt quá một request đơn lẻ, hãy dùng một Session. Đặt proxy một lần trên session và mọi request qua nó sẽ tái sử dụng proxy, cookie jar, và kết nối TCP nền:

session = requests.Session()
session.proxies = {
    "http": proxy_url,
    "https": proxy_url,
}

session.get("https://httpbin.org/ip", timeout=10)
session.get("https://httpbin.org/headers", timeout=10)  # same proxy, same cookies

Điều này quan trọng cho scraping theo hai cách. Cookie được giữ lại, nên trạng thái đăng nhập hoặc phiên được mang qua các request, và việc tái sử dụng kết nối cắt bớt chi phí handshake ở mỗi lần gọi, đó là một lợi thế tốc độ thật khi bạn thực hiện hàng nghìn request qua cùng một proxy.

Thiết lập proxy bằng biến môi trường

requests tự động đọc cấu hình proxy từ môi trường, nên bạn có thể giữ thông tin đăng nhập hoàn toàn ngoài mã của mình:

export HTTP_PROXY="http://user:pass@proxy-host:8080"
export HTTPS_PROXY="http://user:pass@proxy-host:8080"

Cả tên viết hoa lẫn viết thường (http_proxy, https_proxy) đều được nhận. Vài lưu ý về khi nào cách này gọn hơn:

  • Tốt cho việc đổi proxy theo từng môi trường (dev, staging, production) mà không đụng đến mã.
  • Tốt cho việc giữ secret trong một file .env hoặc kho secret CI của bạn thay vì trong repo.
  • Để bỏ qua proxy môi trường cho một session, đặt session.trust_env = False. Điều này tiện khi có một proxy cấp toàn máy nhưng bạn muốn một script cụ thể đi trực tiếp hoặc dùng danh sách riêng của nó.

Thêm timeout và retry

Đây là phần mà hầu hết hướng dẫn bỏ qua, và nó là khác biệt giữa một script chết lúc 3 giờ sáng và một script chạy xong. Proxy chập chờn hơn một kết nối trực tiếp - một IP có thể bị quá tải tạm thời hoặc bị giới hạn tốc độ - nên retry ở đây quan trọng hơn bình thường.

Bắt đầu với một timeout thực sự. Truyền một tuple sẽ tách nó thành timeout kết nối và timeout đọc, nên một proxy chậm sẽ thất bại nhanh khi kết nối nhưng vẫn cho phép một phản hồi chậm hoàn tất:

response = requests.get(url, proxies=proxies, timeout=(5, 20))  # (connect, read) seconds

Sau đó gắn một chiến lược retry lên một session bằng Retry của urllib3 cùng một HTTPAdapter. backoff_factor giãn cách các lần thử để bạn không dội dồn dập vào một endpoint đang quá tải, và status_forcelist thử lại với các mã máy chủ tạm thời và mã giới hạn tốc độ:

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

retry = Retry(
    total=5,
    backoff_factor=1,                       # waits 0s, 2s, 4s, 8s... between tries
    status_forcelist=[429, 500, 502, 503, 504],
    allowed_methods=["GET", "POST"],
)

session = requests.Session()
adapter = HTTPAdapter(max_retries=retry)
session.mount("http://", adapter)
session.mount("https://", adapter)
session.proxies = {"http": proxy_url, "https": proxy_url}

response = session.get("https://httpbin.org/ip", timeout=(5, 20))
print(response.json())

Trên các bản urllib3 cũ hơn, tham số có tên method_whitelist thay vì allowed_methods - hãy cập nhật thư viện nếu bạn gặp phải. Lưu ý rằng Retry xử lý các lỗi kết nối và các mã trạng thái bạn liệt kê; nó không thử lại một 200 sạch mà chỉ đơn giản trả về một trang chặn, đó là lý do các phần xoay vòng và lỗi bên dưới vẫn quan trọng.

Xoay proxy qua một pool

Gửi mọi request từ một IP là cách nhanh nhất để bị giới hạn tốc độ hoặc bị chặn. Việc xoay vòng trải các request qua nhiều IP. Có hai cách tiếp cận trung thực, và chúng đáng công sức ở những quy mô rất khác nhau. Nếu bạn muốn nắm khái niệm trước, hãy xem bài giải thích của chúng tôi về cách proxy xoay vòng hoạt động.

Cách A - một pool thủ công với random.choice

Nếu bạn có một nhúm IP tĩnh, hãy giữ chúng trong một list và chọn một cho mỗi request. random.choice cho phép chọn ngẫu nhiên đơn giản:

import random
import requests

proxy_pool = [
    "http://user:pass@ip-1:8080",
    "http://user:pass@ip-2:8080",
    "http://user:pass@ip-3:8080",
]

targets = ["https://httpbin.org/ip"] * 6
for url in targets:
    proxy = random.choice(proxy_pool)
    proxies = {"http": proxy, "https": proxy}
    try:
        r = requests.get(url, proxies=proxies, timeout=10)
        print(proxy, "->", r.json()["origin"])
    except requests.exceptions.RequestException as exc:
        print(f"failed via {proxy}: {exc}")

Để round-robin nghiêm ngặt thay vì ngẫu nhiên, hãy dùng itertools.cycle và gọi next(proxy_cycle) mỗi lần. Cách A ổn cho một tập IP nhỏ, cố định mà bạn đã sở hữu. Cái giá là bạn phải duy trì danh sách, loại bỏ các IP chết, và theo dõi IP nào đã bắt đầu bị một mục tiêu chặn.

Cách B - một endpoint xoay vòng của nhà cung cấp

Ở quy mô lớn, bạn ngừng duy trì danh sách và trỏ mọi request tới một endpoint xương sống duy nhất, để nhà cung cấp thay IP thoát ra giúp bạn. Dùng định dạng endpoint xoay vòng thực tế của Webshare, bạn thêm -rotate vào username và gửi mọi thứ tới p.webshare.io trên cổng 80:

import requests

# One endpoint; the provider hands you a fresh exit IP per request.
proxy = "http://<username>-rotate:<password>@p.webshare.io:80/"
proxies = {"http": proxy, "https": proxy}

for _ in range(6):
    r = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
    print(r.json()["origin"])   # typically a different IP each call

Hậu tố -rotate yêu cầu một IP mới ở mỗi lần gọi; nếu bạn cần giữ nguyên một IP cho một luồng nhiều bước (một sticky session), nhà cung cấp cho bạn một dạng session id dạng số thay thế. Hãy xác minh chính xác endpoint và định dạng username trên help.webshare.io trước khi tin dùng, vì những thứ này có thể thay đổi. Điểm mấu chốt của Cách B rất đơn giản: nó xóa bỏ toàn bộ gánh nặng bảo trì của Cách A, đó là lý do nó thắng thế một khi bạn vượt qua vài IP.

Để thực sự chạy bất kỳ đoạn mã nào ở trên mà không phải cam kết một gói trả phí, gói miễn phí của Webshare cho bạn 10 proxy và 1GB băng thông mỗi tháng (tính đến tháng 8 năm 2026, hãy xác minh trên trang), đủ để kiểm thử xoay vòng, xác thực, và mã retry từ đầu tới cuối. Bạn có thể lấy proxy miễn phí tại đây và dán thông tin đăng nhập thật của bạn vào các ví dụ ở trên.

Một ghi chú nhỏ thành thật từ trải nghiệm của riêng tôi: dù đoạn mã ở trên xoay vòng theo từng request, cho việc scraping của mình tôi thực ra ghim một sticky session thay vì lấy một IP mới mỗi lần gọi, và nó giữ nguyên cùng một IP thoát ra suốt thời gian công việc chạy. Thiết lập điều đó qua endpoint của Webshare rất dễ dàng, và tôi không gặp lỗi xác thực 407 hay lỗi giới hạn tốc độ 429, nên tôi chưa bao giờ cần giãn cách request mạnh tay để né chúng. Tuy vậy kết quả của bạn sẽ khác nhau tùy mục tiêu, nên hãy đo tỷ lệ thành công trên chính các URL của bạn trước khi mở rộng một công việc.

Kiểm chứng IP proxy của bạn

Trước khi tin bất cứ điều gì trong số này, hãy xác nhận proxy thực sự nằm trong đường đi. Gọi một dịch vụ echo IP có và không có proxy rồi so sánh - nếu hai kết quả trùng nhau, proxy không được sử dụng:

import requests

def show_ip(proxies=None):
    r = requests.get("https://api.ipify.org?format=json", proxies=proxies, timeout=10)
    return r.json()["ip"]

print("direct:   ", show_ip())
print("via proxy:", show_ip({"http": proxy_url, "https": proxy_url}))

Nếu dòng "via proxy" vẫn hiển thị IP thật của bạn, nguyên nhân thường gặp là thiếu key https trong dict trong khi bạn đang request một URL https:// - nên request lặng lẽ đi trực tiếp. Hãy thêm mục https (xem phần thiết lập) và chạy lại. Bạn có thể thay api.ipify.org bằng https://httpbin.org/ip; chỉ cần đọc trường origin thay vì ip.

Xử lý lỗi proxy

Bọc các request trong một try/except và bắt các exception cụ thể để bạn có thể phản ứng đúng thay vì làm sập cả lần chạy:

import requests

try:
    r = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=10)
    r.raise_for_status()
    print(r.json())
except requests.exceptions.ProxyError as exc:
    print("proxy dead or blocked:", exc)
except requests.exceptions.ConnectTimeout:
    print("proxy did not answer in time")
except requests.exceptions.HTTPError as exc:
    print("target returned an error status:", exc.response.status_code)
except requests.exceptions.RequestException as exc:
    print("request failed:", exc)

Hãy bắt từ cụ thể nhất đến tổng quát nhất; RequestException là lớp cơ sở và nên đặt cuối cùng làm bộ bắt tất cả. Đây là cách giải mã các lỗi thường gặp:

Triệu chứngNguyên nhân có thểCách khắc phục
"Max retries exceeded" / ProxyErrorEndpoint proxy chết, sai, hoặc bị chặnKiểm tra host/port, xoay sang một IP khác, xác nhận proxy còn sống
HTTP 407Xác thực proxy bị từ chốiSửa username/password; URL-encode password bằng quote
HTTP 429Bị mục tiêu giới hạn tốc độChậm lại, thêm backoff, xoay IP, giãn cách request
HTTP 403Mục tiêu đã chặn IP hoặc fingerprint nàyXoay IP, thử một loại proxy kín đáo hơn, xem lại header của request
ConnectTimeoutProxy còn sống nhưng chậm hoặc quá tảiTăng timeout kết nối, thử lại, loại IP chậm khỏi pool

Scraping có trách nhiệm

Proxy là một công cụ cho công việc chính đáng: thu thập dữ liệu công khai, theo dõi giá và SERP, xác minh quảng cáo, nghiên cứu thị trường, và dữ liệu SEO. Hãy giữ nó trong sạch - tôn trọng robots.txt và Điều khoản Dịch vụ của từng trang, chỉ nhắm dữ liệu công khai, điều tiết tốc độ request để bạn không làm suy giảm máy chủ của người khác, và tránh xa bất cứ thứ gì đằng sau một trang đăng nhập. Đây là thực hành tốt chung, không phải tư vấn pháp lý; hãy kiểm tra các quy định áp dụng cho dự án và khu vực pháp lý của riêng bạn. Để có bức tranh lớn hơn về việc làm điều này cho tốt, hãy xem hướng dẫn của chúng tôi về proxy cho web scraping.

Câu hỏi thường gặp

Thư viện requests có hỗ trợ SOCKS5 không?

Có, một khi bạn cài phần bổ sung bằng pip install "requests[socks]". Sau đó dùng một giá trị socks5h:// trong dict proxies để DNS được phân giải qua proxy.

Vì sao request của tôi vẫn hiển thị IP thật?

Gần như luôn là do thiếu key https trong khi bạn đang request một URL HTTPS, nên request đi trực tiếp. Hãy chắc chắn cả hai key httphttps đều được đặt, rồi chạy lại đoạn kiểm chứng ở trên.

Proxy miễn phí có ổn cho scraping bằng Python không?

Để học mã thì được. Với công việc thật, chúng chậm, tồn tại ngắn, và thường đã bị chặn hoặc không an toàn. Một gói miễn phí thật từ một nhà cung cấp trả phí (IP hạn chế nhưng đáng tin) là cách tốt hơn để kiểm thử đúng đoạn mã trước khi bạn trả tiền cho khối lượng.

Làm sao để có một IP mới ở mỗi request?

Hoặc tự xoay một pool bằng random.choice (Cách A) hoặc trỏ mọi request tới một endpoint xoay vòng của nhà cung cấp như <username>[email protected]:80 (Cách B), thứ sẽ thay IP thoát ra giúp bạn.

Tôi có thể dùng cùng thiết lập proxy với httpx hoặc aiohttp không?

Khái niệm vẫn áp dụng, nhưng cú pháp khác - httpxaiohttp nhận một URL proxy hoặc cấu hình theo từng client thay vì cùng một dict. Cho dòng lệnh, hãy xem hướng dẫn dùng proxy với curl của chúng tôi, và để có bức tranh lớn hơn thì xem tổng quan của chúng tôi về proxy cho web scraping.

Kết luận

Con đường luôn giống nhau: dựng dict proxies, thêm xác thực (URL-encode password), tái sử dụng một Session, đặt timeout và một adapter Retry, rồi xoay vòng - một pool thủ công cho vài IP, một endpoint xoay vòng của nhà cung cấp khi bạn mở rộng quy mô. Kiểm chứng IP đã đổi, bắt các lỗi, và chỉ thu thập dữ liệu công khai trong khuôn khổ quy định của từng trang. Khi bạn chọn một nhà cung cấp, bài tổng hợp của chúng tôi về các dịch vụ proxy tốt nhất so sánh các lựa chọn một cách trung thực. Để chạy mọi thứ ở trên mà không phải chi tiền trước, hãy lấy proxy miễn phí của Webshare, thả thông tin đăng nhập của bạn vào các đoạn mã, và xác nhận từng khối hoạt động trên chính các mục tiêu của bạn trước khi mở rộng.

J

Jasmine

Tác giả · Jasmine Daily

Người viết nên Jasmine Daily - ghi lại những suy nghĩ, trải nghiệm và những khoảnh khắc đời thường. Thật lòng, không vội vàng, không hoàn hảo.

Jasmine Daily

Vẫn còn nhiều điều đang chờ được đọc.

Nếu bài viết này chạm đến bạn, hãy ghé xem thêm vài trang khác trong cuốn nhật ký này.

Đọc tiếp

Bài viết liên quan