Scarnox · Blog

Scrape không bị chặn: hướng dẫn thực tế kèm code

Bị chặn khi scraping thường có nghĩa là một trong ba điều: bạn gửi quá nhiều request quá nhanh, client của bạn trông không giống trình duyệt nào hoặc giống một bot đã biết, hoặc địa chỉ IP của bạn nằm trong danh sách mà trang ít tin tưởng hơn. Bạn có thể giảm cả ba bằng vài thói quen: nhận ra chặn sớm, chậm lại khi trang đẩy lùi, giữ một danh tính nhất quán và trung thực, và chỉ dùng proxy ở nơi phù hợp. Hướng dẫn này đi qua từng điểm với code chạy được. Chỉ dùng nó cho các trang bạn được phép scrape.

Nhận biết một lần chặn

Một lần chặn không phải lúc nào cũng trông như trang lỗi. Hãy chú ý:

Hãy ghi log mã trạng thái và một đoạn mẫu ngắn của phần thân cho mọi lỗi. Một lần chặn mà bạn không thấy trong log là một lần chặn bạn không thể sửa.

Lùi lại bằng retry

Khi trang nói "chậm lại", code của bạn nên chậm lại nhiều hơn một chút. Backoff lũy thừa, với giới hạn số lần thử, là cách chuẩn. Nó cũng tôn trọng Retry-After khi trang gửi header đó:

import time
import requests

session = requests.Session()
session.headers.update({
    "User-Agent": "ExampleBot/1.0 (+https://example.com/bot)",
    "Accept-Language": "en-US,en;q=0.9",
})

def polite_get(url: str, max_attempts: int = 4) -> requests.Response:
    delay = 2.0
    resp = None
    for _ in range(max_attempts):
        resp = session.get(url, timeout=30)
        if resp.status_code not in (429, 503):
            return resp
        retry_after = resp.headers.get("Retry-After", "")
        wait = float(retry_after) if retry_after.isdigit() else delay
        time.sleep(wait)
        delay *= 2
    return resp  # vẫn bị chặn: trả về phản hồi cuối cùng để code gọi xử lý

resp = polite_get("https://example.com/products?page=1")
print(resp.status_code, len(resp.text))

Lưu ý hàm này không làm gì. Nó không retry một 403, và nó không xoay qua các danh tính để vượt qua một chặn. Một 403 là quyết định dừng lại, không phải một câu đố cần giải.

Chẩn đoán một lần chặn từng bước

Khi một lần chạy bắt đầu thất bại, hãy đi qua các kiểm tra sau trước khi thay đổi bất cứ điều gì khác:

  1. Xem URL thất bại đầu tiên và mã trạng thái của nó. Một lần chặn thường bắt đầu tại một điểm cụ thể, không ngẫu nhiên.
  2. So sánh tốc độ request trong vài phút trước lần thất bại đầu tiên. Đợt bùng phát là nguyên nhân phổ biến nhất.
  3. Kiểm tra xem lỗi có gắn với một đường dẫn, một mục hay một khung giờ trong ngày không.
  4. Tải cùng URL một lần bằng tay trong một trình duyệt bình thường. Nếu trình duyệt tải được, khác biệt nằm ở mẫu request của bạn. Nếu trình duyệt cũng bị chặn, trang tạm thời không phục vụ trang đó cho bạn.
  5. Chỉ sau đó mới thay đổi code. Chậm lại trước, backoff thứ hai, và xem xét danh tính cuối cùng.

Thay đổi nhiều thứ cùng lúc khiến bạn không biết điều nào đã giúp.

Giữ một danh tính nhất quán và trung thực

Scraper bị chặn khi danh tính của chúng thay đổi giữa các request, hoặc khi nó không khớp với phần còn lại của lưu lượng. Một phiên duy nhất với một User-Agent rõ ràng là danh tính ổn định đơn giản nhất. Đặt tên cho bot của bạn, thêm một liên kết liên hệ và chỉ gửi những header mà một client bình thường sẽ gửi. Đổi User-Agent để trông như nhiều khách truy cập khác nhau là một hình thức né tránh, và nó có xu hướng làm tăng khả năng bị chặn, không phải giảm.

Nếu một trang phục vụ nội dung khác cho trình duyệt so với client HTTP đơn giản, hãy dùng trình duyệt thật cho những trang đó và giữ cùng giới hạn tốc độ. Đó là con đường hợp pháp, và nó giữ mẫu request ở mức hợp lý.

Dùng proxy cẩn thận

Proxy thay đổi địa chỉ mà request xuất phát từ đó. Nó là công cụ cho những nhu cầu hợp pháp, như kiểm thử từ một khu vực cụ thể trên một trang bạn được phép kiểm thử, hoặc phân tán tải qua hạ tầng của chính bạn. Nó không phải cách để thoát khỏi một giới hạn mà trang đã đặt lên bạn. Định dạng proxy là định dạng mà các loại task proxy dùng:

proxies = {
    "http": "http://user:pass@proxy.example.net:8000",
    "https": "http://user:pass@proxy.example.net:8000",
}
resp = session.get("https://example.com/products", proxies=proxies, timeout=30)

Việc chọn loại proxy là một câu hỏi riêng, được bao quát trong proxy dân cư và proxy datacenter và proxy xoay vòng cho scraping.

Khi lần chặn trở thành captcha

Một số trang trả lời một vấn đề về tốc độ bằng captcha thay vì chặn cứng. Thứ tự hợp lý là: dừng lại, chậm lại, kiểm tra lại luật lệ, và chỉ sau đó mới cân nhắc giải. Với widget PopularCaptcha, API đang hoạt động của Scarnox trả về một token mà bạn gửi trong trường h-captcha-response. Với các loại captcha khác, hãy kiểm tra danh sách hiện có, vì hỗ trợ cho reCAPTCHA, Cloudflare Turnstile, GeeTest, AWS WAF và Arkose đang được chuẩn bị. Hướng dẫn chi tiết là scraping captcha trên web.

Nếu bạn bắt đầu thấy chặn trên một trang, nguyên nhân gần như luôn là mẫu request. Tránh captcha khi scraping bao quát các thói quen giúp ngăn chặn điều đó.

Scrape có trách nhiệm

Bước tiếp theo