مقدمه
وقتی با وباپهای پویا کار میکنیم، محتوای صفحه معمولاً از طریق درخواستهای پسزمینه (XHR یا Fetch) از سرور بارگذاری میشود. در وب اسکریپینگ، گرفتن این درخواستها میتواند به شما کمک کند دادهٔ خام API را مستقیم دریافت کنید، نقطهنظارت روی ترافیک بسازید یا حتی برخی درخواستها را مسدود و رفتار سایت را آزمایش کنید. در انتهای این مقاله شما میآموزید چگونه با Playwright درخواستهای XHR/Fetch را ضبط کنید، آنها را تحلیل کنید، درخواستها را مسدود یا تغییر دهید و نتایج را برای استفادهٔ بعدی بازپخش (replay) کنید.
درک XHR و روشهای HTTP
پیش از ورود به کد لازم است تفاوتهای پایهای روشهای HTTP را بدانیم، چون در تحلیل درخواستها معمولاً به GET, POST, PUT و DELETE برمیخوریم:
- GET: دریافت داده از سرور (معمولترین حالت برای بارگذاری محتوا).
- POST: ارسال داده به سرور (فرمها، لاگها یا تعاملات کاربر).
- PUT: بهروزرسانی منابع موجود.
- DELETE: حذف منابع.
در عمل، بسیاری از وباپها از XHR/Fetch برای بارگذاری مقادیر JSON یا ارسال فرمها استفاده میکنند؛ ضبط این درخواستها میتواند به شما امکان دسترسی مستقیم به APIها را بدهد و گاهی نیاز به اسکریپ کردن HTML را از بین ببرد.
اسکریپ کردن درخواستها با Playwright (نمونهٔ Python)
در این بخش یک نمونهٔ عملی Python با playwright.async_api میبینیم که روی رخدادهای درخواست گوش میدهد و فقط درخواستهای xhr و fetch را ثبت میکند.
from playwright.async_api import async_playwright
import asyncio
async def scrape(url):
async with async_playwright() as pw:
browser = await pw.chromium.launch(headless=True) # ورودی: headless یا headful
page = await browser.new_page() # خروجی: یک شیٔ Page برای تعامل
def on_request(request):
# نقش: فیلتر کردن درخواستها بر اساس نوع (xhr / fetch)
if request.resource_type in ("xhr", "fetch"):
# request.url : آدرس درخواست
# request.method : متد HTTP
print("Captured:", request.url, request.method)
# اتصال شنونده وقوع درخواست به صفحه
page.on("request", on_request)
await page.goto(url) # ناوبری به سایت هدف
await asyncio.sleep(5) # اجازه بدهیم پسزمینه فعالیت کند
await browser.close() # بستن مرورگر
# اجرای تابع اصلی
asyncio.run(scrape('https://example.com'))توضیح خطبهخط:
- وارد کردن async_playwright و استفاده از async with برای مدیریت چرخهٔ عمر (ایمن و تمیز).
- browser.new_page() یک صفحهٔ جدید میسازد؛ page.on("request", callback) به هر رخداد درخواست گوش میدهد.
- در on_request ما فقط درخواستهایی را که resource_type آنها برابر با xhr یا fetch است پردازش میکنیم.
- با request.url و request.method میتوانیم آدرس و متد را ذخیره یا تحلیل کنیم.
تحلیل دادههای ضبطشده (شمارش متدها)
گاهی میخواهیم آماری داشته باشیم: چند GET، POST و ... رخ داده است؛ برای جلوگیری از محدودیتهای دامنهٔ اسکوپِ توابع، از ساختار دادهٔ تغییرپذیر استفاده میکنیم.
from playwright.async_api import async_playwright
import asyncio
async def scrape_counts(url):
counts = {"GET": 0, "POST": 0, "PUT": 0, "DELETE": 0}
async with async_playwright() as pw:
browser = await pw.chromium.launch(headless=True)
page = await browser.new_page()
def on_request(request):
if request.resource_type in ("xhr", "fetch"):
method = request.method
if method in counts:
counts[method] += 1
page.on("request", on_request)
await page.goto(url)
await asyncio.sleep(5)
await browser.close()
print("Counts:", counts)
asyncio.run(scrape_counts('https://example.com'))خروجی: یک دیکشنری که تعداد هر متد را نشان میدهد — مفید برای فهم الگوی ترافیک و تصمیمگیری دربارهٔ بازپخش یا مسدودسازی برخی متدها.
تکنیکهای پیشرفته: استفاده از page.route برای کنترل مسیرها
اگر بخواهید نه تنها مشاهده بلکه درخواست را مسدود یا تغییر دهید، از page.route استفاده کنید. در مثال زیر درخواستهای POST از نوع XHR/Fetch را میبندیم (abort) و بقیه را اجازه میدهیم ادامه پیدا کنند.
import asyncio
from playwright.async_api import async_playwright
async def scrape_block_posts(url):
counts = {"GET": 0, "Blocked_POST": 0}
async with async_playwright() as pw:
browser = await pw.chromium.launch(headless=True)
page = await browser.new_page()
async def handler(route, request):
# همهٔ مسیرها را میگیریم و تصمیم میگیریم
if request.resource_type in ("xhr", "fetch") and request.method == "POST":
await route.abort() # جلوگیری از ارسال به سرور
counts["Blocked_POST"] += 1
else:
await route.continue_() # آزاد کردن درخواست
# ثبت route: الگوی "**/*" همه منابع را میگیرد
await page.route("**/*", handler)
await page.goto(url)
await asyncio.sleep(5)
await browser.close()
print(counts)
asyncio.run(scrape_block_posts('https://example.com'))نکات خطبهخط:
- page.route("**/*", handler) به شما اجازه میدهد مسیرها را قبل از ارسال به شبکه رهگیری کنید.
- route.abort() برای شبیهسازی قطعی شبکه یا مسدودسازی اطلاعات حساس مفید است.
- route.continue_() (در Python نام تابع ادامهدار با underscore است) درخواست را به حالت عادی برمیگرداند.
مثال کوتاه JavaScript (مقایسهٔ API)
برای کسانی که با Node.js کار میکنند، منطق مشابه است. این نمونهٔ سادهٔ JavaScript همان رفتار گوشدادن به request را نشان میدهد:
const playwright = require('playwright');
(async () => {
const browser = await playwright.chromium.launch({ headless: true });
const page = await browser.newPage();
page.on('request', request => {
if (request.resourceType() === 'xhr' || request.resourceType() === 'fetch') {
console.log('Captured', request.method(), request.url());
}
});
await page.goto('https://example.com');
await page.waitForTimeout(5000);
await browser.close();
})();بازپخش (Replay) و استفادهٔ بعدی از درخواستها
پس از ضبط، اغلب میخواهیم یک درخواست خاص را بازپخش کنیم (مثلاً برای دانلود JSON یا ساخت یک API client). برای این کار میتوان اطلاعات زیر را ذخیره کرد:
- URL
- Method
- Headers (Authorization، Content-Type و ...)
- Body / Post data
نمونهٔ بازپخش با کتابخانهٔ requests در پایتون:
import requests
# ورودیها: url، headers، payload که از Playwright ذخیره شدهاند
resp = requests.post(url, headers=headers, data=payload, timeout=10)
print(resp.status_code, resp.text[:500]) # خروجی: وضعیت و بخشی از پاسخنکته: هنگام بازپخش حتماً هدرهای لازم (مثلاً توکنهای احراز هویت یا کوکی) را منتقل کنید تا درخواست مشابهِ کلاینت اصلی رفتار کند.
چالشها و راهحلها
در عمل با چند چالش معمول روبهرو میشویم:
- CORS: مشکلات مرتبط با سیاستهای بینمنبعی معمولاً در سمت مرورگر رخ میدهد؛ برای اسکریپینگ سرور-به-سرور بهتر است درخواستها را بازپخش کنید یا از پراکسی استفاده کنید.
- مسائل async/await: عدم استفاده از await میتواند منجر به دریافت اشیاء از نوع Promise یا دادهٔ ناقص شود—همهٔ فراخوانیهای async را await کنید یا مدیریت درست Task/شِما را پیاده کنید.
- پایداری و نرخگذاری: زیاد زدن درخواستها ممکن است مسدودسازی (rate-limiting) یا CAPTCHA را فعال کند؛ از تاخیرهای منطقی، retry با backoff و pool پراکسی استفاده کنید.
- سرعت و performance: اگر تنها به XHRها نیاز دارید، با الگوهای route محدود کنید تا پردازش اضافی حداقل شود (مثلاً "**/*.json" یا فیلتر بر اساس Host).
بهترینروشها
- همیشه در تابع اصلی از async استفاده و تمامی عملیات غیرهمزمان را await کنید.
- برای ذخیرهٔ دادهها از ساختار منظم (JSON با فیلدهای url، method، headers، body) استفاده کنید تا بعداً بتوانید بازپخش کنید.
- در هنگام ضبط دادهٔ حساس (توکنها، کوکیها) مراقب حریم خصوصی و قوانین سایت باشید و قوانین قانونی/اخلاقی را رعایت کنید.
- برای جلوگیری از بارگذاری اضافی منابع از page.route با الگوهای خاص بهره ببرید و فقط ترافیک مورد نیاز را رهگیری کنید.
- برای تستهای امنیتی یا آزمون پایداری، از route.abort() برای شبیهسازی خطاها استفاده کنید و رفتار اپ را بررسی نمایید.
جمعبندی
ضبط و تحلیل درخواستهای XHR/Fetch با Playwright یک ابزار قدرتمند در وب اسکریپینگ است: هم برای کشف APIها و هم برای بهینهسازی اسکریپها. با ترکیب page.on برای مانیتورینگ و page.route برای کنترل/مسدودسازی میتوانید جریان شبکه را به دقت مدیریت کنید. رعایت نکات async، مدیریت هدرها و استفاده از پراکسی/سیاستهای مناسب، عملکرد و پایداری کارتان را بالا میبرد—حالا وقتش است که یک اسکریپ میسازید و آن را بهبود میدهید.




