Quay lại danh sách
Cấu hình proxy xoay và sticky trong Scrapy cho scraping ổn định

Cấu hình proxy xoay và sticky trong Scrapy cho scraping ổn định

18 tháng 10, 2026

Scrapy là framework crawl mã nguồn mở phổ biến cho Python, nhưng khi cần thu thập dữ liệu từ hàng nghìn trang web, việc gửi tất cả request từ một địa chỉ IP sẽ nhanh chóng bị chặn. Bài viết này tập trung vào một khía cạnh cụ thể: cách cấu hình proxy xoay (rotating) và sticky session trong Scrapy, kèm mã nguồn có thể chạy ngay để giữ phiên làm việc, tránh lỗi 429 và tăng độ tin cậy cho hệ thống thu thập dữ liệu.

1. Tại sao Scrapy cần quản lý proxy riêng lẻ

Scrapy xử lý request bất đồng bộ và có thể phát ra hàng trăm kết nối mỗi phút. Nếu không dùng proxy, tất cả các kết nối này xuất phát từ cùng một IP, dễ kích hoạt giới hạn tốc độ (rate limit), CAPTCHA hoặc chặn vĩnh viễn của trang mục tiêu.

Proxy xoay thay đổi địa chỉ IP cho mỗi request hoặc mỗi nhóm request, giúp phân tán lưu lượng. Proxy sticky giữ nguyên cùng một IP trong suốt một phiên làm việc, điều cần thiết khi bạn phải đăng nhập, duy trì cookie hoặc theo dõi trạng thái giỏ hàng trên trang thương mại điện tử. Không phân biệt rõ hai chế độ là lỗi phổ biến dẫn đến việc phiên đăng nhập bị mất hoặc IP bị danh sách đen nhanh chóng.

2. Cấu hình cơ bản trong settings.py

Trước hết, bạn cần lưu danh sách proxy và bật cơ chế thử lại khi gặp lỗi mạng.

2.1 Danh sách proxy và xác thực

Tạo danh sách proxy trong settings.py. Nếu proxy yêu cầu tên người dùng và mật khẩu, hãy nhúng thông tin xác thực trực tiếp vào URL:

PROXY_LIST = [
    'http://user:pass@proxy1.rop.com:8000',
    'http://user:pass@proxy2.rop.com:8000',
    'http://user:pass@proxy3.rop.com:8000',
]

PROXY_MODE = 0  # 0 = xoay IP mỗi request, 1 = dùng một proxy cố định, 2 = custom

2.2 Bật RetryMiddleware

Scrapy có RetryMiddleware sẵn, bạn cần cấu hình lại mã trạng thái cần thử lại:

RETRY_TIMES = 3
RETRY_HTTP_CODES = [429, 500, 502, 503, 504]
DOWNLOAD_DELAY = 1
CONCURRENT_REQUESTS_PER_DOMAIN = 8

Việc đặt DOWNLOAD_DELAY giúp tránh gây quá tải trang đích, trong khi CONCURRENT_REQUESTS_PER_DOMAIN kiểm soát số kết nối song song đến mỗi tên miền.

3. Xây dựng Middleware xoay IP

Tạo tệp middlewares.py trong dự án Scrapy. Lớp middleware dưới đây chọn ngẫu nhiên một proxy từ danh sách cho mỗi request mới:

import random
from scrapy import signals

class RotatingProxyMiddleware:
    def __init__(self, proxy_list):
        self.proxy_list = proxy_list

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            proxy_list=crawler.settings.getlist('PROXY_LIST')
        )

    def process_request(self, request, spider):
        # Nếu request đã chỉ định proxy sticky, không thay đổi
        if request.meta.get('sticky_proxy'):
            return None
        proxy = random.choice(self.proxy_list)
        request.meta['proxy'] = proxy

Kích hoạt middleware trong settings.py:

DOWNLOADER_MIDDLEWARES = {
    'myproject.middlewares.RotatingProxyMiddleware': 350,
    'scrapy.downloadermiddlewares.retry.RetryMiddleware': 360,
}

4. Giữ phiên làm việc dính qua meta

Đôi khi bạn cần giữ cùng một IP cho nhiều request liên tiếp. Cách đơn giản nhất là gán một proxy cố định vào request.meta và đánh dấu sticky_proxy = True:

def start_requests(self):
    sticky_proxy = 'http://user:pass@proxy-sticky.rop.com:8000'
    yield scrapy.Request(
        url='https://example.com/login',
        meta={
            'proxy': sticky_proxy,
            'sticky_proxy': True,
            'session_id': 'session_123',
        },
        callback=self.parse_login,
    )

Tất cả request con trong phiên này cũng nên dùng cùng sticky_proxy để cookie và phiên đăng nhập không bị mất.

5. Ví dụ thực tế: scraping danh sách sản phẩm đa trang

Dưới đây là spider thu thập sản phẩm từ trang giả định, dùng proxy xoay cho danh sách và proxy sticky cho trang chi tiết yêu cầu đăng nhập:

import scrapy

class ProductSpider(scrapy.Spider):
    name = 'product_spider'
    start_urls = ['https://example.com/products?page=1']

    def parse(self, response):
        for item in response.css('.product-item'):
            yield {
                'title': item.css('h2::text').get(),
                'price': item.css('.price::text').get(),
            }

        next_page = response.css('a.next::attr(href)').get()
        if next_page:
            yield response.follow(next_page, callback=self.parse)

    def parse_detail(self, response):
        # Trang này yêu cầu đăng nhập, nên dùng sticky proxy
        yield {
            'detail': response.css('.description::text').get(),
        }

Khi chạy:

scrapy crawl product_spider

Scrapy sẽ tự xoay IP cho các trang danh sách, còn request chi tiết giữ IP cố định nếu bạn truyền meta.sticky_proxy.

6. Xử lý lỗi 403/429 và loại proxy chết

Khi một proxy trả về 429 hoặc 403, bạn nên đánh dấu nó là bị chặn và tạm thời loại khỏi danh sách. Cách làm đơn giản là xử lý trong process_response của middleware:

def process_response(self, request, response, spider):
    if response.status in [403, 429]:
        proxy = request.meta.get('proxy')
        if proxy in self.proxy_list:
            self.proxy_list.remove(proxy)
            spider.logger.warning(f'Loại proxy bị chặn: {proxy}')
        # Trả lại request để Scrapy thử lại với proxy khác
        request.meta['proxy'] = random.choice(self.proxy_list)
        return request
    return response

Nếu danh sách proxy rỗng, hãy dừng spider và cảnh báo quản trị viên bổ sung IP mới.

7. Mẹo tối ưu và lưu ý quan trọng

  • Kết hợp RotatingProxyMiddleware với User-Agent middleware để giảm dấu vết bot.
  • Giảm CONCURRENT_REQUESTS nếu bạn nhận thấy nhiều lỗi 429.
  • Luôn tuân thủ robots.txt và điều khoản dịch vụ của trang mục tiêu.
  • Không thu thập dữ liệu cá nhân hoặc nội dung bản quyền mà không có sự cho phép.
  • Theo dõi thời gian phản hồi của từng proxy để loại bỏ IP chậm trước khi ảnh hưởng đến hiệu năng.

Kết luận

Việc cấu hình proxy xoay và sticky trong Scrapy giúp bạn thu thập dữ liệu ổn định hơn, giảm nguy cơ bị chặn IP và giữ phiên đăng nhập khi cần. Hãy bắt đầu bằng danh sách proxy nhỏ, kiểm tra mã trạng thái 429/403, rồi dần mở rộng hệ thống khi dự án phát triển.