Skip to content
مقاله / 2 دقیقه مطالعه

پروکسی‌های چرخشی Selenium در پایتون: ChromeOptions، احراز هویت، و چرخش به‌ازای هر نشست

Selenium یک پروکسی را به اجرای مرورگر گره می‌زند، نه به درخواست — و همین چرخش را دست‌وپاگیر می‌کند و چرخش همراه با احراز هویت را به شکلی دیگر دست‌وپاگیر. این سه رویکردی هستند که واقعاً در Selenium 4 کار می‌کنند، همراه با کد پایتون برای ChromeOptions، یک a

Selenium مفهومی به نام پروکسی چرخشی ندارد. نشست مرورگر و IP خروجی در زمان اجرا به هم گره می‌خورند: شما --proxy-server= را به ChromeOptions می‌دهید، درایور شروع می‌شود، و هر درخواستی که آن مرورگر انجام می‌دهد تا زمانی که آن را ببندید از همان endpoint عبور می‌کند.

همین یک واقعیت طراحی توضیح می‌دهد چرا جست‌وجو برای selenium rotating proxy اینقدر توصیه‌های متناقض برمی‌گرداند. بعضی راهنماها مرورگر را به‌ازای هر پروکسی دوباره اجرا می‌کنند. بعضی دیگر سراغ یک کتابخانه wrapper می‌روند. هر دو کار می‌کنند — اما در جاهای متفاوتی شکست می‌خورند، و چیزی که بیشتر تلاش‌های اول را خراب می‌کند احراز هویت است، نه خود چرخش.

این راهنما مکانیک را آن‌گونه که برای Chrome و Chromium تحت کنترل Selenium 4 در پایتون اعمال می‌شود پوشش می‌دهد: اینکه فلگ‌های پروکسی واقعاً چه چیزی می‌پذیرند، سه روش قابل استفاده برای چرخش، نحوه تأیید IP خروجی، و دام‌هایی که بیشترین زمان را می‌گیرند.

چرا چرخش در Selenium با requests، httpx یا Scrapy تفاوت دارد

  • requests و httpx پروکسی را به‌عنوان آرگومان هر فراخوانی می‌گیرند. چرخش یعنی تغییر یک دیکشنری.
  • Scrapy می‌تواند پروکسی‌ها را بین درخواست‌ها در یک میان‌افزار دانلودر عوض کند، بدون راه‌اندازی مجدد نشست.
  • Selenium پروکسی را روی فرایند مرورگر تنظیم می‌کند. هیچ آرگومان پروکسی پشتیبانی‌شده‌ای برای هر درخواست وجود ندارد، بنابراین چرخش در میانه نشست نیازمند یک لایه رهگیری مانند selenium-wire یا کار با DevTools Protocol است.

محدودیت دوم خود فلگ Chrome است. --proxy-server=socks5://host:port یک scheme، یک host و یک port می‌پذیرد — و چیز دیگری نه. هیچ فیلد اطلاعات ورود وجود ندارد. اگر پروکسی احراز هویت بخواهد، Chrome یک دیالوگ احراز هویت HTTP نشان می‌دهد که Selenium نمی‌تواند آن را پر کند و حالت headless اصلاً آن را رندر نمی‌کند. بعضی endpointهای SOCKS5 به‌طور مشابه بدون اطلاعات ورودی که فلگ جایی برای حمل آن ندارد اتصال را رد می‌کنند.

پس واقعاً دو مسئله جدا برای حل وجود دارد: IP خروجی را بچرخانید، و اطلاعات ورود را به پروکسی برسانید.

سه رویکرد، مقایسه‌شده

رویکرد دانه‌بندی چرخش اطلاعات ورود را مدیریت می‌کند کجا دردناک است
راه‌اندازی مجدد درایور به‌ازای هر پروکسی به‌ازای هر نشست مرورگر خیر — با allowlist کردن IP همراه کنید هر چرخش هزینه یک راه‌اندازی مرورگر را دارد
افزونه احراز هویت از طریق --load-extension به‌ازای هر نشست مرورگر بله سیم‌کشی افزونه و رفتارهای عجیب headless
selenium-wire به‌ازای هر درخواست بله وابستگی اضافی به‌علاوه یک لایه رهگیری محلی

بیشتر خودکارسازی مرورگر به چرخش به‌ازای هر درخواست نیاز ندارد. اگر گردش کار شما این است که صفحه‌ای را باز کنید، منتظر JavaScript بمانید، استخراج کنید، و بروید، چرخش به‌ازای هر نشست معمولاً کافی و بسیار آسان‌تر برای اشکال‌زدایی است.

رویکرد ۱: راه‌اندازی مجدد درایور به‌ازای هر پروکسی

این کم‌جادوترین گزینه و آسان‌ترین برای استدلال در محیط عملیاتی است.

import random

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By

# Documentation ranges from RFC 5737 - replace with your own endpoints.
PROXIES = [
    'http://198.51.100.10:8080',
    'http://198.51.100.11:8080',
    'socks5://203.0.113.7:1080',
]


def build_driver(proxy_url: str) -> webdriver.Chrome:
    options = Options()
    options.add_argument(f'--proxy-server={proxy_url}')
    options.add_argument('--headless=new')
    options.add_argument('--disable-dev-shm-usage')
    return webdriver.Chrome(options=options)


def exit_ip(driver: webdriver.Chrome) -> str:
    driver.get('https://api.ipify.org')
    return driver.find_element(By.TAG_NAME, 'body').text.strip()


for proxy in random.sample(PROXIES, len(PROXIES)):
    driver = build_driver(proxy)
    try:
        print(f'{proxy} -> {exit_ip(driver)}')
    finally:
        driver.quit()

سه عادت یک اسکریپت کارآمد را از اسکریپتی که به‌طور مرموزی یک‌شبه می‌میرد جدا می‌کند:

  1. همیشه در یک بلوک finally خارج شوید. فرایندهای یتیم chromedriver حافظه و قفل فایل را نگه می‌دارند.
  2. به هر کارگر موازی پوشه پروفایل خودش را با --user-data-dir=/tmp/chrome-worker-1 بدهید. دو نمونه Chrome که یک پروفایل را به اشتراک می‌گذارند برای قفل با هم رقابت می‌کنند.
  3. از --no-sandbox فقط در کانتینرهایی که کنترل می‌کنید استفاده کنید، هرگز در یک نشست دسکتاپ معمولی.

این رویکرد با پروکسی‌های بدون احراز هویت تمیز کار می‌کند، و اینجاست که allowlist کردن IP وارد می‌شود.

رویکرد ۲: رساندن اطلاعات ورود به مرورگر

گزینه الف: IP خروجی خودتان را allowlist کنید

بسیاری از ارائه‌دهندگان اجازه می‌دهند IPی که از آن تماس می‌گیرید را مجاز کنید، که در آن نقطه پروکسی شما را بدون نام کاربری یا رمز عبور می‌پذیرد و رویکرد ۱ بدون تغییر کار می‌کند. دو هشدار: IP خروجی شما باید پایدار باشد، که در CI یا روی اتصال خانگی که دوباره وصل می‌شود دشوار است، و یک IP allowlist‌شده یک مجوز دائمی است که باید به یاد داشته باشید لغو کنید.

گزینه ب: افزونه‌ای که به درخواست احراز هویت پاسخ می‌دهد

افزونه‌های Chrome می‌توانند اطلاعات ورود پروکسی را از طریق chrome.webRequest.onAuthRequired فراهم کنند. یک جفت مینیمال Manifest V3 شبیه این است.

{
  "manifest_version": 3,
  "name": "proxy-auth",
  "version": "1.0",
  "permissions": ["webRequest", "webRequestAuthProvider"],
  "host_permissions": ["<all_urls>"],
  "background": { "service_worker": "background.js" }
}
chrome.webRequest.onAuthRequired.addListener(
  (details, callback) => {
    callback({ authCredentials: { username: 'USER', password: 'PASS' } });
  },
  { urls: ['<all_urls>'] },
  ['asyncBlocking']
);

آن را وقتی درایور شروع می‌شود بارگذاری کنید:

options.add_argument('--disable-extensions-except=/opt/proxy-auth')
options.add_argument('--load-extension=/opt/proxy-auth')

دو نکته که پیش از ساختن روی این باید بدانید. مدیریت callback احراز هویت در نسخه‌های Chrome تغییر کرده است؛ اگر شکل callback رد شد، به‌جای فراخوانی callback() شیء اطلاعات ورود را از listener برگردانید. و بارگذاری افزونه در حالت headless بخشی است که بیشتر از همه ممکن است شما را غافلگیر کند — از --headless=new به‌جای حالت headless قدیمی استفاده کنید.

گزینه پ: selenium-wire

اگر واقعاً به چرخش به‌ازای هر درخواست یا در میانه نشست نیاز دارید، selenium-wire یک پروکسی محلی کوچک جلوی مرورگر می‌گذارد و به شما اجازه می‌دهد پروکسی بالادستی را در پایتون تنظیم کنید.

from selenium.webdriver.common.by import By
from seleniumwire import webdriver

proxy = 'http://USER:[email protected]:8080'

wire_options = {
    'proxy': {
        'http': proxy,
        'https': proxy,
        'no_proxy': 'localhost,127.0.0.1',
    }
}

driver = webdriver.Chrome(seleniumwire_options=wire_options)
try:
    driver.get('https://api.ipify.org')
    print(driver.find_element(By.TAG_NAME, 'body').text)
finally:
    driver.quit()

چون یک لایه رهگیری محلی درگیر است، نسخه‌های selenium-wire و Selenium را با هم پین کنید و پس از ارتقای هر یک دوباره تست کنید.

آیا به‌طور خاص برای Selenium به SOCKS5 نیاز دارید؟

معمولاً نه، اما پشتیبانی می‌شود. Chrome socks5://host:port را در --proxy-server می‌پذیرد، و SOCKS5 ترافیک را در لایه سوکت تحویل می‌دهد، که وقتی استخر شما فقط SOCKS است مفید است. محدودیت اطلاعات ورود یکسان است: فلگ جایی برای گذاشتن نام کاربری ندارد، پس دوباره به allowlist کردن یا یک افزونه برمی‌گردید.

یک توضیح که برای تأیید مهم است: مسیریابی مرورگر از طریق SOCKS5 ترافیک TCP را کنترل می‌کند. این به‌خودی‌خود جلوی WebRTC را برای باز کردن یک مسیر UDP بدون پروکسی نمی‌گیرد. چک‌لیست زیر را ببینید.

انتخاب استراتژی چرخش برای خودکارسازی مرورگر

استراتژی محرک چرخش مناسب برای بده‌بستان‌ها
نشست چسبنده به‌ازای هر وظیفه در شروع وظیفه ورودها، جریان‌های چندمرحله‌ای، لیست‌های صفحه‌بندی‌شده یک IP کل وظیفه را جذب می‌کند
به‌ازای هر N صفحه یا هر بازه زمانی شمارنده یا تایمر خزش گسترده، صفحات فهرست چرخش در میانه جریان می‌تواند وضعیت را بشکند
به‌ازای هر نشست مرورگر هر بار راه‌اندازی درایور اسکرپینگ ساده و کارهای اسکرین‌شات هزینه راه‌اندازی بر زمان اجرا غالب است
یک پروکسی به‌ازای هر کارگر در شروع کارگر خزش‌های موازی بار هر IP نیاز به ردیابی دارد

چون یک نشست مرورگر گران است، چرخش به‌ازای هر درخواست معمولاً پیش‌فرض اشتباهی برای Selenium است — شما برای هر URL هزینه راه‌اندازی مجدد یک فرایند را می‌پردازید. سازگاری مکان نیز برای نتایج وابسته به جغرافیا بیش از حجم خام IP اهمیت دارد. اگر در حال نمونه‌برداری از صفحات بومی‌سازی‌شده، فروشگاه‌ها یا قیمت‌ها برای یک بازار خاص هستید، منطقه خروجی را برای کل نشست ثابت نگه دارید، وگرنه اعدادی که جمع می‌کنید در اجرای بعدی قابل بازتولید نخواهند بود.

تأیید IP خروجی، DNS و WebRTC

تأیید برای اجراهای headless اختیاری نیست، چون پنجره‌ای برای نگاه کردن وجود ندارد. سه چیز را بررسی کنید.

۱. IP خروجی واقعاً تغییر کرده است. از کمک‌تابع exit_ip بالا دوباره استفاده کنید و مطمئن شوید مقدار با آدرس خودتان تفاوت دارد. گاهی با یک endpoint echo دوم مقایسه کنید تا پاسخ کش‌شده شما را فریب ندهد.

۲. رفتار حل DNS. با یک پروکسی HTTP نام میزبان معمولاً توسط پروکسی حل می‌شود، که همان چیزی است که می‌خواهید. رفتار SOCKS5 ارزش تأیید دارد نه فرض: یک صفحه تست نشت DNS عمومی را در یک اجرای غیر headless باز کنید و resolver نشان‌داده‌شده را با resolver ISP خود مقایسه کنید.

۳. WebRTC. Chrome ممکن است حتی وقتی پروکسی تنظیم شده از یک مسیر UDP بدون پروکسی استفاده کند. فلگ زیر WebRTC را مجبور می‌کند از UDP بدون پروکسی اجتناب کند.

options.add_argument('--force-webrtc-ip-handling-policy=disable_non_proxied_udp')

در نهایت، پروکسی استفاده‌شده و IP خروجی مشاهده‌شده را برای هر نشست ثبت کنید. وقتی یک صفحه چالش، زبان اشتباه، یا قیمتی غیرمنتظره برگرداند، آن خط لاگ اولین چیزی است که می‌خواهید.

دام‌هایی که بیشترین زمان را هدر می‌دهند

  • نشت درایور. هر chromedriver یتیم حافظه را نگه می‌دارد. در یک کانتینر این معمولاً شبیه یک کرش مرموز به نظر می‌رسد نه یک نشت. همه‌جا از try / finally استفاده کنید.
  • پوشه‌های پروفایل اشتراکی. نمونه‌های موازی که یک پوشه داده کاربر را به اشتراک می‌گذارند یکدیگر را بلاک می‌کنند. یک پوشه به‌ازای هر کارگر.
  • تعبیر شکست‌ها به‌عنوان بلاک. یک 403، یک پاسخ خالی و یک صفحه چالش سه مسئله متفاوت‌اند. قبل از سرزنش پروکسی، کد وضعیت، عنوان صفحه و طول بدنه را ثبت کنید.
  • تعمیم از یک صفحه. قبل از خودکارسازی صد صفحه IP خروجی را تأیید کنید، نه بعد از آن.
  • فراموش کردن قوانین غیرفنی. چرخاندن یک IP آنچه را که شرایط خدمات یک سایت، robots.txt آن، یا قانون حفاظت از داده قابل اعمال مجاز می‌داند تغییر نمی‌دهد — به‌ویژه اگر در حوزه GDPR داده شخصی جمع می‌کنید. سرعت را کم کنید، در بروز خطا عقب بکشید، و همزمانی را معتدل نگه دارید.

نسخه کوتاه

  • Selenium یک پروکسی را به راه‌اندازی مرورگر گره می‌زند، پس چرخش یعنی راه‌اندازی مجدد مگر اینکه یک لایه رهگیری اضافه کنید.
  • فلگ --proxy-server کروم نمی‌تواند اطلاعات ورود را حمل کند. allowlist کردن IP، یک افزونه احراز هویت، یا selenium-wire را انتخاب کنید.
  • برای خودکارسازی مرورگر، چرخش به‌ازای هر نشست یا چسبنده را به چرخش به‌ازای هر درخواست ترجیح دهید.
  • IP خروجی را داخل نشست تأیید کنید، UDP بدون پروکسی WebRTC را غیرفعال کنید، و ثبت کنید کدام پروکسی به کدام درخواست سرویس داده است.
  • درایور را در finally ببندید، پوشه‌های پروفایل را به‌ازای هر کارگر جدا کنید، و راه‌اندازی را روی یک URL قبل از مقیاس‌دهی اثبات کنید.