Xoay danh sách proxy trong Python: tự xoay IP tĩnh bằng requests
Cập nhật:
Muốn xoay danh sách proxy trong Python, bạn chỉ cần ba thứ: một file chứa các proxy dạng ip:port:user:pass, một hàm chuyển mỗi dòng thành URL http://user:pass@ip:port, và một bộ chọn proxy cho từng request (xoay vòng, ngẫu nhiên hoặc gắn cố định theo phiên). Không cần dịch vụ proxy xoay riêng: với một nhóm IP tĩnh dùng riêng, bạn tự quyết định IP nào gửi request nào, bao lâu thì đổi và khi nào loại IP lỗi ra. Bài này đi từ cách đọc danh sách, ba kiểu xoay phổ biến, đến xử lý lỗi và chạy song song, kèm code mẫu dùng thư viện requests.
Tự xoay IP tĩnh khác gì dịch vụ proxy xoay
Dịch vụ proxy xoay cho bạn một cổng duy nhất, phía sau nhà cung cấp tự đổi IP theo từng request hoặc theo thời gian. Bạn không biết trước IP nào sẽ được dùng, cũng không chọn được IP nào giữ lại. Cách đó tiện cho việc cào số lượng rất lớn, nhưng khó kiểm soát khi cần biết chính xác request nào đi ra từ IP nào. Nếu muốn hiểu kỹ hai mô hình, đọc bài proxy tĩnh và proxy xoay khác nhau thế nào.
Khi tự xoay một danh sách IP tĩnh, mọi quyết định nằm trong code của bạn. Mỗi IP là của riêng bạn, không ai khác dùng chung, nên uy tín của IP phụ thuộc vào cách bạn gửi request. Bạn có thể ghi log từng request kèm IP, loại tạm một IP khi website trả lỗi, và giữ cố định IP cho một phiên đăng nhập. Đổi lại, bạn phải tự viết phần chọn proxy, retry và giới hạn tốc độ. Phần còn lại của bài là những đoạn code tối thiểu để làm việc đó cho đúng.
Bước 1: chuẩn bị danh sách proxy
Proxy được cấp ở định dạng ip:port:user:pass, mỗi dòng một proxy. Lưu toàn bộ vào file proxies.txt. Thư viện requests cần dạng URL, nên ta chuyển đổi một lần khi đọc file. Nếu mật khẩu có ký tự đặc biệt như @, : hay #, phải mã hoá URL, nếu không requests sẽ hiểu sai và bạn nhận lỗi 407 Proxy Authentication Required. Bạn cũng có thể đổi định dạng hàng loạt bằng công cụ chuyển định dạng proxy.
from urllib.parse import quotedef to_url(line):ip, port, user, pw = line.strip().split(':', 3)return 'http://' + quote(user, safe='') + ':' + quote(pw, safe='') + '@' + ip + ':' + portPROXIES = [to_url(l) for l in open('proxies.txt') if l.strip()]- Dùng scheme
http://cho cả khoáhttpvàhttpstrong dictproxies. Với trang HTTPS,requestsmở đường hầm CONNECT qua proxy HTTP, nên không cần proxy hỗ trợ TLS riêng.
Bước 2: ba cách xoay danh sách
Không có cách xoay nào đúng cho mọi trường hợp. Chọn theo việc bạn làm: cào trang công khai không đăng nhập thì xoay đều là đủ, còn việc có phiên đăng nhập hoặc giỏ hàng thì phải giữ một IP cho cả phiên.
- Ví dụ xoay vòng:
for url in urls: p = next(pool); r = requests.get(url, proxies={'http': p, 'https': p}, timeout=(5, 20)) - Ví dụ sticky: đặt
keylà tên tài khoản hoặc domain, cùng một khoá luôn ra cùng một IP, kể cả khi bạn khởi động lại script. Dùngzlib.crc32thay chohash()vìhash()của Python đổi giá trị giữa các lần chạy.
| Cách xoay | Code cốt lõi | Khi nào dùng |
|---|---|---|
| Xoay vòng tròn | pool = itertools.cycle(PROXIES) rồi next(pool) | Cào trang công khai, muốn tải chia đều cho mọi IP |
| Ngẫu nhiên | random.choice(PROXIES) | Thứ tự request không quan trọng, muốn tránh nhịp lặp đều đặn |
| Gắn theo khoá (sticky) | PROXIES[zlib.crc32(key.encode()) % len(PROXIES)] | Một tài khoản, một phiên hoặc một domain luôn đi qua cùng một IP |
Bước 3: dùng Session và gắn proxy theo phiên
Gọi requests.get() cho từng URL sẽ mở kết nối mới mỗi lần, tốn thời gian bắt tay TLS. Với một nhóm request cùng IP, hãy tạo một requests.Session() riêng cho mỗi proxy và tái sử dụng nó. Session giữ kết nối mở và giữ cookie, nên rất hợp với kiểu xoay sticky: mỗi phiên là một cặp IP và cookie cố định.
Một mẫu đơn giản là tạo dict sessions = {}, khi cần thì lấy s = sessions.setdefault(p, requests.Session()) rồi gán s.proxies = {'http': p, 'https': p}. Đừng dùng chung một Session rồi đổi proxy liên tục giữa các request, vì cookie của IP trước sẽ bị gửi kèm theo IP sau, website dễ thấy một phiên nhảy IP bất thường. Hướng dẫn cấu hình chi tiết hơn có trong trang cài proxy cho Python requests.
Bước 4: retry sang IP khác và tạm loại IP lỗi
Trong danh sách vài chục IP, sẽ có lúc một IP bị timeout hoặc website trả 403, 429 cho riêng IP đó. Code tốt không dừng cả script mà chuyển request sang IP khác, đồng thời cho IP vừa lỗi nghỉ một lúc. Cách làm gọn nhất là lưu thời điểm IP được dùng lại trong một dict cooldown = {}.
- Bắt lỗi kết nối:
except (requests.exceptions.ProxyError, requests.exceptions.ConnectTimeout, requests.exceptions.ReadTimeout)thì ghi nhận lỗi và thử IP tiếp theo. - Gặp mã 429 hoặc 403: đặt
cooldown[p] = time.time() + 300để IP nghỉ năm phút; khi chọn proxy, bỏ qua những IP cócooldown.get(p, 0) > time.time(). - Giới hạn số lần thử, ví dụ tối đa ba IP cho một URL, để một trang hỏng không ăn hết cả danh sách.
- Ghi log mỗi request gồm URL, IP, mã trạng thái và thời gian phản hồi. Khi có vấn đề, log này cho biết lỗi nằm ở một IP, ở cả nhóm hay ở website đích.
- Nếu muốn đi xa hơn với theo dõi IP sống chết và phân bổ tải, đọc bài quản lý proxy pool.
Bước 5: chạy song song mà không dồn tải lên một IP
Khi dùng concurrent.futures.ThreadPoolExecutor, số luồng nên tỷ lệ với số IP chứ không phải đặt thật cao cho nhanh. Một quy tắc khởi đầu an toàn là số luồng không vượt quá số IP, và mỗi IP chỉ xử lý một request tại một thời điểm. Có thể làm việc này bằng queue.Queue chứa các proxy rảnh: luồng lấy một proxy ra, gửi request, chờ một khoảng ngắn rồi trả proxy về hàng đợi.
Thêm độ trễ ngẫu nhiên nhỏ giữa hai request trên cùng một IP, ví dụ time.sleep(random.uniform(1, 3)), giúp nhịp truy cập bớt máy móc và nhẹ cho máy chủ đích. Tốc độ phù hợp phụ thuộc vào từng website; bài giới hạn tốc độ khi cào dữ liệu giải thích cách ước lượng số request mỗi IP và số IP cần cho một khối lượng công việc.
Checklist trước khi chạy thật
- Kiểm tra toàn bộ danh sách trước khi chạy bằng công cụ check proxy hoặc một request thử tới trang trả về IP, ví dụ
requests.get('https://httpbin.org/ip', proxies=..., timeout=10). - Luôn đặt
timeout, không bao giờ để mặc định, nếu không một IP treo có thể làm luồng đứng mãi. - Đặt
User-Agenthợp lý và giữ nhất quán trong một phiên. - Tôn trọng
robots.txtvà điều khoản của website; chỉ thu thập dữ liệu công khai mà bạn được phép dùng. - Không lưu mật khẩu proxy trong mã nguồn đưa lên Git; đọc từ file hoặc biến môi trường.
- Khi IP hết hạn thuê, gia hạn trên dashboard để giữ nguyên IP, tránh phải sửa lại danh sách và mất lịch sử log theo IP.
Câu hỏi thường gặp
Xoay danh sách proxy tĩnh có thay được proxy xoay dân cư không?
Với cào trang công khai ở tốc độ vừa phải thì thường là đủ, vì bạn chia tải cho nhiều IP riêng. Nếu website chặn mạnh mọi dải datacenter hoặc cần hàng nghìn IP khác nhau, tự xoay một danh sách nhỏ sẽ không thay được. VinaProxy hiện chưa bán proxy dân cư xoay.
Nên đổi IP sau mỗi request hay sau một khoảng thời gian?
Trang công khai không đăng nhập có thể đổi sau mỗi request. Việc có cookie, giỏ hàng hoặc đăng nhập thì giữ một IP cho cả phiên, vì đổi IP giữa phiên là tín hiệu bất thường.
Vì sao proxy chạy trong trình duyệt nhưng lỗi 407 trong Python?
Thường do mật khẩu có ký tự đặc biệt chưa được mã hoá URL, hoặc sai thứ tự user và pass khi tách chuỗi. Dùng quote(pw, safe='') như code mẫu trong bài.
Code này có dùng được với httpx hoặc aiohttp không?
Phần đọc danh sách và chọn proxy giữ nguyên. Chỉ khác cách truyền proxy: httpx nhận proxy khi tạo client, aiohttp nhận tham số proxy trong từng request.
Proxy có hỗ trợ SOCKS5 để dùng với requests không?
Proxy của VinaProxy dùng giao thức HTTP/HTTPS với xác thực user và mật khẩu. Bạn dùng scheme http:// như trong bài, không cần cài thêm gói SOCKS.
Tạo danh sách IP riêng để tự xoay
Proxy IPv4 riêng ở 7 nước, mỗi IP chỉ cấp cho một khách, cố định suốt thời gian thuê, không giới hạn băng thông, giao định dạng ip:port:user:pass.
Xem bảng giá