← بازگشت به وبلاگ

پروکسی در زمینه Playwright به جای مرورگر: صرفه‌جویی در حافظه و چرخش پاک IP

بررسی می‌کنیم که چرا اتصال پروکسی به BrowserContext در Playwright مؤثرتر از اتصال به کل مرورگر است - با کد، بنچمارک‌ها و الگوهای آماده چرخش.

📅۲۹ مهر ۱۴۰۵

اگر شما اسکریپت‌های پارسینگ یا چندحسابی را در Playwright اجرا می‌کنید و برای هر پروکسی یک نمونه جداگانه از مرورگر را بالا می‌آورید — شما به طور بیهوده حافظه و CPU را مصرف می‌کنید. Playwright می‌تواند پروکسی را در سطح BrowserContext، نه کل مرورگر، تعیین کند و این به طور اساسی معماری اسکریپت‌ها را تغییر می‌دهد: یک فرآیند مرورگر، ده‌ها زمینه ایزوله با IPهای مختلف، کوکی‌ها و اثر انگشت. در این مقاله — چگونه این کار می‌کند، کد در Python و Node.js، و چرا این موضوع به ویژه در کار با پروکسی‌های مقیم و موبایل مهم است.

مسئله: پروکسی در browser.launch() — گران و نامناسب

رویکرد کلاسیک بسیاری از اسکریپت‌های Playwright به این شکل است: برای هر پروکسی chromium.launch({ proxy: {...} }) فراخوانی می‌شود، یک فرآیند جداگانه مرورگر ایجاد می‌شود، کار می‌کند، سپس بسته می‌شود و بعدی با پروکسی جدید راه‌اندازی می‌شود. اگر بخواهید 50 آدرس IP را به صورت همزمان برای پارسینگ Wildberries یا Ozon پردازش کنید، این به معنای 50 فرآیند کامل Chromium به صورت همزمان است — هر کدام از 150 تا 300 مگابایت RAM فقط در شروع مصرف می‌کند، بدون احتساب تب‌ها و رندرینگ.

در عمل، این به سقف می‌رسد حتی با 15-20 مرورگر همزمان در یک VPS معمولی با 8 گیگابایت حافظه. سرور شروع به سوآپ کردن می‌کند، اسکریپت‌ها با تایم‌اوت سقوط می‌کنند، بخشی از پروکسی‌ها نمی‌توانند کار کنند. مشکل نه در پروکسی و نه در Playwright است — مشکل در معماری است: هر بار که مرورگر راه‌اندازی می‌شود، کل موتور رندرینگ، پردازش GPU، شبکه و ده‌ها رشته کمکی را به همراه دارد، در حالی که در واقع شما فقط به یک IP جدید و یک جلسه پاک نیاز دارید.

Playwright این مشکل را به طور شیک حل می‌کند: پروکسی می‌تواند نه در زمان راه‌اندازی مرورگر، بلکه در زمان ایجاد BrowserContext — یک "جعبه شنی" ایزوله سبک درون یک فرآیند مرورگر یکسان — تعیین شود. یک فرآیند Chromium می‌تواند ده‌ها زمینه را نگه دارد، هر کدام با پروکسی خود، کوکی‌ها و localStorage خود، بدون تداخل با یکدیگر.

مرورگر در مقابل BrowserContext: تفاوت چیست

Browser — خود فرآیند موتور (Chromium، Firefox یا WebKit) است که یک بار راه‌اندازی می‌شود و مدت طولانی زندگی می‌کند. BrowserContext — یک محیط ایزوله درون این فرآیند است، مشابه یک پنجره خصوصی (incognito)، با کوکی‌ها، کش، مجوزها و، برای ما مهم است، تنظیمات شبکه خود، از جمله پروکسی.

اگر با مرورگرهای ضد شناسایی مانند Dolphin Anty یا AdsPower مقایسه کنیم — هر "پروفایل" در واقع یک کانتینر ایزوله جداگانه با کوکی‌ها و اثر انگشت است. Playwright اجازه می‌دهد تا منطق مشابهی به صورت برنامه‌نویسی ایجاد شود: یک مرورگر — چندین زمینه، هر کدام مانند یک "پروفایل" جداگانه با IP خود.

پارامتر پروکسی در مرورگر پروکسی در BrowserContext
حافظه برای یک IP 150-300 مگابایت (فرآیند جدید) 15-40 مگابایت (زمینه جدید)
سرعت تغییر IP 2-5 ثانیه (راه‌اندازی مجدد مرورگر) 0.1-0.3 ثانیه (زمینه جدید)
ایزولاسیون کوکی‌ها کامل (فرآیندهای مختلف) کامل (زمینه‌ها کوکی‌ها را به اشتراک نمی‌گذارند)
حداکثر جلسات همزمان در 8 گیگابایت RAM ~20-25 ~150-200

پروکسی در زمینه: کد در Python

در زیر الگوی پایه‌ای: یک مرورگر یک بار راه‌اندازی می‌شود و سپس برای هر پروکسی یک زمینه جداگانه ایجاد می‌شود. این API همزمان Playwright است، اما منطق برای نسخه async مشابه است.

from playwright.sync_api import sync_playwright

proxies = [
    {"server": "http://proxy1.example.com:8000", "username": "user1", "password": "pass1"},
    {"server": "http://proxy2.example.com:8000", "username": "user2", "password": "pass2"},
    {"server": "http://proxy3.example.com:8000", "username": "user3", "password": "pass3"},
]

with sync_playwright() as p:
    # مرورگر را یک بار راه‌اندازی می‌کنیم، بدون پروکسی
    browser = p.chromium.launch(headless=True)

    for proxy in proxies:
        # پروکسی به طور خاص به زمینه اختصاص داده می‌شود، نه به مرورگر
        context = browser.new_context(
            proxy={
                "server": proxy["server"],
                "username": proxy["username"],
                "password": proxy["password"],
            },
            user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
        )
        page = context.new_page()
        page.goto("https://httpbin.org/ip", timeout=30000)
        print(page.content())

        page.close()
        context.close()  # فقط زمینه را می‌بندیم، مرورگر به کار خود ادامه می‌دهد

    browser.close()

نکته کلیدی — browser.launch() یک بار خارج از حلقه فراخوانی می‌شود، در حالی که browser.new_context() — درون حلقه بر اساس پروکسی. برای پروکسی‌هایی که نیاز به احراز هویت با نام کاربری و رمز عبور دارند، Playwright به طور خودکار هدرهای Basic Auth را در سطح شبکه قرار می‌دهد و نیازی به تنظیمات اضافی نیست.

پروکسی در زمینه: کد در Node.js

همان منطق در JavaScript با استفاده از async/await — که اغلب در کنار فریمورک‌های پارسینگ در Node.js استفاده می‌شود:

const { chromium } = require('playwright');

const proxies = [
  { server: 'http://proxy1.example.com:8000', username: 'user1', password: 'pass1' },
  { server: 'http://proxy2.example.com:8000', username: 'user2', password: 'pass2' },
];

(async () => {
  const browser = await chromium.launch({ headless: true });

  for (const proxy of proxies) {
    const context = await browser.newContext({
      proxy: {
        server: proxy.server,
        username: proxy.username,
        password: proxy.password,
      },
    });

    const page = await context.newPage();
    await page.goto('https://httpbin.org/ip', { timeout: 30000 });
    console.log(await page.content());

    await page.close();
    await context.close();
  }

  await browser.close();
})();

برای پردازش همزمان ده‌ها پروکسی، می‌توان زمینه‌ها را از طریق Promise.all ایجاد کرد، بدون اینکه منتظر بسته شدن قبلی باشیم — این همان مزیت اصلی رویکرد "یک مرورگر، چند زمینه" در مقایسه با راه‌اندازی فرآیندهای جداگانه است.

چرخش پاک پروکسی بدون راه‌اندازی مجدد مرورگر

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

async function rotateProxy(browser, proxyList, index) {
  const proxy = proxyList[index % proxyList.length];
  const context = await browser.newContext({ proxy });
  const page = await context.newPage();
  return { context, page };
}

// به این شکل استفاده می‌کنیم: در هر N-امین تکرار IP را تغییر می‌دهیم
for (let i = 0; i < 100; i++) {
  if (i % 10 === 0) {
    const { context, page } = await rotateProxy(browser, proxies, i);
    // با جلسه جدید 10 درخواست کار می‌کنیم
  }
}

این الگو به ویژه در پارسینگ بازارها مفید است: به عنوان مثال، هنگام جمع‌آوری قیمت‌ها از Wildberries یا Ozon، جایی که حفاظت در برابر ربات‌ها تعداد درخواست‌ها از یک IP را در واحد زمان ردیابی می‌کند. چرخش در سطح زمینه اجازه می‌دهد تا IP را هر 5-10 درخواست تغییر دهید، بدون اینکه در عملکرد به دلیل راه‌اندازی مجدد مرورگر آسیب ببینید.

برای چنین سناریوهایی، پروکسی‌های مقیم بسیار مناسب هستند — آنها IP کاربران واقعی را ارائه می‌دهند که بازارها کمتر به عنوان مشکوک علامت‌گذاری می‌کنند، بر خلاف دامنه‌های سروری واضح.

مقایسه از نظر حافظه و سرعت

در یک VPS آزمایشی با 8 گیگابایت RAM و 4 vCPU، بررسی دو رویکرد در وظیفه عبور از 100 صفحه با IPهای مختلف انجام شد. در مورد اول، برای هر IP یک chromium.launch() جداگانه راه‌اندازی شد، در حالی که در مورد دوم، یک مرورگر با ایجاد 100 زمینه به صورت متوالی استفاده شد.

معیار 100 راه‌اندازی browser.launch() 100 زمینه در 1 مرورگر
حافظه پیک ~6.8 گیگابایت ~1.9 گیگابایت
زمان کل اجرا 182 ثانیه 97 ثانیه
تعداد جلسات سقوط کرده به دلیل تایم‌اوت 7 0

صرفه‌جویی در حافظه در این تست حدود 70٪ بود و زمان کل اجرا تقریباً به نصف کاهش یافت به دلیل عدم وجود هزینه‌های راه‌اندازی و خاتمه فرآیند Chromium هر بار. در حجم‌های بزرگ — هزاران IP در روز — تفاوت حتی بیشتر مشهود می‌شود و این به طور مستقیم بر تعداد پروکسی‌هایی که می‌توانید در یک سرور بدون ارتقاء سخت‌افزار استفاده کنید تأثیر می‌گذارد.

موارد عملی: پارسینگ و چندحسابی

پارسینگ بازارها. هنگام جمع‌آوری قیمت‌ها از Wildberries، Ozon یا Avito، یک مرورگر یکسان ده‌ها زمینه را با IP منحصر به فرد و ذخیره‌سازی کوکی‌های خود باز نگه می‌دارد. این خطر مسدود شدن بر اساس الگوی درخواست‌ها را کاهش می‌دهد و اجازه می‌دهد داده‌ها سریع‌تر جمع‌آوری شوند، بدون اینکه منتظر راه‌اندازی مجدد فرآیند بین IP باشیم.

چندحسابی و فارم حساب‌ها. برای آزمایش کمپین‌های تبلیغاتی در Facebook Ads یا TikTok Ads از طریق اسکریپت‌های Playwright، منطقی است که هر حساب را در یک زمینه جداگانه با پروکسی و user-agent خود نگه داریم — این رفتار مرورگرهای ضد شناسایی مانند Multilogin یا GoLogin را شبیه‌سازی می‌کند، فقط بدون مجوز برای خود ضد شناسایی، به طور کامل برنامه‌نویسی.

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

برای سناریوهایی که در آن مهم است ترافیک موبایل را شبیه‌سازی کنید — به عنوان مثال، بررسی نسخه موبایل یک وب‌سایت یا تبلیغات در TikTok Ads — باید پروکسی‌های موبایل را در نظر بگیرید که IP را از اپراتورهای واقعی تلفن همراه ارائه می‌دهند و کمتر باعث مشکوک شدن به سیستم‌های ضد تقلب می‌شوند.

اگر هدف — پارسینگ سریع و پایدار بدون نیاز به ناشناسی بالا (به عنوان مثال، جمع‌آوری داده‌های عمومی از وب‌سایت‌ها بدون حفاظت شدید ضد ربات) باشد، پروکسی‌های دیتا سنتر بهترین نسبت سرعت به هزینه را در حجم‌های بالای درخواست‌ها ارائه می‌دهند.

خطاهای رایج و چگونه از آنها اجتناب کنیم

1. پروکسی‌ها در launch() تعیین می‌شوند، نه در new_context(). این رایج‌ترین اشتباه مبتدیان است — پروکسی که در زمان راه‌اندازی مرورگر تعیین می‌شود، به همه زمینه‌ها به طور همزمان اعمال می‌شود و نمی‌تواند به طور فردی بازنویسی شود. همیشه پروکسی را در new_context() ارسال کنید، اگر به انعطاف‌پذیری IP نیاز دارید.

2. زمینه‌های قدیمی بسته نمی‌شوند. اگر صدها زمینه ایجاد کنید و context.close() را فراخوانی نکنید، حافظه مرورگر به طور خطی افزایش می‌یابد تا زمانی که فرآیند به دلیل OOM سقوط کند. همیشه پس از اتمام کار با این IP، زمینه را ببندید.

3. یک user-agent برای همه زمینه‌ها. اگر پروکسی تغییر کند و اثر انگشت مرورگر یکسان باقی بماند (user-agent، زبان، منطقه زمانی)، سیستم‌های ضد ربات بازارها و شبکه‌های اجتماعی همچنان می‌توانند جلسات را به هم مرتبط کنند. user-agent و viewport را به همراه پروکسی برای هر زمینه تغییر دهید.

4. نادیده گرفتن تایم‌اوت‌های پروکسی. پروکسی‌های مقیم و موبایل گاهی اوقات به دلیل زیرساخت واقعی شبکه کندتر از پروکسی‌های دیتا سنتر پاسخ می‌دهند. یک تایم‌اوت معقول (20-40 ثانیه) هنگام فراخوانی page.goto() تعیین کنید، در غیر این صورت اسکریپت پروکسی کاری را "مرده" می‌داند.

5. تعداد زیادی زمینه به طور همزمان در یک مرورگر. با وجود صرفه‌جویی در حافظه، ایجاد بی‌رویه 500+ زمینه فعال به طور همزمان همچنان به محدودیت‌های فرآیند برخورد می‌کند. تعادل معقول — 30-80 زمینه فعال همزمان برای یک فرآیند مرورگر، بسته به حجم RAM سرور.

نتیجه‌گیری

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

اگر شما اسکریپتی در Playwright برای جمع‌آوری داده‌ها از Wildberries، Ozon یا برای کار با پنل‌های تبلیغاتی Facebook Ads و TikTok Ads می‌سازید، کیفیت خود پروکسی‌پول کمتر از معماری کد مهم نیست. برای وظایفی که اعتبار IP و مقاومت در برابر سیستم‌های ضد ربات مهم است، بهتر است از پروکسی‌های مقیم شروع کنید — آنها به خوبی با رویکرد "یک مرورگر، چند زمینه" که در بالا توضیح داده شده است، ترکیب می‌شوند.