Scrapy vẫn là con ngựa thồ cho các lần crawl production trong năm 2026 — và vẫn là framework khiến người ta bối rối nhất về thiết lập proxy, bởi có tới bốn chỗ khác nhau để cắm proxy vào và ba trong số đó sai với hầu hết dự án. Hướng dẫn này cho bạn cái đúng: một middleware tùy chỉnh nhỏ với định tuyến theo từng request, sticky session, phát hiện ban, và hành vi retry hợp lý.
Nếu bạn đang dùng requests/httpx/aiohttp thuần thay vì vậy, hãy xem Cách Luân chuyển Proxy trong Python. Bài viết này dành riêng cho Scrapy.
Với một gateway residential luân chuyển, thiết lập tối thiểu khả dụng là một dòng cho mỗi request — không cần middleware:
def start_requests(self):
for url in self.urls:
yield scrapy.Request(
url,
meta={"proxy": "http://USERNAME:[email protected]:913"},
)
HttpProxyMiddleware tích hợp sẵn của Scrapy đọc request.meta["proxy"] và xử lý xác thực từ URL. Gateway luân chuyển exit IP giúp bạn. Nếu đó là tất cả những gì bạn cần, hãy dừng ở đây. Phần còn lại của hướng dẫn này dành cho khi bạn cần kiểm soát: sticky session, định tuyến theo quốc gia, luân chuyển nhận biết ban, và tinh chỉnh độ đồng thời (concurrency).
Bỏ đoạn này vào middlewares.py. Nó gán sticky session theo từng domain, luân chuyển khi bị ban, và gắn thẻ mọi request để bạn có thể gỡ lỗi xem session nào đã fetch cái gì:
import random
import string
GATEWAY = "us.jibaoproxy.com:913"
USERNAME = "USERNAME" # move to settings.py / env in real projects
PASSWORD = "PASSWORD"
def _new_session(n=8):
return "".join(random.choices(string.ascii_lowercase + string.digits, k=n))
class JibaoProxyMiddleware:
"""Sticky session per domain; rotate session on ban."""
def __init__(self):
self.sessions = {} # domain -> session id
def _proxy_url(self, session_id):
user = f"{USERNAME}-session-{session_id}"
return f"http://{user}:{PASSWORD}@{GATEWAY}"
def process_request(self, request, spider):
domain = request.url.split("/")[2]
session = self.sessions.setdefault(domain, _new_session())
request.meta["proxy"] = self._proxy_url(session)
request.meta["proxy_session"] = session
def rotate(self, domain):
"""Call when a session is burned."""
self.sessions[domain] = _new_session()
Và một downloader middleware đi kèm để phát hiện ban và thử lại trên một session mới:
from scrapy.downloadermiddlewares.retry import RetryMiddleware
from scrapy.utils.response import response_status_message
BAN_CODES = {403, 429}
BAN_MARKERS = (b"captcha", b"access denied", b"unusual traffic")
class BanAwareRetryMiddleware(RetryMiddleware):
def process_response(self, request, response, spider):
banned = (
response.status in BAN_CODES
or any(m in response.body[:2048].lower() for m in BAN_MARKERS)
)
if banned:
domain = request.url.split("/")[2]
proxy_mw = spider.crawler.engine.downloader.middleware.middlewares
for mw in proxy_mw:
if hasattr(mw, "rotate"):
mw.rotate(domain) # burn the session
reason = response_status_message(response.status)
return self._retry(request, reason, spider) or response
return super().process_response(request, response, spider)
Khai báo cả hai trong settings.py:
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.JibaoProxyMiddleware": 350,
"scrapy.downloadermiddlewares.retry.RetryMiddleware": None, # replace stock retry
"myproject.middlewares.BanAwareRetryMiddleware": 550,
}
RETRY_TIMES = 2
Thứ tự ưu tiên quan trọng: proxy middleware phải chạy trước HttpProxyMiddleware (750) của Scrapy, nên bất kỳ giá trị nào dưới 750 đều hoạt động; 350 giữ nó chạy sớm và dễ dự đoán.
| Loại crawl | Chế độ | Cách triển khai |
|---|---|---|
| Thu thập trang không trạng thái | Rotating | Username trần, gateway luân chuyển theo từng request |
| Đăng nhập + crawl sau xác thực | Sticky theo tài khoản | -session-{account_id}, không bao giờ luân chuyển giữa chừng khi đăng nhập |
| Danh sách nhiều trang (pagination) | Sticky theo domain, luân chuyển khi bị ban | Middleware ở trên |
| Giá theo khu vực địa lý | Rotating + ghim quốc gia | Tham số kiểu USERNAME-country-de |
Phân tích sâu hơn về sự đánh đổi này: Sticky so với Rotating Proxy Session.
Các giá trị mặc định của Scrapy được tinh chỉnh cho việc crawl lịch sự bằng một IP. Đứng sau một pool luân chuyển, bạn có thể đẩy mạnh hơn nhiều — nhưng giới hạn theo từng domain vẫn quan trọng vì mục tiêu nhìn thấy hành vi tổng hợp:
# settings.py - sane starting point behind a residential pool
CONCURRENT_REQUESTS = 64
CONCURRENT_REQUESTS_PER_DOMAIN = 8 # what the target experiences
DOWNLOAD_DELAY = 0.25 # jitter applied per slot
RANDOMIZE_DOWNLOAD_DELAY = True # 0.5x-1.5x the delay
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 6.0
DOWNLOAD_TIMEOUT = 30
ROBOTSTXT_OBEY = True
Chỉ tăng CONCURRENT_REQUESTS_PER_DOMAIN sau khi đã theo dõi tỷ lệ 403 ở mức hiện tại trong vài nghìn request. Nhảy 8 → 32 vì "đằng nào proxy cũng luân chuyển" chính là cách người ta đốt sạch GB vào việc retry.
IMAGES_STORE để fetch qua một proxy datacenter hoặc kết nối trực tiếp nếu CDN không quan tâm — media chiếm phần lớn GB của bạn và hiếm khi được bảo vệ.HTTPCACHE_ENABLED = True trong lúc bạn lặp lại việc chỉnh parser sẽ phát lại response từ đĩa thay vì fetch lại qua pool. Một cài đặt này thường giảm một nửa hóa đơn băng thông của dự án.COMPRESSION_ENABLED = True (mặc định) — HTML đã gzip nhỏ hơn 5–10 lần khi truyền qua đường truyền.407 Proxy Authentication RequiredThông tin đăng nhập chưa tới được proxy. Hãy đặt chúng trong URL (http://user:pass@host:port) ở meta["proxy"] — Scrapy phân tích và đặt Proxy-Authorization cho bạn. Việc đặt header thủ công đồng thời dùng thông tin đăng nhập trong URL gây ra hiện tượng xác thực kép kỳ lạ; hãy chọn một cách.
TunnelError: Could not open CONNECT tunnelHầu như luôn là gõ sai host/port, hoặc mục tiêu HTTPS qua một endpoint không cho phép CONNECT trên cổng đó. Hãy kiểm chứng bằng curl -x bên ngoài Scrapy trước.
Sticky session của bạn đã sống quá lâu so với mức hoan nghênh, hoặc tốc độ theo từng domain của bạn quá nóng. Middleware nhận biết ban ở trên xử lý trường hợp đầu; hạ CONCURRENT_REQUESTS_PER_DOMAIN cho trường hợp sau. Nếu đó là một mục tiêu kiểm tra JA4, bản thân TLS stack của Scrapy có thể chính là dấu hiệu tố giác — xem giải thích JA3/JA4 để hiểu vì sao không proxy nào sửa được điều đó.
meta["proxy"] với URL gateway là toàn bộ thiết lập.Session residential rotating và sticky trên cùng một gateway. 500MB lưu lượng miễn phí để crawl.
Bắt đầu Dùng thử Miễn phíNgười dùng mới nhận 500MB khi đăng ký, cùng tiền thưởng cho lần nạp đầu tiên. Ưu đãi có thời hạn.