خانه/مقالات/اسکریپینگ با Selenium و مدیریت پراکسی (۲۰۲۶)
سلنیوم
پروکسی و چرخش IP
ضد بلاک (Anti-bot)
برگشت به مقاله‌ها

اسکریپینگ با Selenium و مدیریت پراکسی (۲۰۲۶)

اسکریپینگ با Selenium و مدیریت پراکسی (۲۰۲۶)
این مقاله راهنمای عملیِ ۲۰۲۶ برای استفاده از پراکسی در اسکریپینگ با Selenium است: روش ساده با <strong>--proxy-server</strong>، پاسخ به احراز هویت پروکسی از طریق CDP (<strong>Fetch.continueWithAuth</strong>)، محدودیت‌های <strong>selenium-wire</strong> و نکات مربوط به چرخش، کارایی و تشخیص Cloudflare را پوشش می‌دهد.
آسان اسکریپ آسان اسکریپ
1405-06-08

مقدمه

در این راهنما کاربردی برای توسعه‌دهندگان پایتون می‌خواهیم نشان دهیم چگونه پراکسی‌ها را در اسکریپینگ با Selenium (نسخهٔ 4.x و بالاتر) به‌درستی یکپارچه کنید. در پایان این مقاله شما می‌آموزید: روش سادهٔ تنظیم پراکسی بدون احراز هویت، چگونگی پاسخ به چالش احراز هویت پروکسی با استفاده از Chrome DevTools Protocol (CDP)، محدودیت‌های راهکارهای قدیمی مثل selenium-wire و نحوهٔ انتخاب و یکپارچه‌سازی API یا پورت‌های پراکسی برای چرخش و عبور از حفاظ‌های مبتنی بر Cloudflare.

روش سریع: کدام روش را انتخاب کنیم

  • اگر پراکسی بدون نام‌کاربری/رمز است: از آرگومان --proxy-server در ChromeOptions یا FirefoxOptions استفاده کنید.
  • اگر پراکسی نام‌کاربری/رمز می‌خواهد: از CDP و Fetch.continueWithAuth استفاده کنید (روش مطمئن در ۲۰۲۶).
  • برای چرخش با بازده بالا: از ارائه‌دهنده‌های gateway-style استفاده کنید تا دوران IP سمت سرور انجام شود.
  • برای آنالیز درخواست/پاسخ: از mitmproxy جلوی Selenium یا دامین Network در CDP بهره ببرید؛ selenium-wire در پایتون‌های جدید خراب است.

پراکسی بدون احراز هویت: مثال ساده برای Chrome

وقتی پراکسی فقط یک آدرس IP و پورت است، ساده‌ترین راه افزودن پرچم --proxy-server به گزینه‌های کروم است. ورودی این قطعه کد: رشتهٔ پراکسی مثل "198.51.100.7:8080". خروجی: درخواستی که از طریق آن پراکسی هدایت می‌شود؛ شما می‌توانید با سرویس‌هایی مانند httpbin خروجی IP را چک کنید.

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

PROXY = "198.51.100.7:8080"  # ورودی: host:port
options = Options()
options.add_argument(f"--proxy-server=http://{PROXY}")
# خروجی: webdriver.Chrome بازشده با ترافیک از طریق پراکسی
driver = webdriver.Chrome(options=options)
driver.get("https://httpbin.org/ip")
print(driver.find_element("tag name", "body").text)
driver.quit()

توضیح مرحله‌به‌مرحله:

  1. تعریف PROXY: رشتهٔ آدرس پراکسی.
  2. ساخت Options و افزودن --proxy-server با پیشوند scheme مناسب (http/https/socks5).
  3. ایجاد درایور با webdriver.Chrome(options=...) و بازکردن صفحه برای بررسی IP.

پراکسی در Firefox

برای فایرفاکس معمولاً بهتر است از شیٔ Proxy یا آپشن options.proxy استفاده کنید. ورودی همان آدرس host:port است و خروجی همان رفت و برگشت ترافیک از طریق پراکسی خواهد بود.

from selenium import webdriver
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.common.proxy import Proxy, ProxyType

proxy = Proxy()
proxy.proxy_type = ProxyType.MANUAL
proxy.http_proxy = "198.51.100.7:8080"
proxy.ssl_proxy = "198.51.100.7:8080"

options = Options()
options.proxy = proxy

driver = webdriver.Firefox(options=options)
driver.get("https://httpbin.org/ip")
driver.quit()

پراکسی‌های احراز هویت‌شده: چرا URL کار نمی‌کند و راه حل CDP

فرمت http://user:pass@host:port در حالت headless توسط کروم محدود شده و تکنیک‌های قدیمی مانند افزونهٔ درون‌حافظه (in-memory extension) در نسخه‌های جدید Chrome به‌خاطر Manifest V3 و lifecycle سرویس‌ورکرها پایدار نیستند. روش قابل‌اطمینان در ۲۰۲۶ استفاده از Chrome DevTools Protocol است تا چالش احراز هویت را در سطح شبکه جواب دهیم.

نمونهٔ CDP: wrapper ساده برای پاسخ به Fetch.authRequired

این کلاس یک ورودی می‌گیرد: میزبان/پورت/نام‌کاربری/رمز پراکسی. خروجی آن یک webdriver.Chrome است که درخواست‌های نیازمند احراز هویت را از طریق CDP پاسخ می‌دهد.

import json
import threading
import requests  # برای گرفتن لیست targets از debug endpoint
import websocket  # pip install websocket-client
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

class ChromeWithAuthProxy:
    """ورودی: proxy_host, proxy_port, proxy_user, proxy_pass
    خروجی: webdriver.Chrome که از CDP برای پاسخ auth استفاده می‌کند."""

    def __init__(self, proxy_host, proxy_port, proxy_user, proxy_pass):
        self.proxy_host = proxy_host
        self.proxy_port = proxy_port
        self.proxy_user = proxy_user
        self.proxy_pass = proxy_pass
        self._ws = None
        self._cmd_id = 0
        self._lock = threading.Lock()

    def _cdp_send(self, method, params=None):
        with self._lock:
            self._cmd_id += 1
            msg = {"id": self._cmd_id, "method": method}
        if params is not None:
            msg["params"] = params
        self._ws.send(json.dumps(msg))

    def _cdp_loop(self):
        while True:
            try:
                msg = json.loads(self._ws.recv())
            except Exception:
                return
            # وقتی CDP پیغام Fetch.authRequired داد، از continueWithAuth استفاده می‌کنیم
            if msg.get("method") == "Fetch.authRequired":
                self._cdp_send("Fetch.continueWithAuth", {
                    "requestId": msg["params"]["requestId"],
                    "authChallengeResponse": {
                        "response": "ProvideCredentials",
                        "username": self.proxy_user,
                        "password": self.proxy_pass,
                    },
                })
            elif msg.get("method") == "Fetch.requestPaused":
                self._cdp_send("Fetch.continueRequest", {
                    "requestId": msg["params"]["requestId"],
                })

    def new_driver(self):
        options = Options()
        options.add_argument(f"--proxy-server=http://{self.proxy_host}:{self.proxy_port}")
        options.add_argument("--remote-allow-origins=*")  # اجازهٔ اتصال websocket CDP
        driver = webdriver.Chrome(options=options)

        debugger_address = driver.capabilities["goog:chromeOptions"]["debuggerAddress"]
        targets = requests.get(f"http://{debugger_address}/json", timeout=10).json()
        page_target = next(t for t in targets if t["type"] == "page")
        self._ws = websocket.create_connection(page_target["webSocketDebuggerUrl"])
        threading.Thread(target=self._cdp_loop, daemon=True).start()
        self._cdp_send("Fetch.enable", {"handleAuthRequests": True, "patterns": [{"urlPattern": "*"}]})
        return driver

شرح مختصر عملکرد و نقش توابع:

  • __init__: پارامترهای پراکسی را ذخیره می‌کند.
  • _cdp_send: بسته‌های JSON به وب‌سوکت CDP می‌فرستد؛ مدیریت شناسهٔ پیام‌ها (id) را برعهده دارد.
  • _cdp_loop: حلقه‌ای که پیام‌های ورودی CDP را می‌خواند و به Fetch.authRequired و Fetch.requestPaused پاسخ می‌دهد.
  • new_driver: درایور کروم را می‌سازد، آدرس debugger را خوانده و به websocket صفحه متصل می‌شود و Fetch.enable را با handleAuthRequests روشن می‌کند (خروجی: یک webdriver فعال با auth proxy).

نکات عملی دربارهٔ CDP

  • این روش در headless و non-headless یکسان کار می‌کند و از مشکلات timing افزونهٔ موقت جلوگیری می‌کند.
  • اطمینان حاصل کنید پرچم --remote-allow-origins=* اضافه شده تا اتصال CDP محلی مجاز باشد.
  • حساسیت‌های امنیتی: نام‌کاربری و رمز در پروسس پایتون نگهداری می‌شوند؛ دسترسی به لاگ‌ها و محیط اجرا را کنترل کنید تا آستانهٔ افشای اعتبارنامه‌ها کاهش یابد.

روش قدیمی: selenium-wire (Legacy) — چرا دیگر قابل اعتماد نیست

در ۲۰۲۶ بستهٔ selenium-wire در بسیاری از توزیع‌های مدرن پایتون به‌خاطر وابستگی‌های قدیمی دیگر import نمی‌شود. اگر در پروژه‌ای به آن وابسته هستید، یا باید نسخهٔ قفل‌شدهٔ محیط اجرا را نگه دارید یا به راهکارهایی مثل CDP یا mitmproxy مهاجرت کنید. برای بازرسی درخواست/پاسخ توصیهٔ پایدارتری این است که mitmproxy را جلوی Selenium اجرا کنید یا از دامین Network در CDP استفاده کنید.

یکپارچه‌سازی با Proxy APIs و Proxy Port

بسیاری از ارائه‌دهندگان «smart proxy» یک API endpoint یا یک proxy port ارائه می‌دهند. مشکل رایج با API endpoint این است که وقتی مرورگر headless از لینک‌های نسبی استفاده کند، Selenium ممکن است URL نسبی را به آدرس endpoint الحاق کند و صفحهٔ مقصد به‌درستی بارگذاری نشود. بنابراین توصیه می‌شود در صورت امکان از proxy port استفاده کنید و آن را مثل پراکسی عادی (host:port) به Selenium وصل کنید.

چرخش پراکسی‌ها و ملاحظات Performance

نکات مهم:

  • راه‌اندازی مجدد WebDriver برای هر درخواست هزینه‌بر است و مناسب بارهای بزرگ نیست.
  • برای Throughput بالا از gateway-style providers استفاده کنید تا rotation سمت سرور انجام شود و نیازی به restart مرورگر نباشد.
  • در طراحی، محدودیت‌هایی مثل latency، تعداد کانکشن همزمان و محدودیت‌های IP provider را لحاظ کنید.

پراکسی، TLS/JA3 و مقابله با Cloudflare

فقط تغییر IP معمولاً برای عبور از محافظت‌هایی مثل Cloudflare کافی نیست. سیگنال‌های دیگری که بررسی می‌شوند شامل TLS/JA3 fingerprint، مقدار navigator.webdriver، الگوهای حرکتی کاربر و پرچم headless هستند. برای کاهش fingerprinting می‌توانید از گزینه‌هایی مثل undetected-chromedriver یا nodriver استفاده کنید یا از smart proxyهایی بهره ببرید که عبور از Cloudflare را در سمت سرور انجام می‌دهند.

نحوهٔ بررسی اینکه واقعا از پراکسی استفاده می‌کنید

یک تست ساده: صفحه‌ای که IP مشتری را برمی‌گرداند (مانند httpbin) را باز کنید و خروجی را با IP پراکسی مقایسه کنید. همچنین بررسی TLS fingerprint مفید است اگر به‌نظر می‌رسد محدودیت TLS باعث بلاک شدن شده است.

نکات امنیتی و بهترین روش‌ها

  • هرگز نام‌کاربری/رمز پراکسی را در سورس کد عمومی قرار ندهید؛ از متغیرهای محیطی یا secret manager استفاده کنید.
  • لاگ‌های ساختارمند نگهداری کنید اما اعتبارنامه‌ها را فیلتر کنید.
  • برای حجم بالای درخواست، اتصال‌های همزمان و retry با backoff مناسب پیاده‌سازی کنید تا از ban شدن سریع جلوگیری شود.
  • در صورت نیاز به بازرسی ترافیک HTTPS از mitmproxy استفاده کنید، ولی مطمئن شوید اصول قانونی و اخلاقی را رعایت می‌کنید.

جمع‌بندی

برای پراکسی‌های بدون احراز هویت از --proxy-server استفاده کنید؛ برای پروکسی‌های نیازمند نام‌کاربری/رمز در ۲۰۲۶ بهترین راه پاسخ به چالش auth از طریق CDP (Fetch.continueWithAuth) است. selenium-wire دیگر روی پایتون‌های مدرن قابل اتکا نیست؛ برای بازرسی ترافیک از mitmproxy یا دامین Network در CDP بهره ببرید. در نهایت، اگر نیاز به چرخش با بازده بالا دارید یا دنبال دورزدن محافظ‌های پیچیده‌تری مثل Cloudflare هستید، از ارائه‌دهنده‌های smart proxy یا ابزارهای کمتر قابل‌شناسایی مثل undetected-chromedriver/nodriver استفاده کنید.

مطالب مرتبط

مقاله‌های مرتبط