كيفية استخراج المواقع الثقيلة بـ JavaScript

(سراب جافاسكريب) محتواه بلا رأس Puppeteer, Playwright, and chromedp setup guides with performance optimization and API interception strategies.

كيفية استخراج المواقع الثقيلة بـ JavaScript

The Challenge of JavaScript-Rendered Content

وتعتمد المواقع الشبكية الحديثة بشكل متزايد على جافاسكريبت لإصدار المحتوى. Un-page applications (SPAs) built with React, Vue, or Angular load a minimal HTML shell, then fetch and render data client-side. عندما تقدمين طلب بسيط من شركة (هاتم تي بي) إلى هذه المواقع تحصلين على صفحة فارغة أو غير كاملة لأن المحتوى موجود فقط بعد إعدام (جافاسكريبت)

Scraping JavaScript-heavy websites requires مصفوفة الرأس - محركات بروزر حقيقية تعمل بدون نافذة مرئية قادرة على إعدام جافاسكريبت، وجعل إدارة مكافحة المخدرات، والتفاعل مع عناصر الصفحات. مقترنة بالوكلاء، المصفوفون العاقلة تفتح البيانات من أكثر المواقع دينامية.

هذا الدليل جزء من دليل كامل للدعاوى الإلكترونيةلتجنّب الاكتشاف أثناء استخدام المروجين العاجزين How Anti-Bot Systems Detect Proxies.

متى تحتاج لبائع بلا رأس؟

متى تحتاج لبائع بلا رأس؟
السيناريوSimple HTTPالحشد بلا رأس
الصفحات الثابتة HTMLيعمل بشكل مثاليOverkill
صفحتان من طراز Server-render مع API' 2` الأشغال (تأثّر بصورة مباشرة في سجل الأداء)غير مطلوب
SPA (React, Vue, Angular)الحصول على قذيفة فارغةالمطلوب
التفريغ الزهيد/التحميل الكسوللا يمكن أن تطلقالمطلوب
Content behind login + JSصعوبةالموصى بها
عدد الصفحات التي تفحصها الورقة المشتركة المضادة للدباباتكشف الفشلالمطلوب
تحقق دائماً إذا كان الموقع لديه جهاز تسجيل أو خادم سيصدره قبل الوصول إلى مصفف لا رأس له العديد من مواقع "جافا سكريبت الثقيلة" في الواقع لديها نقاط نهاية لـ "إي بي آي" التي تعود نظيفة "جيون"

Puppeteer + Proxies (Node.js)

ويتحكم الفرسان في الكروم/الكروميوم من الناحية البرنامجية. إنّها أضخم أداة لـ (نودج جايس).

تركيبة أساسية مع بروكسي هات

const puppeteer = require('puppeteer');
async function scrapeWithPuppeteer(url) {
  const browser = await puppeteer.launch({
    headless: 'new',
    args: [
      '--proxy-server=http://gate.proxyhat.com:8080',
      '--no-sandbox',
      '--disable-setuid-sandbox',
      '--disable-dev-shm-usage',
    ],
  });
  const page = await browser.newPage();
  // Authenticate with proxy
  await page.authenticate({
    username: 'USERNAME',
    password: 'PASSWORD',
  });
  // Set realistic viewport and user agent
  await page.setViewport({ width: 1920, height: 1080 });
  await page.setUserAgent(
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
    '(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
  );
  try {
    await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });
    // Wait for specific content to render
    await page.waitForSelector('.product-list', { timeout: 10000 });
    const content = await page.content();
    const data = await page.evaluate(() => {
      return Array.from(document.querySelectorAll('.product-item')).map(el => ({
        name: el.querySelector('.product-name')?.textContent?.trim(),
        price: el.querySelector('.product-price')?.textContent?.trim(),
        url: el.querySelector('a')?.href,
      }));
    });
    return { html: content, data };
  } finally {
    await browser.close();
  }
}
// Usage
const result = await scrapeWithPuppeteer('https://example.com/products');
console.log(`Found ${result.data.length} products`);

التشويش الأمثل المتعدد المراحل

const puppeteer = require('puppeteer');
class PuppeteerScraper {
  constructor(concurrency = 3) {
    this.concurrency = concurrency;
    this.browser = null;
  }
  async init() {
    this.browser = await puppeteer.launch({
      headless: 'new',
      args: [
        '--proxy-server=http://gate.proxyhat.com:8080',
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-dev-shm-usage',
        '--disable-gpu',
        '--disable-extensions',
      ],
    });
  }
  async scrapePage(url) {
    const page = await this.browser.newPage();
    await page.authenticate({ username: 'USERNAME', password: 'PASSWORD' });
    await page.setViewport({ width: 1920, height: 1080 });
    // Block unnecessary resources to speed up loading
    await page.setRequestInterception(true);
    page.on('request', (req) => {
      const type = req.resourceType();
      if (['image', 'stylesheet', 'font', 'media'].includes(type)) {
        req.abort();
      } else {
        req.continue();
      }
    });
    try {
      await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
      const content = await page.content();
      return { url, status: 'success', html: content };
    } catch (err) {
      return { url, status: 'error', error: err.message };
    } finally {
      await page.close();
    }
  }
  async scrapeMany(urls) {
    const results = [];
    for (let i = 0; i < urls.length; i += this.concurrency) {
      const batch = urls.slice(i, i + this.concurrency);
      const batchResults = await Promise.all(
        batch.map(url => this.scrapePage(url))
      );
      results.push(...batchResults);
      console.log(`Progress: ${results.length}/${urls.length}`);
    }
    return results;
  }
  async close() {
    if (this.browser) await this.browser.close();
  }
}
// Usage
const scraper = new PuppeteerScraper(3);
await scraper.init();
const results = await scraper.scrapeMany(urls);
await scraper.close();

بلايرايت + Proxies (Python)

(بلايرايت) بديل جديد يدعم (كروم) و(فايرفوكس) و(ويكيت) (بيتون آي) نظيف ومناسب للخردة

الهيكل الأساسي

from playwright.sync_api import sync_playwright
def scrape_with_playwright(url: str) -> dict:
    """Scrape a JavaScript-heavy page using Playwright with ProxyHat proxy."""
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=True,
            proxy={
                "server": "http://gate.proxyhat.com:8080",
                "username": "USERNAME",
                "password": "PASSWORD",
            }
        )
        context = browser.new_context(
            viewport={"width": 1920, "height": 1080},
            user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                       "AppleWebKit/537.36 (KHTML, like Gecko) "
                       "Chrome/120.0.0.0 Safari/537.36",
        )
        page = context.new_page()
        try:
            page.goto(url, wait_until="networkidle", timeout=60000)
            # Wait for dynamic content
            page.wait_for_selector(".product-list", timeout=10000)
            # Extract data using page.evaluate
            products = page.evaluate("""() => {
                return Array.from(document.querySelectorAll('.product-item')).map(el => ({
                    name: el.querySelector('.product-name')?.textContent?.trim(),
                    price: el.querySelector('.product-price')?.textContent?.trim(),
                    url: el.querySelector('a')?.href,
                }));
            }""")
            return {"url": url, "products": products, "html": page.content()}
        finally:
            browser.close()

Async Playwright for Parallel Scraping

import asyncio
from playwright.async_api import async_playwright
async def scrape_batch(urls: list[str], concurrency: int = 3) -> list[dict]:
    """Scrape multiple JS-heavy pages in parallel using Playwright."""
    results = []
    async with async_playwright() as p:
        browser = await p.chromium.launch(
            headless=True,
            proxy={
                "server": "http://gate.proxyhat.com:8080",
                "username": "USERNAME",
                "password": "PASSWORD",
            }
        )
        semaphore = asyncio.Semaphore(concurrency)
        async def scrape_one(url: str) -> dict:
            async with semaphore:
                context = await browser.new_context(
                    viewport={"width": 1920, "height": 1080},
                )
                page = await context.new_page()
                # Block heavy resources
                await page.route("**/*.{png,jpg,jpeg,gif,svg,css,woff,woff2}",
                                 lambda route: route.abort())
                try:
                    await page.goto(url, wait_until="networkidle", timeout=30000)
                    html = await page.content()
                    return {"url": url, "status": "success", "html": html}
                except Exception as e:
                    return {"url": url, "status": "error", "error": str(e)}
                finally:
                    await context.close()
        tasks = [scrape_one(url) for url in urls]
        results = await asyncio.gather(*tasks)
        await browser.close()
    return results
# Usage
urls = [f"https://example.com/product/{i}" for i in range(50)]
results = asyncio.run(scrape_batch(urls, concurrency=5))

"استخدم "كروميدب" مع "بروكسيس

package main
import (
    "context"
    "fmt"
    "log"
    "time"
    "github.com/chromedp/chromedp"
)
func scrapeJSPage(targetURL string) (string, error) {
    // Configure proxy
    opts := append(chromedp.DefaultExecAllocatorOptions[:],
        chromedp.ProxyServer("http://gate.proxyhat.com:8080"),
        chromedp.Flag("headless", true),
        chromedp.Flag("disable-gpu", true),
        chromedp.Flag("no-sandbox", true),
        chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) "+
            "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"),
    )
    allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
    defer cancel()
    ctx, cancel := chromedp.NewContext(allocCtx)
    defer cancel()
    ctx, cancel = context.WithTimeout(ctx, 60*time.Second)
    defer cancel()
    var htmlContent string
    err := chromedp.Run(ctx,
        chromedp.Navigate(targetURL),
        chromedp.WaitVisible(".product-list", chromedp.ByQuery),
        chromedp.OuterHTML("html", &htmlContent),
    )
    if err != nil {
        return "", fmt.Errorf("scrape failed: %w", err)
    }
    return htmlContent, nil
}
func main() {
    html, err := scrapeJSPage("https://example.com/products")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Got %d bytes of rendered HTML\n", len(html))
}

ألف - استراتيجيات تحسين الأداء

المصفوفات العديمة الرأس أبطأ بـ1050x من الطلبات البسيطة لـ HTTP وهنا توجد استراتيجيات للتقليل إلى أدنى حد من فجوة الأداء:

1 - الموارد غير الضرورية

ولا يلزم الحصول على صور، وخدمات الدعم المركزية، وملفات إعلامية لاستخلاص البيانات. غلقها بسرعة كبيرة فوق عدد الصفحات

# Playwright resource blocking
async def fast_scrape(page, url):
    # Block images, CSS, fonts, media
    await page.route("**/*.{png,jpg,jpeg,gif,svg,css,woff,woff2,mp4,webm}",
                     lambda route: route.abort())
    # Also block tracking scripts
    await page.route("**/*google-analytics*", lambda route: route.abort())
    await page.route("**/*facebook*", lambda route: route.abort())
    await page.goto(url, wait_until="domcontentloaded")  # Faster than networkidle
    return await page.content()

2. Use the Right Wait Strategy

2. Use the Right Wait Strategy
الاستراتيجيةالسرعةالموثوقيةالقضية
domcontentloadedبسرعةقد تفوت بيانات (أسينك)الصفحات التي تتضمن بيانات خطية
loadمتوسطةجيدمعظم الصفحات
networkidleبطيئةأعلىمبيدات الآفات الثقيلة، لا نهاية لها
اختيار محددالفرقأعلىعندما تعرف عنصر الهدف

3. Reuse Browser Instances

إطلاق مروحية يستغرق 1-3 ثانية. For batch scraping, launch once and create new pages/contexts for each URL:

from playwright.sync_api import sync_playwright
class BrowserPool:
    """Reusable browser pool for efficient headless scraping."""
    def __init__(self, pool_size: int = 3):
        self.pool_size = pool_size
        self.playwright = None
        self.browsers = []
    def start(self):
        self.playwright = sync_playwright().start()
        for _ in range(self.pool_size):
            browser = self.playwright.chromium.launch(
                headless=True,
                proxy={
                    "server": "http://gate.proxyhat.com:8080",
                    "username": "USERNAME",
                    "password": "PASSWORD",
                }
            )
            self.browsers.append(browser)
    def get_browser(self, index: int):
        return self.browsers[index % self.pool_size]
    def stop(self):
        for browser in self.browsers:
            browser.close()
        self.playwright.stop()
# Usage
pool = BrowserPool(pool_size=3)
pool.start()
for i, url in enumerate(urls):
    browser = pool.get_browser(i)
    context = browser.new_context()
    page = context.new_page()
    page.goto(url, wait_until="networkidle")
    html = page.content()
    context.close()
pool.stop()

4. Intercept API calls instead of Parsing DOM

Many SPAs fetch data from APIs. إعترضي تلك المكالمات مباشرة - تحصلين على نظيف من (جون) دون أن تقطعي الـ (إتش تي إل)

const puppeteer = require('puppeteer');
async function interceptAPIData(url) {
  const browser = await puppeteer.launch({
    headless: 'new',
    args: ['--proxy-server=http://gate.proxyhat.com:8080'],
  });
  const page = await browser.newPage();
  await page.authenticate({ username: 'USERNAME', password: 'PASSWORD' });
  const apiResponses = [];
  // Intercept XHR/fetch responses
  page.on('response', async (response) => {
    const url = response.url();
    if (url.includes('/api/') || url.includes('/graphql')) {
      try {
        const json = await response.json();
        apiResponses.push({ url, data: json });
      } catch {
        // Not JSON, skip
      }
    }
  });
  await page.goto(url, { waitUntil: 'networkidle2' });
  await browser.close();
  return apiResponses;
}
// Get clean API data instead of scraping DOM
const data = await interceptAPIData('https://example.com/products');
console.log(`Intercepted ${data.length} API calls`);

Huless Browser vs HTTP Comparison

Huless Browser vs HTTP Comparison
القياسبسيطة HTTP + ProxyBrowser + Proxy
السرعة لكل صفحة0.5-2 ثوان3-15 ثانية
Memory per instance50 MB200-500 MB
CPU usageالحد الأدنىكبيرة
Bandwidth per page50-200 KB2-10 MB (مع الموارد)
JavaScript renderingلاكاملة
جواز سفر مضاد للدباباتLimitedأفضل (مصفف حقيقي)
صفحات متزامنة100+3-10 لكل آلة

أفضل الممارسات

  • دائماً جربي برنامج (هاتي بي) أولاً تحقق من النقاط النهائية للرابطة، أو المحتوى المحتوي على الخادم، أو أن (جوسون) قد أدمج في نظام HTML قبل استخدام مصفف لا رأس له.
  • حجب الموارد غير الضرورية. وتضيف الصور، وخدمات الدعم المركزية، والرسومات وقتا للشحن دون توفير البيانات.
  • استخدموا مختارين محددين للانتظار networkidle آمن لكن بطيء انتظر العنصر المحدد الذي تحتاجه
  • إعادة إستعمال حالات المصفح. الإطلاق مرة واحدة، خلق سياقات جديدة لكل صفحة.
  • اعتراض المكالمات Many SPAs load data via APIs - intercept the JSON directly.
  • الاكتفاء المصفوفات العديمة الرأس كثيفة الذاكرة. 3-5 صفحات متزامنة لكل فئة GB of RAM هي قاعدة جيدة.
  • استعملوا العملاء المقيمين وكيلات النيابة توفر أعلى درجات الثقة، مما يقلل من الكشف عند تشغيل المروجين الذين لا رؤوس لهم.

من أجل التعامل مع (كابتيشا) التي يلتقي بها مصفوفون بلا رأس معالجة CAPTCHAs عندما تهتزمن أجل رفع مستوى الخردة الرأسية How to Scale Scraping Infrastructure.

ابدأ مع Python SDK.. Node SDKأو Go SDK من أجل الإدماج المحترف ProxyHat for Web Scraping.

الأسئلة المتكررة

هل أحتاج دائماً إلى مصفف غير رأسى لمواقع (جافاسكريبت)؟

لا Many JavaScript-heavy sites load data from API endpoints. تحققي من مسلسل شبكة (بوزر) الخاص بطلبات (إكس إتش آر) و(إتش دي) إذا كانت البيانات تأتي من مكتب التحقيقات، يمكنكِ أن تتصلي بـ (إي بي آي) مباشرة مع طلبات بسيطة من شركة (إتش تي بي)

مبتدئ أو بلايرايت - الذي هو أفضل للخردة؟

ويوصى عموماً بمشاريع جديدة. It supports multiple browser motors (Chromium, Firefox, WebKit), has better auto-waiting, native async support in Python, and built-in proxy formation. الجرو أكثر نضوجاً وله نظام إيكولوجي أكبر إذا كنت في عالم النودج

كم عدد صفحات المصفوفين التي لا رأس لها يمكنني تشغيلها في نفس الوقت؟

وتستهلك كل صفحة 200-500 ميغابايت من RAM. وعلى آلة تحمل 8 صواريخ من طراز GB RAM، فإن 3-10 صفحات متزامنة واقعية. Use resource blocking (images, CSS) to reduce memory. ولزيادة الاتّفاق، يُوزّع عبر آلات متعددة باستخدام بنية قائمة على الاستواء.

لماذا تستخدم المحترفين مع المصفوفين؟

حتى مع مهرّب حقيقي، الطلبات المتكررة من نفس IP يتم إيقافها. العملاء يتناوبون معدّل الصوت لذا يبدو أن كل صفحة تأتي من مستعمل مختلف وتوفر شركات المحترفين المقيمة من خلال شركة بروكسي هات أعلى درجات الثقة، وتخفض إلى أدنى حد الكتل، وشركة CAPHAs.

¿Listo para empezar?

Accede a más de 50M de IPs residenciales en más de 148 países con filtrado impulsado por IA.

Ver preciosProxies residenciales
← Volver al Blog