كشط الويب والوكلاء

حل أخطاء HTTP 429 و Cloudflare 520 في زواحف ووكلاء الذكاء الاصطناعي

إجابة سريعة: يشير خطأ HTTP 429 إلى تجاوز معدل الطلبات (Rate Limit) لواجهة برمجة التطبيقات أو جدار الحماية، ويتم حله عبر التراجع الأسي العشوائي غير المترابط (Decorrelated Jitter) وبروكسيات سكنية دوارة. بينما ينجم خطأ Cloudflare 520 عن إرسال الخادم الأصلي استجابات مشوهة أو إعادة تعيين TCP RST بسبب الضغط الشديد، ويُعالج بضبط Keep-Alive ونمط قاطع الدائرة (Circuit Breaker).

1. المقدمة: هشاشة زواحف الويب لوكلاء الذكاء الاصطناعي

مع تطور وكلاء الذكاء الاصطناعي المستقلين إلى محركات استدلال متعددة الخطوات (البحث اللحظي، التدقيق المالي، تحليل المنافسين)، لم تعد السرعة هي العائق الأكبر، بل موثوقية الاتصال بالشبكة والوصول إلى أطراف الويب (Edge CDN).

تطلق أنظمة الوكلاء الحديثة آلاف الطلبات في الدقيقة. وبخلاف الزواحف التقليدية (مثل Scrapy أو Googlebot)، يحتاج وكيل الذكاء الاصطناعي إلى:

  1. استرجاع متزامن وبزمن استجابة منخفض لمنع تعطيل حلقة الاستدلال.
  2. تنفيذ جافا سكريبت متقدم لمعالجة تطبيقات الصفحة الواحدة (SPAs).
  3. تحويل نظيف إلى Markdown لتجنب هدر الرموز (Tokens).

ولكن، تفعل أنظمة الحماية الحديثة مثل Cloudflare و DataDome خوارزميات رصد متطورة، مسببة خطأين رئيسيين:

  • HTTP 429 Too Many Requests: تجاوز حدود معدل الطلبات.
  • HTTP 520 Web Server Returned an Unknown Error: فشل الخادم الأصلي في الاستجابة بشكل سليم لبوابة Cloudflare.

2. تشريح الخطأ HTTP 429: حدود المعدل وبصمات TLS

ينشأ الخطأ 429 على ثلاثة مستويات:

  1. المستوى الأول: جدار الحماية الطرفي (WAF): يكتشف عدم التطابق بين بصمة JA4 التابعة لمكتبات بايثون القياسية و User-Agent الخاص بمتصفح Chrome.
  2. المستوى الثاني: بوابة واجهة برمجة التطبيقات (API Gateway): خوارزميات Token Bucket و Leaky Bucket.
  3. المستوى الثالث: خادم التطبيق الأصلي: حماية قواعد البيانات والموارد من الانهيار.

ترويسات هامة للفحص: Retry-After و RateLimit-Limit و RateLimit-Remaining و RateLimit-Reset.


3. فهم خطأ Cloudflare 520

الرمز 520 خاص بـ Cloudflare ويعكس فشل الخادم الأصلي تحت وطأة الزحف:

  1. انهيار العمليات (OOM / SIGSEGV): استنزاف الذاكرة ينهي العمليات ويرسل حزمة TCP RST إلى Cloudflare.
  2. عدم تطابق مهلة الاتصال المستمر (Keep-Alive): تحتفظ Cloudflare بالاتصال لمدة 15 ثانية، فإذا كانت مهلة الخادم 5 ثوانٍ، تفشل الطلبات المتزامنة.
  3. تجاوز حجم الترويسات (أكبر من 16 كيلوبايت): تكرار ملفات تعريف الارتباط المفرطة.
  4. استجابات فارغة (صفر بايت): إنهاء الاتصال دون إرسال بيانات.

4. الحلول الخوارزمية: التراجع الأسي وقواطع الدوائر (Circuit Breakers)

التكرار الساذج للطلبات يسبب مشكلة الهجوم المتزامن (Thundering Herd). الحل الأمثل المثبت من AWS هو التذبذب غير المترابط (Decorrelated Jitter):

# معادلة Decorrelated Jitter:
sleep = min(cap, random.uniform(base, previous_delay * 3.0))

كود بايثون متقدم مع قاطع الدائرة

import asyncio, random, time, aiohttp
from urllib.parse import urlparse

class ResilientAgentCrawler:
    def __init__(self, base_delay=1.0, max_delay=60.0, max_retries=5, threshold=4, cooldown=30.0):
        self.base_delay = base_delay
        self.max_delay = max_delay
        self.max_retries = max_retries
        self.threshold = threshold
        self.cooldown = cooldown
        self.failures = {}
        self.opened_at = {}

    def _is_open(self, domain):
        t = self.opened_at.get(domain)
        if not t: return False
        if time.monotonic() - t > self.cooldown:
            del self.opened_at[domain]
            self.failures[domain] = 0
            return False
        return True

    async def fetch(self, session, url):
        domain = urlparse(url).netloc
        if self._is_open(domain):
            raise RuntimeError(f"قاطع الدائرة مفتوح للنطاق: {domain}")

        delay = self.base_delay
        for attempt in range(1, self.max_retries + 1):
            try:
                async with session.get(url) as resp:
                    if resp.status == 200:
                        self.failures[domain] = 0
                        return await resp.text()
                    elif resp.status == 429:
                        retry_after = resp.headers.get("Retry-After")
                        wait = float(retry_after) if retry_after else random.uniform(self.base_delay, delay * 3.0)
                        delay = min(self.max_delay, wait)
                        await asyncio.sleep(delay)
                    elif resp.status in (520, 502, 503, 504):
                        self.failures[domain] = self.failures.get(domain, 0) + 1
                        if self.failures[domain] >= self.threshold:
                            self.opened_at[domain] = time.monotonic()
                        delay = min(self.max_delay, random.uniform(self.base_delay, delay * 3.0))
                        await asyncio.sleep(delay)
                    else:
                        resp.raise_for_status()
            except Exception as e:
                if attempt == self.max_retries: raise e
                await asyncio.sleep(delay)

5. محاكاة بصمة المتصفح باستخدام curl_cffi

لم يعد تغيير User-Agent كافياً في 2026. استخدم curl_cffi لمحاكاة بصمة JA4 لمتصفح Chrome بدقة:

from curl_cffi.requests import AsyncSession

async def fetch_stealth(url: str):
    async with AsyncSession(impersonate="chrome124") as s:
        res = await s.get(url, timeout=15)
        return res.text

6. استراتيجيات البروكسي وضبط الخادم

  • البروكسيات السكنية الدوارة: تضمن توزيع الطلبات على آلاف العناوين السكنية لتفادي الحظر.
  • بروكسيات الهاتف المحمول (4G/5G بتقنية CGNAT): توفر أعلى معدل نجاح لمشاركة العنوان مع آلاف المستخدمين الحقيقيين.
  • إعدادات Nginx لتفادي خطأ 520:
http {
    keepalive_timeout 75s;
    keepalive_requests 10000;
    proxy_buffer_size 128k;
    proxy_buffers 4 256k;
}

7. الخاتمة

لبناء وكلاء ذكاء اصطناعي قادرين على الزحف دون انقطاع:

  1. طبق خوارزمية Decorrelated Jitter لمنع طفرات التكرار.
  2. استخدم curl_cffi لمحاكاة بصمات المتصفح بدقة.
  3. اضبط مهلة الاتصال المستمر في خادمك الأصلي على 75 ثانية.
  4. فعل نمط قاطع الدائرة (Circuit Breaker) لحماية ميزانية الرموز (Tokens).
→ كل المقالات
0 / 4