BinProxy
Hướng dẫn

Cách dùng proxy với Python requests: HTTP, SOCKS5 proxy, xoay IP và xử lý lỗi

Hướng dẫn từng bước dùng proxy với thư viện requests của Python: proxy HTTP, SOCKS5 proxy, timeout, Session, proxy xoay IP và giữ IP theo phiên, tự thử lại khi lỗi, kèm đoạn code hoàn chỉnh.

7 phút đọc
Lập trình viên làm việc trước màn hình có trình soạn thảo code trong một góc làm việc tại nhà
Mục lục
  1. Bước 1: Cài thư viện
  2. Bước 2: Lấy dòng proxy dạng URL
  3. Bước 3: Gửi yêu cầu đầu tiên qua proxy
  4. Bước 4: Luôn đặt timeout
  5. Bước 5: Dùng Session cho nhiều yêu cầu
  6. Bước 6: Chuyển sang SOCKS5 proxy
  7. Bước 7: Xoay IP hay giữ IP
  8. Bước 8: Tự thử lại khi gặp lỗi tạm thời
  9. Bước 9: Bắt những lỗi hay gặp
  10. Đoạn code hoàn chỉnh
  11. Câu hỏi thường gặp
  12. Vì sao requests báo lỗi "Missing dependencies for SOCKS support"?
  13. Có cần đặt proxy cho cả http và https không?
  14. Làm sao để mỗi yêu cầu một IP khác nhau?
  15. Timeout bao nhiêu là hợp lý?
  16. Có dùng biến môi trường HTTP_PROXY thay cho tham số proxies được không?

Python và thư viện requests là bộ đôi quen thuộc nhất khi cần lấy dữ liệu từ web. Gắn proxy vào requests thì chỉ cần vài dòng, nhưng để chạy ổn định lâu dài, bạn cần thêm vài thứ mà ví dụ ngắn trên mạng hay bỏ qua: timeout, tự thử lại khi lỗi, cách xoay IP hay giữ IP. Bài này đi từng bước, từ yêu cầu đầu tiên qua proxy HTTP, sang SOCKS5 proxy, rồi tới proxy xoay IP và xử lý lỗi, kèm một đoạn code hoàn chỉnh ở cuối để bạn sao chép.

Mọi ví dụ trong bài dùng proxy dân cư của BinProxy, nhưng cách làm giống hệt với bất kỳ dịch vụ proxy nào có tên đăng nhập và mật khẩu.

Bước 1: Cài thư viện

Nếu chưa có requests, bạn cài bằng pip. Muốn dùng proxy SOCKS5, cài thêm phần hỗ trợ SOCKS ngay từ đầu cho tiện:

python -m pip install requests
python -m pip install "requests[socks]"

Theo tài liệu của requests, hỗ trợ SOCKS có từ phiên bản 2.10.0 và là phần cài thêm, nên thiếu nó thì requests sẽ báo lỗi ngay khi bạn dùng địa chỉ socks5://.

Bước 2: Lấy dòng proxy dạng URL

Trên trang Tạo proxy của BinProxy, ở ô Định dạng, bạn chọn http://user:pass@host:port rồi sao chép một dòng. Dòng proxy sẽ có dạng:

http://USER-cc-VN:[email protected]:80

Phần -cc-VN là quốc gia bạn đã chọn. Tài liệu requests lưu ý rằng địa chỉ proxy phải có phần giao thức ở đầu (http:// hay socks5h://), nên đừng chỉ dán host:port.

Bước 3: Gửi yêu cầu đầu tiên qua proxy

import requests

proxy = "http://USER-cc-VN:[email protected]:80"
proxies = {"http": proxy, "https": proxy}

r = requests.get("https://ipinfo.io/json", proxies=proxies, timeout=(5, 30))
print(r.json())

Hai khoá http và https trong proxies cho biết proxy nào dùng cho trang http:// và trang https://. Với proxy dân cư, bạn để cả hai cùng một dòng. Kết quả in ra sẽ cho biết IP, quốc gia và nhà mạng mà website đích nhìn thấy.

Bước 4: Luôn đặt timeout

Đây là điều quan trọng nhất trong bài. Tài liệu requests ghi rõ: mặc định requests không bao giờ hết thời gian chờ nếu bạn không đặt timeout. Với proxy dân cư, thỉnh thoảng sẽ có kết nối treo vì thiết bị phía sau mất mạng, và không có timeout thì chương trình của bạn đứng im mãi mãi.

Bạn nên dùng dạng hai số timeout=(5, 30):

  • Số đầu là thời gian chờ kết nối: 5 giây là đủ để biết proxy có nhận kết nối hay không.
  • Số sau là thời gian chờ đọc, tức khoảng chờ giữa các lần nhận dữ liệu, không phải tổng thời gian tải trang.

Bước 5: Dùng Session cho nhiều yêu cầu

Khi gửi nhiều yêu cầu, Session giúp tái sử dụng kết nối và đỡ phải lặp lại cấu hình. Có một cái bẫy nhỏ: tài liệu requests cho biết proxy đặt trong session.proxies có thể bị biến môi trường như HTTP_PROXY ghi đè. Vì vậy, cách chắc chắn nhất là vẫn truyền proxies= vào từng yêu cầu:

s = requests.Session()
for url in ["https://example.com/a", "https://example.com/b"]:
    r = s.get(url, proxies=proxies, timeout=(5, 30))
    print(url, r.status_code)

Bước 6: Chuyển sang SOCKS5 proxy

Đổi sang SOCKS5 chỉ là đổi phần đầu của dòng proxy. BinProxy dùng chung một địa chỉ và cổng cho cả HTTP lẫn SOCKS5:

proxy = "socks5h://USER-cc-VN:[email protected]:80"
proxies = {"http": proxy, "https": proxy}

Chữ h trong socks5h nghĩa là để proxy tự tra tên miền thay vì máy bạn. Với proxy dân cư, chúng tôi khuyên dùng socks5h. Lý do và sự khác nhau giữa hai loại giao thức có trong bài SOCKS5 proxy là gì.

Bước 7: Xoay IP hay giữ IP

Mặc định, dòng proxy BinProxy ở chế độ IP xoay: mỗi yêu cầu đi ra từ một IP mới. Bạn không phải làm gì thêm.

Khi cần nhiều yêu cầu liên tiếp dùng cùng một IP, bạn thêm mã phiên và thời gian giữ vào tên đăng nhập: -s-<mã phiên>-ttl-<số giây>. Mã phiên gồm chữ thường và chữ số, không có dấu gạch nối, giống mã mà bảng điều khiển tự tạo. Ví dụ tạo một phiên giữ IP 10 phút:

import secrets
import string

def sticky_proxy(user, password, minutes=10):
    session_id = "".join(secrets.choice(string.ascii_lowercase + string.digits) for _ in range(8))
    login = f"{user}-s-{session_id}-ttl-{minutes * 60}"
    url = f"http://{login}:{password}@proxy.binproxy.com:80"
    return {"http": url, "https": url}

proxies = sticky_proxy("USER-cc-VN", "PASS")

Gọi lại sticky_proxy() là có phiên mới, tức IP mới. Bạn nhớ rằng IP dân cư có thể đổi sớm hơn thời gian giữ nếu thiết bị đứng sau nó mất kết nối. Bài proxy đổi IP là gì giải thích kỹ chuyện này.

Bước 8: Tự thử lại khi gặp lỗi tạm thời

Requests mặc định không tự thử lại khi kết nối hỏng. Bạn có thể bật việc này bằng Retry của thư viện urllib3, gắn vào Session qua HTTPAdapter:

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

retries = Retry(
    total=4,
    backoff_factor=0.5,
    status_forcelist=[429, 502, 503, 504],
)
s = requests.Session()
s.mount("http://", HTTPAdapter(max_retries=retries))
s.mount("https://", HTTPAdapter(max_retries=retries))

backoff_factor quyết định thời gian chờ giữa các lần thử. Theo tài liệu urllib3, thời gian chờ bằng backoff_factor × 2^(số lần đã thử trước đó), nên mỗi lần sau chờ lâu gấp đôi lần trước. Tài liệu đưa ví dụ với hệ số 0,1: các khoảng chờ là 0; 0,2; 0,4; 0,8 giây, tức lần thử lại đầu tiên chạy ngay. Với hệ số 0,5 như trên, bạn sẽ có 0, 1, 2 rồi 4 giây. Với mã 429 (gửi quá nhiều yêu cầu) và 503, urllib3 mặc định tôn trọng tiêu đề Retry-After mà website gửi về, tức là chờ đúng khoảng website yêu cầu.

Sơ đồ tự thử lại khi lỗi với hệ số 0,5: yêu cầu đầu lỗi, lần thử lại thứ nhất chạy ngay, sau đó chờ 1 giây, 2 giây, 4 giây, mỗi lần chờ gấp đôi lần trước

Với proxy xoay, mỗi lần thử lại thường đi ra từ một IP khác, nên một lỗi do riêng một IP sẽ tự qua ở lần sau.

Bước 9: Bắt những lỗi hay gặp

Khi đã thử lại hết số lần mà vẫn lỗi, bạn nên bắt lỗi để chương trình không dừng giữa chừng:

from requests.exceptions import ConnectTimeout, ProxyError, ReadTimeout

try:
    r = s.get("https://example.com", proxies=proxies, timeout=(5, 30))
except ProxyError:
    print("Proxy từ chối kết nối: kiểm tra user/mật khẩu, bộ lọc vị trí, dung lượng còn lại")
except ConnectTimeout:
    print("Không kết nối được tới proxy trong thời gian chờ")
except ReadTimeout:
    print("Website trả lời quá chậm")

Tài liệu requests ghi rằng yêu cầu gặp ConnectTimeout luôn an toàn để thử lại. Còn ý nghĩa từng lỗi và cách sửa, bạn xem bài lỗi proxy thường gặp và cách khắc phục.

Đoạn code hoàn chỉnh

Gom tất cả lại, đây là một khung bạn có thể dùng ngay:

import requests
from requests.adapters import HTTPAdapter
from requests.exceptions import RequestException
from urllib3.util import Retry

PROXY = "http://USER-cc-VN:[email protected]:80"  # dòng proxy của bạn
PROXIES = {"http": PROXY, "https": PROXY}

retries = Retry(total=4, backoff_factor=0.5, status_forcelist=[429, 502, 503, 504])
s = requests.Session()
s.mount("http://", HTTPAdapter(max_retries=retries))
s.mount("https://", HTTPAdapter(max_retries=retries))

def fetch(url):
    try:
        r = s.get(url, proxies=PROXIES, timeout=(5, 30))
        r.raise_for_status()
        return r.text
    except RequestException as error:
        print("Bỏ qua", url, "vì", type(error).__name__)
        return None

html = fetch("https://example.com")

Cận cảnh hai tay gõ trên bàn phím cơ, phía sau là màn hình cửa sổ dòng lệnh mờ

Khi đã chạy ổn, bạn nên theo dõi dung lượng dùng mỗi ngày trên trang Tổng quan. Nếu chỉ cần nội dung HTML, requests vốn đã tiết kiệm hơn trình duyệt tự động rất nhiều, vì nó không tải ảnh, font hay JavaScript. Bài 1 GB proxy dùng được bao nhiêu có con số cụ thể để bạn ước lượng trước khi xem bảng giá.

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

Vì sao requests báo lỗi "Missing dependencies for SOCKS support"?

Vì bạn chưa cài phần hỗ trợ SOCKS. Chạy python -m pip install "requests[socks]" một lần là hết lỗi.

Có cần đặt proxy cho cả http và https không?

Nên đặt cả hai. Khoá http dùng cho trang http://, khoá https dùng cho trang https://. Nếu thiếu khoá nào, yêu cầu tới loại trang đó sẽ đi thẳng không qua proxy.

Làm sao để mỗi yêu cầu một IP khác nhau?

Với BinProxy, dòng proxy mặc định đã xoay IP theo từng yêu cầu. Bạn chỉ cần không thêm phần mã phiên -s-… vào tên đăng nhập.

Timeout bao nhiêu là hợp lý?

Một mức hay dùng là 5 giây cho kết nối và 30 giây cho việc đọc. Proxy dân cư đi qua mạng gia đình nên nên để thời gian đọc rộng hơn một chút so với khi gọi thẳng.

Có dùng biến môi trường HTTP_PROXY thay cho tham số proxies được không?

Được, requests đọc các biến HTTP_PROXY, HTTPS_PROXY, NO_PROXY và ALL_PROXY. Nhưng khi chương trình lớn dần, truyền proxies= rõ ràng trong code sẽ dễ kiểm soát hơn.

Bài viết liên quan