วิธีตั้งค่า Proxy แบบกำหนดเองใน Scrapy (พร้อมใช้งานระดับโปรดักชัน)

อัปเดตล่าสุดเมื่อ August 11, 2026
Hand-drawn Scrapy crawler routing through a proxy pool, retrying a 503 route and completing through a 200 route
สรุปด้วย AI
  • สร้าง custom Scrapy proxy middleware ที่กำหนดเส้นทางที่อนุญาต, เพิ่มการยืนยันตัวตนอย่างปลอดภัย, บันทึกสาเหตุความล้มเหลว, และคง metadata ของ request ไว้ข้ามการ retry
  • ทำความเข้าใจลำดับการทำงานของ downloader middleware โดยเฉพาะวิธีที่ proxy logic แบบกำหนดเองทำงานร่วมกับ HttpProxyMiddleware, RetryMiddleware, redirect และ exception handling
  • เพิ่ม rotation ด้วยจำนวนครั้งที่จำกัด, cooldown, สถานะสุขภาพของ proxy, และ policy ที่รับรู้เป้าหมาย แทนการสุ่ม proxy ใหม่ทุก request
  • แยกความแตกต่างระหว่าง proxy authentication failure, connection error, DNS issue, 403 จากเป้าหมาย, และ rate limit เพื่อให้แต่ละกรณีตอบสนองได้ถูกต้อง
  • ใช้มาตรการป้องกันระดับโปรดักชันสำหรับการเก็บ secret, concurrency, observability, session consistency, และพฤติกรรม fail-closed เมื่อไม่มี proxy ที่ได้รับอนุญาตเหลืออยู่

สไปเดอร์ Scrapy ของคุณอาจดูเหมือนทำงานได้ดีในช่วงทดสอบ 100 หน้าแรก แต่พอไต่ไปถึง 10,000 หน้าแล้วดันล้มเอาเฉยๆ นั่นมักไม่ใช่บั๊กของการสแครปจริงๆ — แต่มันคือปัญหาเครือข่ายที่แต่งตัวมาเป็นบั๊กต่างหาก วิธีแก้ที่คนมักหยิบมาใช้ก็คือ Proxy และการใส่มันลงใน request.meta ใช้เวลาแค่ราวสามสิบวินาที

ปัญหาคือ วิธีแก้แบบสามสิบวินาทีมักเป็นจุดที่บทความพื้นฐานส่วนใหญ่หยุดไว้ พวกเขาโชว์แค่ meta={"proxy": "http://IP:PORT"} บางทีก็แถม middleware class มาให้ดูนิดหน่อย แล้วก็จบ แต่แทบไม่พูดถึงกรณีที่ Proxy ล่มระหว่าง crawl, การเผลอปล่อย credentials หลุดไปอยู่ใน Git history, หรือเหตุผลที่การ retry อาจย้อนกลับไปใช้ Proxy ตัวเดิมที่พังแล้วอีกครั้ง ประเด็นระดับโปรดักชันพวกนี้แหละคือสิ่งที่คู่มือนี้จะครอบคลุม: การตั้งค่าแบบ fail-closed, การจัดการ secret อย่างปลอดภัย, การเลือก Proxy อย่างมีเหตุผลตอน retry, ข้อจำกัดของโปรโตคอล, และการชั่งน้ำหนักต้นทุนที่วัดผลได้

Proxy Middleware ใน Scrapy คืออะไร?

Proxy middleware คือโค้ดชิ้นหนึ่งที่อยู่ใน downloader pipeline ของ Scrapy และทำหน้าที่ตัดสินว่า request แต่ละตัวควรถูกมองว่ามาจาก IP ไหน ก่อนจะถูกส่งออกไปจริง Scrapy มีตัว built-in อยู่แล้วชื่อ HttpProxyMiddleware ซึ่งจะอ่านคีย์ proxy จาก request.meta, แนบข้อมูลยืนยันตัวตนของ Proxy ให้ถ้าจำเป็น, แล้วส่ง request ต่อให้ download handler ที่เป็นคนเปิดการเชื่อมต่อจริง Custom proxy middleware จึงไม่ได้มาแทนขั้นตอน transport นั้น — แต่มันคือ ตัวเลือก ที่ตัดสินใจว่า Proxy ตัวไหนจะถูกใส่เข้าไป ก่อนที่กลไก built-in จะทำงาน ความต่างตรงนี้สำคัญกว่าที่ฟังดู และมันคือสาเหตุหลักของรายงานบั๊กประเภท “custom middleware ของฉันไม่ทำงาน” เสียเป็นส่วนใหญ่

ทำไม Proxy ถึงสำคัญกับโปรเจกต์ Scrapy ระดับโปรดักชัน

Proxy จะเปลี่ยนเส้นทางเครือข่ายและ IP ต้นทางที่มองเห็นได้ ซึ่งช่วยได้ทั้งในงานทดสอบตามภูมิภาค, การกระจายทราฟฟิกที่ได้รับอนุญาต, และการแยกความผิดพลาดของเครือข่ายออกจากกัน แต่มันไม่ได้ให้สิทธิ์พิเศษ, ไม่ได้ช่วยเลี่ยง rate limit อย่างถูกต้อง, และไม่รับประกันการเข้าถึง ทุกการ crawl จะต้องใช้ Proxy หรือไม่ ขึ้นอยู่กับเป้าหมาย, เงื่อนไขการใช้งาน, อัตราการส่ง request, และระดับความเสถียรที่งานนั้นต้องการ

Proxy แต่ละแบบก็ไม่ได้มีคุณภาพเท่ากัน และถ้าเลือกผิด ก็อาจกลายเป็น incident ในโปรดักชันได้ไม่แพ้กัน:

ประเภท Proxyความเสถียรความเร็วความเสี่ยงถูกตรวจจับต้นทุนโดยทั่วไป
Free public proxiesผันผวนมากผันผวนมักสูงไม่เสียค่าบริการ แต่มีความเสี่ยงด้านความปลอดภัยและการปฏิบัติการสูง
Datacenter proxiesขึ้นอยู่กับผู้ให้บริการและเป้าหมายมักเร็วขึ้นอยู่กับเป้าหมายมักคิดราคาเป็น GB หรือเป็น IP
Residential proxiesขึ้นอยู่กับผู้ให้บริการและเป้าหมายผันผวนขึ้นอยู่กับเป้าหมายมักคิดราคาเป็น GB
ISP proxiesขึ้นอยู่กับผู้ให้บริการและเป้าหมายผันผวนขึ้นอยู่กับเป้าหมายแตกต่างตามผู้ให้บริการ

ควรพูดถึง free proxy แยกต่างหาก เพราะความเสี่ยงที่วัดได้มันสูงจริงๆ งานวิจัย Free Proxies Unmasked ระยะเวลา 30 เดือน ติดตามที่อยู่มากกว่า 640,000 รายการจากผู้ให้บริการ 11 ราย พบว่า 34.5% เปิดใช้งานอย่างน้อยหนึ่งครั้ง และมี 16,923 รายการที่มีการแก้ไขเนื้อหา ข้อมูลชุดนี้ช่วยยืนยันคำเตือนด้านความปลอดภัยสำหรับ public list ได้ชัดมาก แต่ไม่ได้แปลว่าอัตราความล้มเหลวแบบเดียวกันจะใช้ได้กับทุก list, ทุก pool แบบเสียเงิน, ทุกเป้าหมาย, หรือทุก workload ในปัจจุบัน

Proxy ก็ไม่ได้ทำให้ bot detection สมัยใหม่พังไปเองโดยอัตโนมัติ บริการอย่าง Cloudflare ประเมิน request จากสัญญาณนับสิบ — ตั้งแต่ TLS fingerprint, ความสอดคล้องของ headers, การรัน JavaScript, ไปจนถึงพฤติกรรมการใช้งาน — โดย IP เป็นเพียงหนึ่งในหลายปัจจัยเท่านั้น ต่อให้ใช้ residential proxy ที่ดูสะอาด แต่ request มี headers ขัดกันและไม่มี cookie jar ก็ยังถูกตั้งธงได้อยู่ดี อย่าคาดหวังเกินจริงจนสร้างสถาปัตยกรรมทั้งระบบบนแนวคิดว่า “แค่หมุน IP ก็พอ”

ก่อนเริ่มต้น

  • ระดับความยาก: ปานกลาง
  • เวลาที่ใช้: ประมาณ 30–45 นาทีสำหรับการตั้งค่าแบบโปรดักชันเต็มรูปแบบ, ประมาณ 5 นาทีสำหรับการทดสอบแบบเร็ว
  • สิ่งที่ต้องมี: Python 3.9+, ติดตั้ง Scrapy แล้ว (คู่มือนี้ทดสอบกับ Scrapy 2.17.0 ซึ่งเปิดตัวในเดือนกรกฎาคม 2026), สไปเดอร์ที่ใช้งานได้จาก scrapy startproject, และอย่างน้อยหนึ่ง Proxy endpoint (ทดลองใช้ฟรีจากผู้ให้บริการ datacenter proxy รายใดก็ได้ก็พอสำหรับการทดสอบ)

ขั้นตอนที่ 1: ทดสอบ Proxy ด้วยพารามิเตอร์ Request Meta

วิธีที่เร็วที่สุดในการยืนยันว่า Proxy ใช้ได้จริง คือข้ามสถาปัตยกรรม middleware ไปก่อน แล้วลองใช้มันตรงๆ เลย

HttpProxyMiddleware ที่มีอยู่ใน Scrapy จะอ่านคีย์ proxy จาก request.meta โดยตรง แล้วส่ง request ผ่านมันไป ไม่ต้องแก้ settings, ไม่ต้องสร้าง middleware class — ใช้แค่ keyword argument เดียวเท่านั้น

import scrapy

class ProxyTestSpider(scrapy.Spider):
    name = "proxy_test"
    start_urls = ["https://httpbin.org/ip"]

    def start_requests(self):
        for url in self.start_urls:
            yield scrapy.Request(
                url,
                meta={"proxy": "http://203.0.113.10:8080"},
                callback=self.parse,
            )

    def parse(self, response):
        self.logger.info(response.text)

รันด้วย scrapy runspider proxy_test.py ถ้า Proxy ใช้งานได้ httpbin.org/ip จะสะท้อน IP ของ Proxy กลับมาแทน IP ของคุณเอง — นั่นคือสัญญาณยืนยันของคุณ ถ้า response ค้างแล้วสุดท้ายขึ้น TCP connection timed out แปลว่า Proxy ตายหรือเข้าถึงไม่ได้ ซึ่งพูดตรงๆ ก็คือเกิดขึ้นบ่อยกว่าที่ผู้ขาย Proxy อยากยอมรับ

วิธีนี้เหมาะกับสไปเดอร์แบบทำครั้งเดียวหรือใช้ทดสอบเร็วๆ แต่จะเริ่มลำบากทันทีที่คุณมีสไปเดอร์มากกว่าหนึ่งตัว เพราะจากนั้นคุณจะต้อง hardcode สตริง Proxy เดิมซ้ำไปมาหลายไฟล์

ขั้นตอนที่ 2: สร้าง Custom Proxy Middleware

ถ้าไม่ใช่สไปเดอร์ตัวเดียว คุณควรรวม logic ของ Proxy ไว้ที่จุดเดียว สร้างคลาส ProxyMiddleware ใน middlewares.py ของโปรเจกต์:

from scrapy.exceptions import NotConfigured

class ProxyMiddleware:
    def __init__(self, proxy_url):
        self.proxy_url = proxy_url

    @classmethod
    def from_crawler(cls, crawler):
        proxy_url = crawler.settings.get("PROXY_URL")
        if not proxy_url:
            raise NotConfigured("PROXY_URL is required; refusing silent direct fallback")
        return cls(proxy_url=proxy_url)

    def process_request(self, request, spider):
        if "proxy" not in request.meta:
            request.meta["proxy"] = self.proxy_url

แล้วลงทะเบียนใน settings.py:

import os

PROXY_URL = os.environ.get("PROXY_URL")

DOWNLOADER_MIDDLEWARES = {
    "myproject.middlewares.ProxyMiddleware": 350,
}

สังเกตว่าใช้ from_crawler classmethod แทน __init__ ธรรมดา นี่คือรูปแบบที่ Scrapy แนะนำจริงสำหรับการอ่าน settings และเป็น hook เดียวกับที่เราจะกลับมาใช้อีกครั้งในขั้นตอนที่ 4 ตอนพูดถึง secret ข้อดีของแนวทางนี้ตรงไปตรงมามาก: เปลี่ยนค่า setting ตัวเดียว สไปเดอร์ทุกตัวในโปรเจกต์จะใช้ Proxy ใหม่ทันที ไม่ต้องไล่ grep หา IP ที่ตายไปตามไฟล์ต่างๆ

ขั้นตอนที่ 3: เข้าใจลำดับการทำงานของ Middleware (ไม่งั้น Proxy ของคุณอาจเหมือนไม่ทำอะไรเลย)

นี่คือส่วนที่บทความส่วนใหญ่มักข้ามด้วยประโยคประมาณว่า “ตั้ง priority เป็น 350 ก็พอ เชื่อฉัน” Downloader middleware ใน Scrapy ทำงานตามลำดับที่แน่นอนและคาดเดาได้ ถ้าคุณไม่เข้าใจ มันจะกลายเป็นบั๊กที่ดูเหมือนไม่เกี่ยวกับ Proxy เลย

hook ฝั่ง request (process_request) จะทำงานตามลำดับ priority แบบ น้อยไปมาก — เลขต่ำก่อน ส่วน hook ฝั่ง response (process_response, process_exception) จะทำงานแบบ มากไปน้อย — เลขสูงก่อน แล้วค่อยย้อนกลับลงมาตาม chain

เส้นทาง Request (ascending):
  Spider → [100] RobotsTxt → [300] HttpAuth → [350] YOUR PROXY MIDDLEWARE
         → [500] UserAgent → [550] Retry → [700] Cookies → [750] HttpProxy
         → Downloader

เส้นทาง Response (descending):
  Downloader → [750] HttpProxy → [700] Cookies → [550] Retry
             → [500] UserAgent → [350] YOUR PROXY MIDDLEWARE → [300] HttpAuth
             → [100] RobotsTxt → Spider

นี่คือตาราง priority ค่าเริ่มต้นปัจจุบันของ built-in middleware ใน Scrapy ที่ตรวจสอบกับ 2.17.0 แล้ว:

MiddlewarePriority เริ่มต้น
RobotsTxtMiddleware100
HttpAuthMiddleware300
DownloadTimeoutMiddleware350
DefaultHeadersMiddleware400
UserAgentMiddleware500
RetryMiddleware550
RedirectMiddleware600
CookiesMiddleware700
HttpProxyMiddleware750
DownloaderStats850
HttpCacheMiddleware900

เมื่อ RetryMiddleware สั่ง retry มันจะคัดลอก request ที่ล้มเหลว—including metadata—แล้ว request ใหม่นั้นจะกลับเข้าสู่ downloader middleware chain อีกครั้ง ความสัมพันธ์ของ selector กับ priority 550 จึง ไม่ได้ การันตีการ rotate การหมุน Proxy จะเกิดขึ้นก็ต่อเมื่อโค้ดของ selector ตรวจจับได้ว่าเป็น retry แล้วจงใจเขียนทับ meta["proxy"] ที่ถูกคัดลอกมา Priority 350 เป็นตำแหน่งที่สะดวกสำหรับ selector เพราะมันทำงานก่อน transport step ของ built-in ที่ 750 แต่ตัวมันเองไม่ใช่กลไก rotation

Scrapy request and response paths through priorities 350, 550, and 750, with a 503 retry re-entering the middleware chain

ข้อผิดพลาดที่พบบ่อยเกี่ยวกับลำดับ Middleware

  • ตั้ง middleware ของคุณที่ priority เดียวกับ HttpProxyMiddleware (750): จะเกิด race condition ที่ทำให้ dict-ordering ของ Scrapy (ไม่ใช่ logic ของคุณ) เป็นตัวตัดสินว่า middleware ไหนได้ประมวลผล request ก่อน อาการคือพฤติกรรม Proxy แปลกๆ แบบเกิดบ้างไม่เกิดบ้าง
  • ใช้ setdefault() หรือ if "proxy" not in request.meta ใน selector แบบหมุน Proxy: retry ที่ถูกคัดลอกจะเก็บ Proxy ตัวเดิมไว้ อาการคือ retry ทุกครั้งวิ่งเส้นทางเดิมที่ล้มเหลวซ้ำๆ วิธีแก้คือจับให้ได้ว่าเป็น retry (เช่น retry_times > 0) แล้วเขียนทับค่า proxy ที่ selector เป็นคนดูแลโดยตรง
  • ปิด HttpProxyMiddleware ไปเลย: บางบทความแนะนำให้ตั้ง "scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware": None เพราะบอกว่า “custom middleware จัดการให้แล้ว” แต่จริงๆ ไม่ใช่ — custom middleware ของคุณเป็นแค่ selector ไม่ใช่ transport layer การปิด built-in ตัวนี้หมายความว่า header สำหรับ proxy authentication จะไม่ถูกแนบไป และ request ของคุณจะถูกส่งออกไปแบบไม่ยืนยันตัวตน (หรือไม่ถูกส่งออกไปเลย)

ขั้นตอนที่ 4: เลิก hardcode credentials ของ Proxy

บทความสอน Scrapy เกี่ยวกับ Proxy ที่ขึ้นอันดับสูงๆ แทบทุกชิ้นที่ผมเจอ จะเขียน http://username:password@proxy.example.com:8080 ลงในไฟล์ Python ตรงๆ นั่นคือ credentials ที่จะติดอยู่ใน Git history ตลอดไป โผล่ในทุก clone ทุก fork และทุก log dump ถ้ามีใครเผลอใช้ print() แบบไม่ระวัง

จริงๆ แล้วการทำแบบนี้มีอยู่สามระดับ:

วิธีการความปลอดภัยความยืดหยุ่นเหมาะกับ
hardcode ใน spider/settings.pyแย่ — secret อยู่ใน repoต่ำทดสอบ local แบบรวดเร็วเท่านั้น
environment variable http_proxy (รองรับโดย Scrapy โดยตรง)ดีกว่า — ไม่อยู่ในโค้ดต่ำ (Proxy เดียว)CI/CD pipelines, Docker
.env + python-dotenv + from_crawlerดีที่สุด — ไม่อยู่ในโค้ด, แยกตาม environment ได้สูง (หลาย Proxy, rotation)ตัวดึงข้อมูลระดับโปรดักชัน

ตัวเลือกที่สามคุ้มค่าที่จะทำให้ถูกต้องตั้งแต่แรก ติดตั้ง python-dotenv, สร้างไฟล์ .env (แล้วรีบเพิ่มเข้า .gitignore ทันที — พูดจริง ทำเดี๋ยวนี้เลย):

PROXY_USER=myuser
PROXY_PASSWORD=my$ecret!Pass
PROXY_HOST=proxy.example.com
PROXY_PORT=8080

โหลดค่าขึ้นมาตั้งแต่ต้นของ settings.py:

from dotenv import load_dotenv
import os

load_dotenv()

PROXY_USER = os.getenv("PROXY_USER")
PROXY_PASSWORD = os.getenv("PROXY_PASSWORD")
PROXY_HOST = os.getenv("PROXY_HOST")
PROXY_PORT = os.getenv("PROXY_PORT")

จากนั้นอ่านค่าอย่างปลอดภัยภายใน middleware โดยใช้ from_crawler — นี่คือรูปแบบที่ช่วยแก้ความสับสนที่ผมเห็นในฟอรัมบ่อยๆ ว่า “แล้วจะตั้งค่านี้ก่อน scrapy crawl ยังไง ถ้า credentials ต้องเปลี่ยนทุกครั้งที่รัน?”:

import os
from urllib.parse import quote

class SecureProxyMiddleware:
    def __init__(self, user, password, host, port):
        self.user = quote(user, safe="")
        self.password = quote(password, safe="")
        self.host = host
        self.port = port

    @classmethod
    def from_crawler(cls, crawler):
        settings = crawler.settings
        return cls(
            user=settings.get("PROXY_USER"),
            password=settings.get("PROXY_PASSWORD"),
            host=settings.get("PROXY_HOST"),
            port=settings.get("PROXY_PORT"),
        )

    def process_request(self, request, spider):
        proxy_url = f"http://{self.user}:{self.password}@{self.host}:{self.port}"
        request.meta["proxy"] = proxy_url

สังเกตการเรียก urllib.parse.quote() ครอบ credentials ไว้ หากรหัสผ่านของคุณมี @, :, หรือ / อยู่ด้วย (และ password generator ก็มักสุ่มสัญลักษณ์พวกนี้มาให้) URL parsing จะพังทันทีถ้าไม่เข้ารหัสก่อนแบบ percent-encoding นี่คือบรรทัดเดียวที่ช่วยประหยัดเวลาหงุดหงิดเป็นชั่วโมงกับ error แบบ “invalid proxy URL” ที่จริงๆ แล้วไม่ได้เกิดจาก Proxy ผิดอะไรเลย

เก็บ credentials ให้พ้นจาก logs และทดสอบ percent-encoding ด้วยค่าตัวอย่างที่ดูสมจริงแต่เป็นข้อมูลปลอมไว้ก่อน HTTP tunneling, SOCKS5, และ credentials ที่ไม่ใช่อักษรละตินมีขอบเขตการทำงานที่ต่างกันตาม handler; อย่าคิดว่ารูปแบบ authentication เดียวจะใช้ได้เหมือนกันทุกกรณีโดยไม่ทำ integration test แบบล็อกเวอร์ชัน

Secure flow from a locked environment file through percent-encoded proxy settings into Scrapy request metadata

ขั้นตอนที่ 5: เพิ่ม Proxy Rotation

Proxy เดียว — ต่อให้ดีแค่ไหน — ถ้าวิ่ง 5,000 requests ไปยังเว็บเดียวกัน สุดท้ายก็มีโอกาสโดนจับได้ คุณต้องมี pool

ตัวเลือก A — สร้างเอง วิธีนี้ง่ายจริงๆ:

import random

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

    @classmethod
    def from_crawler(cls, crawler):
        return cls(proxy_pool=crawler.settings.getlist("PROXY_POOL"))

    def process_request(self, request, spider):
        request.meta["proxy"] = random.choice(self.proxy_pool)

วิธีนี้ใช้ได้กับงานพื้นฐาน แต่ไม่มีการรับรู้เลยว่า Proxy ตัวไหนยังมีชีวิตอยู่จริง คุณกำลังโยนลูกเต๋าใหม่ทุก request

ตัวเลือก B — ใช้ scrapy-rotating-proxies แพ็กเกจจากภายนอกตัวนี้เพิ่มการตรวจจับการถูกบล็อกและ backoff อัตโนมัติมาให้ตั้งแต่ต้น:

pip install scrapy-rotating-proxies

ขอพูดตรงๆ ตรงนี้: release ล่าสุดบน PyPI คือเวอร์ชัน 0.6.2 ที่ลงวันที่ปี 2019 และโปรเจกต์ถูกแท็กว่า Alpha มันไม่ได้หมายความว่ามันพังแน่ๆ บน Scrapy รุ่นใหม่ แต่คำว่า “ไม่ได้ดูแลมาตั้งแต่ 2019” ไม่ได้แปลว่า “พร้อมรับทราฟฟิกโปรดักชันปี 2026” ให้ pin เวอร์ชัน, ทดสอบกับเว็บเป้าหมายจริงของคุณ, และอย่าคิดว่ามันรองรับ authenticated proxy endpoint ได้สมบูรณ์ — ส่วนใหญ่แล้วมันไม่รองรับ

ขั้นตอนที่ 6: สร้าง Proxy Middleware ที่ทนต่อความผิดพลาดได้ (ตรวจจับ Proxy ตาย)

นี่คือส่วนที่บทความคู่แข่งแทบทุกชิ้นข้ามไปหมด และมันคือความต่างระหว่างเดโม กับระบบที่รัน crawl ยาว 6 ชั่วโมงแบบไม่มีคนเฝ้าแล้วยังรอด

สถานการณ์สิ่งที่บทความส่วนใหญ่มักแสดงสิ่งที่ middleware นี้เพิ่มให้
Proxy ตอบ 407ไม่ได้กล่าวถึงมองว่าเป็นความล้มเหลวของการยืนยันตัวตนกับ Proxy
เป้าหมายตอบ 403/429มักจับรวมกันหมดแยกสัญญาณด้าน policy/rate ออกจากสุขภาพของ Proxy
Proxy timeoutไม่ได้กล่าวถึงตั้ง threshold timeout ได้, health score ลดลง
Proxy ตายหมดไม่ได้กล่าวถึงfallback อย่างเหมาะสม หรือหยุด crawl พร้อม log เตือน
Proxy กระพริบไปมา (ติดๆ ดับๆ)ไม่ได้กล่าวถึงมีช่วงพักก่อนนำกลับเข้ากลุ่ม
import time
import random
from scrapy.exceptions import IgnoreRequest

class FaultTolerantProxyMiddleware:
    MAX_FAILURES = 3
    COOLDOWN_SECONDS = 300

    def __init__(self, proxy_pool):
        self.pool = {p: {"failures": 0, "banned_until": 0} for p in proxy_pool}

    @classmethod
    def from_crawler(cls, crawler):
        return cls(proxy_pool=crawler.settings.getlist("PROXY_POOL"))

    def _healthy_proxies(self):
        now = time.time()
        return [p for p, s in self.pool.items() if s["banned_until"] < now]

    def process_request(self, request, spider):
        healthy = self._healthy_proxies()
        if not healthy:
            spider.logger.warning("Proxy ทั้งหมดมีปัญหา — กำลังพัก crawl")
            raise IgnoreRequest("No healthy proxies available")
        request.meta["proxy"] = random.choice(healthy)

    def process_response(self, request, response, spider):
        proxy = request.meta.get("proxy")
        if proxy and response.status == 407:
            self._mark_failure(proxy)
        return response

    def process_exception(self, request, exception, spider):
        proxy = request.meta.get("proxy")
        if proxy:
            self._mark_failure(proxy)

    def _mark_failure(self, proxy):
        state = self.pool[proxy]
        state["failures"] += 1
        if state["failures"] >= self.MAX_FAILURES:
            state["banned_until"] = time.time() + self.COOLDOWN_SECONDS
            state["failures"] = 0

มีข้อสังเกตจากประสบการณ์จริงตอนสร้างแบบนี้: อย่ามองทุก 403 ว่าเป็นหลักฐานว่า Proxy ตาย — RFC 9110 ให้ความหมาย 403 ว่า “server เข้าใจแต่ปฏิเสธ” ซึ่งอาจหมายถึง headers หรือ session ของคุณน่าสงสัยก็ได้ ไม่ใช่เพราะ Proxy อย่างเดียว ส่วน 407 นั้นหมายความว่า Proxy เองกำลังปฏิเสธการยืนยันตัวตนของคุณ — สัญญาณนี้แรงและเฉพาะเจาะจงกว่ามาก การเอาสัญญาณพวกนี้ไปรวมกองเดียวกันมักทำให้คนเผา Proxy ที่ยังดีอยู่ทิ้งไปโดยไม่มีเหตุผล

ในโปรดักชันควรตั้งค่า retry ของ Scrapy ให้ชัดเจน เพื่อให้ reviewer เห็น policy ได้เลย:

RETRY_HTTP_CODES = [500, 502, 503, 504, 522, 524, 408, 429]
RETRY_TIMES = 2

นี่คือค่า default ของ Scrapy 2.17 อย่าใส่ 403 หรือ 407 แบบหว่านรวมเข้าไป: 403 คือการปฏิเสธจากเป้าหมายที่อาจมีหลายสาเหตุ ส่วน 407 คือปัญหา proxy authentication ที่ retry request ทั่วไปแก้ไม่ได้ ถ้ามีเหตุผลเฉพาะของสัญญาเป้าหมายที่ต้อง retry โค้ดสถานะอื่น ให้บันทึกเหตุผลนั้นให้ชัดและทดสอบแยกต่างหาก

Distinct handling for 407 authentication failure, 429 rate limiting, 503 retry, 200 success, and a closed gate when proxies are unavailable

ขั้นตอนที่ 7: รูปแบบ Proxy-on-Retry แบบวัดผลได้

บาง target ที่ได้รับอนุญาตอาจตอบเนื้อหาปกติได้โดยตรง และต้องใช้ Proxy แค่หลังจากเจอสถานะชั่วคราวที่ระบุไว้แล้ว วิธีนี้ช่วยลด byte ที่ต้องผ่าน Proxy ได้ แต่ไม่มีเปอร์เซ็นต์ประหยัดแบบใช้ได้กับทุกกรณี หรืออัตราความสำเร็จของ direct request ที่ยกไปใช้ข้ามงานได้แบบน่าเชื่อถือ คุณต้องวัด workload ของตัวเองก่อนค่อยใช้แนวทางนี้

ใช้ helper สาธารณะ get_retry_request() ของ Scrapy และจำกัดการ escalation ไว้เฉพาะสถานะที่ผูกกับ target นั้นและได้จำแนกไว้ชัดเจนแล้ว ตัวอย่างนี้ถือว่า 429 และ 503 เป็นสัญญาณแรงกดดันที่ retry ได้; ตั้งใจไม่รวม 403 และ 407

from scrapy.downloadermiddlewares.retry import get_retry_request

class CostAwareEscalationMiddleware:
    def process_response(self, request, response, spider):
        if response.status not in {429, 503}:
            return response

        retry = get_retry_request(
            request,
            spider=spider,
            reason=f"proxy_escalation_{response.status}",
            max_retry_times=2,
        )
        if retry is None:
            return response

        current_tier = request.meta.get("proxy_tier", "direct")
        if current_tier == "direct":
            retry.meta["proxy"] = spider.settings["DATACENTER_PROXY"]
            retry.meta["proxy_tier"] = "datacenter"
        elif current_tier == "datacenter":
            retry.meta["proxy"] = spider.settings["RESIDENTIAL_PROXY"]
            retry.meta["proxy_tier"] = "residential"
        else:
            return response
        return retry

ให้บันทึกอัตรา valid content ของ direct, อัตรา valid content ของ proxied, bytes ต่อหนึ่ง record ที่สำเร็จ, จำนวน retries ต่อความสำเร็จหนึ่งครั้ง, และ latency ที่เพิ่มขึ้น การออกแบบแบบ direct-first จะใช้ได้ก็ต่อเมื่อ direct access ได้รับอนุญาต และระบบต้อง fail closed ทันทีเมื่อจำเป็นต้องใช้ Proxy การประหยัดที่แท้จริงคือการลดปริมาณทราฟฟิกผ่าน Proxy ที่วัดได้ ไม่ใช่ตัวเลขเปอร์เซ็นต์ที่เดากันเอา

เมื่อการจัดการ Proxy ด้วยตัวเองไม่คุ้มค่า

ขอพูดตรงๆ ก่อนว่าไม่อยากทำให้ทั้งบทความดูไร้ประโยชน์: ทุกอย่างข้างบนเป็นวิศวกรรมที่จริงและมีประโยชน์ และสำหรับหลายโปรเจกต์ — การ crawl ปริมาณมาก, pipeline แบบกำหนดเอง, หรืออะไรที่ต้องควบคุม scheduling ของ request แบบเต็มรูปแบบ — นี่คือแนวทางที่ถูกต้อง

แต่ถ้าเป้าหมายจริงของคุณคือ “ดึงข้อมูลที่เป็นโครงสร้างจากหน้านี้” ไม่ใช่ “ดูแลโครงสร้างพื้นฐานของ Proxy” บางที extraction API อาจเป็นขอบเขตที่เหมาะกว่า เอกสาร POST /extract ล่าสุดของ Thunderbit รับ page URL และ JSON Schema แบบเลือกใส่ได้; ถ้าไม่ส่ง schema มา ระบบสามารถสร้าง schema จากเนื้อหาหน้าเว็บให้ได้ด้วย endpoint นี้ยังมีโหมด render แบบ none, basic, และ full พร้อมตัวควบคุม timeout และระยะรอหลังโหลด การดึงข้อมูลแบบใช้ prompt อย่างเดียวไม่ได้อยู่ใน request surface ที่รองรับอยู่ตอนนี้ ดังนั้นการทำ integration ระดับโปรดักชันควรยึดตามสัญญา schema ที่เอกสารระบุไว้ วิธีนี้ย้าย interface สำหรับ extraction ไปไว้หลัง request เดียว; แต่มันไม่ได้หมายความว่าจะรับประกันผลกับทุกเป้าหมายที่มี anti-bot หรือ CAPTCHA เสมอ

ปัจจัยDIY Scrapy + Proxyการดึงข้อมูลผ่าน API (เช่น Thunderbit)
ความยุ่งยากในการตั้งค่าสูง — ต้องมี middleware, rotation, retry logicต่ำ — เรียก API ครั้งเดียวพร้อม schema
พฤติกรรมเครือข่าย/renderingคุณเป็นคนกำหนด handler, proxy, headers, และ delaysควบคุมผ่านตัวเลือก API ที่ระบุไว้
การดูแลรักษาคุณต้องดูแล selector, สุขภาพของ pool, และการเปลี่ยนแปลงของ target เองคุณดูแลคุณภาพ schema, validation, และพฤติกรรม integration
รูปแบบต้นทุนค่า Proxy + compute + เวลา engineeringเอกสารปัจจุบันระบุ 20 units ต่อหนึ่งหน้า (ตรวจสอบเมื่อ 2026-08-10)
การควบคุมเต็มที่ — custom pipeline, middleware chainจำกัดตามความสามารถของ API
เหมาะที่สุดสำหรับการ crawl ซับซ้อน, ปริมาณสูง, logic เฉพาะทางการดึงข้อมูลเฉพาะจุด, การทำ prototype, enrichment

ถ้าคุณส่วนใหญ่ต้องการดึงหน้าที่มีข้อมูลเป็นโครงสร้างสำหรับ lead list, ข้อมูลสินค้า, หรือการวิจัย ให้ลอง pilot กับ Thunderbit Chrome Extension หรือ API บนตัวอย่างงานจริง แล้วเทียบจำนวนเรคคอร์ดที่ใช้ได้, latency, units, และเวลาที่ต้องใช้ดูแลรักษา ถ้าโปรเจกต์ของคุณต้องการกราฟของการ crawl แบบกำหนดเองและควบคุม pipeline เต็มรูปแบบ Scrapy ยังคงเป็นตัวเลือกที่แข็งแรงกว่า

อ่านเพิ่มเติม

เคล็ดลับและข้อผิดพลาดที่พบบ่อย

  • เคล็ดลับ: ทดสอบ Proxy กับ httpbin.org/ip ก่อนจะเอาไปยิงใส่ target จริง วิธีนี้เร็วที่สุดในการยืนยันว่า routing ทำงานถูกต้อง ก่อนที่คุณจะเพิ่มความซับซ้อนเข้าไปอีกชั้น
  • ข้อผิดพลาด: ตั้ง Proxy ไว้ใน request.headers แทน request.meta นี่เป็นบั๊กที่คนพลาดกันบ่อยแบบใกล้เคียงกับการพิมพ์ผิด HttpProxyMiddleware อ่านแค่ meta["proxy"] และการพยายามใส่ผ่าน header จะล้มเหลวแบบเงียบๆ โดยไม่มี error ที่ชัดเจน
  • ข้อผิดพลาด: คิดว่า HTTP และ HTTPS proxy ตั้งค่าเหมือนกันทั้งหมด URL ของ HTTP proxy ที่ส่งต่อไปยังปลายทาง HTTPS โดยทั่วไปใช้งานได้ผ่าน CONNECT tunneling ภายใต้ handler ที่รองรับ แต่การใช้สคีม https:// สำหรับตัว endpoint ของ proxy เองเป็นคนละเรื่อง และรองรับน้อยกว่า อย่าสับสนสองอย่างนี้เข้าด้วยกัน
  • เคล็ดลับ: ถ้าคุณต้องใช้ SOCKS5 ให้ตรวจสอบความสามารถของ download handler ในเวอร์ชัน Scrapy ของคุณก่อน Scrapy 2.17 มี Httpx handler แบบ experimental ที่เพิ่ม SOCKS5 support ผ่าน httpx[socks] แต่ยังถูกติดป้ายว่า experimental อยู่ — อย่าสร้าง dependency สำหรับโปรดักชันบนมันโดยไม่ทดสอบด้วยตัวเอง

วิธีอื่นที่เป็นไปได้

นอกจาก scrapy-rotating-proxies แล้ว บางทีมเลือกส่ง logic ของ Proxy ทั้งหมดผ่าน provider gateway — คือใช้ proxy URL เดียว แล้วปล่อยให้ vendor จัดการ rotation, session stickiness, และ geo-targeting อยู่เบื้องหลัง แนวทางนี้แลกความควบคุมบางส่วนกับการลดโค้ด middleware ลงมาก และควรลองประเมินราคาดูเทียบกับ pool ที่คุณดูแลเอง ก่อนจะลงมือสร้างทั้งหมดตั้งแต่ศูนย์

สรุป

การตั้ง Proxy ใน Scrapy ใช้แค่หนึ่งบรรทัด แต่การทำให้การตั้งค่านั้นรอดผ่าน crawl ระดับโปรดักชันจริง ต้องมี policy ความล้มเหลวที่ชัดเจน, credentials ที่ป้องกันไว้, boundary ของ handler ที่ผ่านการทดสอบ, และ selector code ที่ตั้งใจเขียนทับ metadata ของ Proxy ที่ถูกคัดลอกมาเมื่อ retry ถ้าจะจำแค่อย่างสองอย่าง ให้จำสองข้อนี้: fail closed เมื่อจำเป็นต้องใช้ Proxy และอย่า commit รหัสผ่านของ Proxy ลงในไฟล์ Python เด็ดขาด

คำถามที่พบบ่อย

จะตั้งค่า custom proxy ใน Scrapy พร้อมการยืนยันตัวตนได้อย่างไร?
ใช้รูปแบบ URL protocol://username:password@host:port แต่ถ้า username หรือ password มีอักขระพิเศษ ให้ percent-encode ด้วย urllib.parse.quote() ก่อน สำหรับโปรดักชัน ควรอ่าน credentials ผ่าน classmethod from_crawler ที่ดึงค่าจาก environment variables แทนการ hardcode

ควรใช้หมายเลข priority เท่าไหร่สำหรับ custom proxy middleware ใน Scrapy?
350 เป็นค่า priority ที่ใช้กันบ่อยสำหรับ selector เพราะมันทำงานก่อน HttpProxyMiddleware ที่ 750 แต่มันไม่ได้การันตีการ rotate การ retry จะได้ Proxy ใหม่ก็ต่อเมื่อ selector ตรวจจับ request ที่ถูกคัดลอกตอน retry ได้ แล้วเขียนทับค่า meta["proxy"] เดิม

จะจัดการ Proxy ที่ตายใน Scrapy แบบอัตโนมัติได้อย่างไร?
สร้าง middleware ที่นับความล้มเหลวของแต่ละ Proxy ใน process_response และ process_exception, นำ Proxy ออกจาก pool ที่ใช้งานอยู่เมื่อถึง threshold ของความล้มเหลว, แล้วค่อยนำกลับมาใช้ใหม่หลังช่วง cooldown แทนที่จะแบนถาวร

ใช้ Scrapy กับ SOCKS5 proxy ได้ไหม?
ใน Scrapy 2.17 HttpxDownloadHandler แบบ experimental รองรับ SOCKS5 เมื่อมี httpx[socks] ติดตั้งอยู่ ส่วน HTTP/1.1 handler เริ่มต้นไม่ได้รองรับ SOCKS proxy ให้ pin handler/version และทำ integration test ก่อนจะถือว่าใช้ในโปรดักชันได้

รูปแบบ proxy-on-retry ช่วยประหยัดต้นทุน proxy ได้มากแค่ไหน?
ไม่มีเปอร์เซ็นต์กลางที่ใช้ได้กับทุกกรณี ให้วัดสัดส่วน request ที่ได้รับอนุญาตและได้ผลลัพธ์ตรงๆ, bytes ที่วิ่งผ่านแต่ละ proxy tier, retries ต่อหนึ่งเรคคอร์ดที่สำเร็จ, และ latency ที่เพิ่มขึ้น การลดปริมาณทราฟฟิกผ่าน proxy ที่วัดได้คือเงินที่คุณประหยัดจริง; ถ้า direct-first access ไม่ได้รับอนุญาตหรือให้ผลไม่ถูกต้อง อย่าใช้รูปแบบนี้

Ke
Ke
CTO ที่ Thunderbit | นักวิทยาศาสตร์ข้อมูลอาวุโสและผู้เชี่ยวชาญด้านแมชชีนเลิร์นนิง ด้วยประสบการณ์เกือบสิบปีในด้านแมชชีนเลิร์นนิงและวิทยาศาสตร์ข้อมูล เคเฉินเป็นศิษย์เก่ามหาวิทยาลัยโคลัมเบีย และอดีตนักวิทยาศาสตร์ข้อมูลอาวุโสที่ Walmart Labs ด้วยความเชี่ยวชาญลึกซึ้งที่ได้รับการยอมรับจากเพื่อนร่วมสายงานใน Python, R, Java และสถิติ เขาจึงแบ่งปันมุมมองที่ผ่านการพิสูจน์มาแล้วในการพัฒนาอัลกอริทึม AI ที่ซับซ้อนจากแนวคิดไปสู่สถาปัตยกรรมระดับใช้งานจริง
Topics
Scrapy proxy middlewarePython web scrapingProxy rotation
สารบัญ
Thunderbit · เอเจนต์ข้อมูลเว็บด้วย AI

ดึงข้อมูลจากทุกหน้าได้ใน คลิกเดียว

ได้รับความไว้วางใจจากผู้ใช้กว่า 250,000+ คน
มีแพ็กเกจใช้ฟรี
จากหน้าเว็บสู่สเปรดชีต
อธิบายสิ่งที่คุณต้องการ — AI Agent ของ Thunderbit จะดึงข้อมูลให้และส่งออกไปยัง Excel, Google Sheets, Airtable หรือ Notion เริ่มใช้ได้ฟรี
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week