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
| الاستراتيجية | السرعة | الموثوقية | القضية |
|---|---|---|---|
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
| القياس | بسيطة HTTP + Proxy | Browser + Proxy |
|---|---|---|
| السرعة لكل صفحة | 0.5-2 ثوان | 3-15 ثانية |
| Memory per instance | 50 MB | 200-500 MB |
| CPU usage | الحد الأدنى | كبيرة |
| Bandwidth per page | 50-200 KB | 2-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.






