Scrapy ยังคงเป็นเครื่องมือหลักสำหรับการ crawl ในระดับ production ในปี 2026 — และยังเป็นเฟรมเวิร์กที่ทำให้คนสับสนกับการตั้งค่า proxy มากที่สุด เพราะมีถึงสี่จุดที่คุณเสียบ proxy เข้าไปได้ และสามในนั้นไม่เหมาะกับโปรเจกต์ส่วนใหญ่ คู่มือนี้จะให้จุดที่ถูกต้องกับคุณ: middleware แบบกำหนดเองขนาดเล็กที่มีการกำหนดเส้นทางต่อ request, sticky sessions, การตรวจจับการแบน และพฤติกรรมการ retry ที่สมเหตุสมผล
หากคุณใช้ requests/httpx/aiohttp ธรรมดาแทน ดูที่ วิธีหมุนเวียน Proxy ใน Python บทความนี้เจาะจงเฉพาะ Scrapy
สำหรับ gateway แบบ residential ที่หมุนเวียน การตั้งค่าขั้นต่ำที่ใช้งานได้คือหนึ่งบรรทัดต่อ request — ไม่ต้องใช้ middleware:
def start_requests(self):
for url in self.urls:
yield scrapy.Request(
url,
meta={"proxy": "http://USERNAME:[email protected]:913"},
)
HttpProxyMiddleware ที่มีมาในตัวของ Scrapy จะอ่าน request.meta["proxy"] และจัดการการยืนยันตัวตนจาก URL ให้ gateway จะหมุนเวียน exit IP ให้คุณเอง หากนี่คือทั้งหมดที่คุณต้องการ หยุดตรงนี้ได้เลย ส่วนที่เหลือของคู่มือนี้มีไว้สำหรับตอนที่คุณต้องการ การควบคุม: sticky sessions, การกำหนดเส้นทางตามประเทศ, การหมุนเวียนที่รับรู้การแบน และการปรับจูน concurrency
วางโค้ดนี้ลงใน middlewares.py มันจะกำหนด sticky session ต่อโดเมน หมุนเวียนเมื่อโดนแบน และติดแท็กทุก request เพื่อให้คุณดีบักได้ว่า session ไหนดึงอะไรมา:
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()
และ downloader middleware คู่หูที่ตรวจจับการแบนและ retry บน session ใหม่:
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)
เชื่อมต่อทั้งสองตัวเข้าด้วยกันใน settings.py:
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.JibaoProxyMiddleware": 350,
"scrapy.downloadermiddlewares.retry.RetryMiddleware": None, # replace stock retry
"myproject.middlewares.BanAwareRetryMiddleware": 550,
}
RETRY_TIMES = 2
ลำดับความสำคัญมีผล: proxy middleware ต้องทำงาน ก่อน HttpProxyMiddleware ของ Scrapy (750) ดังนั้นค่าใดก็ตามที่ต่ำกว่า 750 ใช้ได้ ค่า 350 ทำให้มันทำงานเร็วและคาดเดาได้
| ประเภทการ Crawl | โหมด | การนำไปใช้ |
|---|---|---|
| การเก็บเกี่ยวหน้าเว็บแบบไร้สถานะ | หมุนเวียน | username เปล่า gateway หมุนเวียนต่อ request |
| Login + crawl หลังการยืนยันตัวตน | Sticky ต่อบัญชี | -session-{account_id}, ห้ามหมุนเวียนกลางการ login |
| รายการที่มีการแบ่งหน้าจำนวนมาก | Sticky ต่อโดเมน หมุนเวียนเมื่อโดนแบน | middleware ด้านบน |
| ราคาเฉพาะตามภูมิภาค | หมุนเวียน + ปักหมุดประเทศ | พารามิเตอร์รูปแบบ USERNAME-country-de |
การวิเคราะห์เชิงลึกเกี่ยวกับการแลกเปลี่ยนนี้: Sticky vs Rotating Proxy Sessions
ค่าเริ่มต้นของ Scrapy ถูกปรับจูนมาสำหรับการ crawl แบบสุภาพด้วย IP เดียว เมื่ออยู่หลัง pool ที่หมุนเวียน คุณสามารถเร่งได้แรงกว่ามาก — แต่ขีดจำกัดต่อโดเมนยังคงสำคัญ เพราะ เป้าหมาย มองเห็นพฤติกรรมโดยรวม:
# 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
เพิ่มค่า CONCURRENT_REQUESTS_PER_DOMAIN เฉพาะหลังจากเฝ้าดูอัตรา 403 ที่ระดับปัจจุบันไปสักสองสามพัน request การกระโดดจาก 8 → 32 เพราะ "ยังไง proxy ก็หมุนเวียน" คือวิธีที่คนเผาผลาญ GB ไปกับการ retry
IMAGES_STORE ให้ดึงผ่าน datacenter proxy หรือการเชื่อมต่อโดยตรงหาก CDN ไม่สนใจ — สื่อคือ GB ส่วนใหญ่ของคุณและแทบไม่มีการป้องกันHTTPCACHE_ENABLED = True ขณะที่คุณปรับปรุง parser จะเล่นซ้ำ response จากดิสก์แทนการดึงผ่าน pool ใหม่ การตั้งค่านี้เพียงอย่างเดียวมักลดบิลแบนด์วิดท์ของโปรเจกต์ลงครึ่งหนึ่งCOMPRESSION_ENABLED = True (ค่าเริ่มต้น) ไว้ — HTML ที่ถูก gzip เล็กกว่า 5–10 เท่าบนสาย407 Proxy Authentication Requiredcredentials ไปไม่ถึง proxy ใส่มันลงใน URL (http://user:pass@host:port) ใน meta["proxy"] — Scrapy จะ parse และตั้งค่า Proxy-Authorization ให้คุณ การตั้งค่า header เองด้วย และ ใช้ credentials ใน URL ด้วยทำให้เกิดความผิดเพี้ยนจากการ auth ซ้ำ เลือกอย่างใดอย่างหนึ่ง
TunnelError: Could not open CONNECT tunnelเกือบทุกครั้งเป็น host/port ที่พิมพ์ผิด หรือเป้าหมาย HTTPS ผ่าน endpoint ที่ไม่อนุญาต CONNECT บนพอร์ตนั้น ตรวจสอบด้วย curl -x นอก Scrapy ก่อน
sticky session ของคุณอยู่นานเกินที่จะได้รับการต้อนรับ หรืออัตราต่อโดเมนของคุณร้อนเกินไป ban-aware middleware ด้านบนจัดการกรณีแรก ลดค่า CONCURRENT_REQUESTS_PER_DOMAIN สำหรับกรณีที่สอง หากเป็นเป้าหมายที่ตรวจสอบ JA4 ตัว TLS stack ของ Scrapy เองอาจเป็นตัวบ่งบอก — ดู JA3/JA4 อธิบาย ว่าทำไมไม่มี proxy ใดแก้ปัญหานั้นได้
meta["proxy"] กับ URL ของ gateway คือการตั้งค่าทั้งหมดsession แบบหมุนเวียนและ sticky residential บน gateway เดียว 500MB ทราฟฟิกฟรี ไว้ใช้ crawl
เริ่มทดลองใช้ฟรีผู้ใช้ใหม่รับ 500MB เมื่อสมัครสมาชิก พร้อมโบนัสในการเติมเงินครั้งแรก ข้อเสนอมีระยะเวลาจำกัด