الشروحات المعمقة

إدارة وكيل المستخدم User-Agent في سير عمل حلّ CAPTCHA

القاعدة التي تختصر الموضوع كله بسيطة: يجب أن يقدّم سكربتك الهوية نفسها في كل خطوة — سلسلة User-Agent ذاتها في طلب الصفحة، وفي بقية ترويسات الطلب، وفي المهمة التي ترسلها إلى CaptchaAI. التناقض بين هذه المواضع الثلاثة هو ما يحوّل اختباراً بسيطاً إلى سلسلة تحديات متكررة، لا سرعة السكربت ولا عدد الطلبات.

وفي مشاريع الأتمتة العربية تتكرر ثلاث حالات: ترويسة افتراضية من مكتبة requests، أو إصدار متصفح متأخر بسنوات، أو سلسلة تتبدّل داخل الجلسة الواحدة. والعلاج واحد في الحالات الثلاث: ضبط السلسلة وبقية الرؤوس ضبطاً واحداً في Python وSelenium.

ما الذي تقرأه أنظمة التحقق في ترويسة الطلب

سلسلة User-Agent ليست معلومة معزولة؛ إنها ادّعاء عن العميل الذي أرسل الطلب، ويُقارَن بإشارات أخرى تصل معه:

  • الرؤوس المصاحبةAccept وAccept-Language وAccept-Encoding ورؤوس Sec-Fetch-*، يرسلها المتصفح الحقيقي كاملة.
  • تلميحات العميل — رأس sec-ch-ua في متصفحات Chromium يعلن رقم الإصدار نفسه الوارد في السلسلة.
  • ما يراه JavaScript — قيمة navigator.userAgent تطابق ما أُرسل في الترويسة.
  • سياق الجلسة — ملفات تعريف الارتباط وسمعة عنوان IP وترتيب الصفحات.

عندما تتفق هذه الإشارات يأخذ الطلب مساره الطبيعي. وعندما تتناقض — ترويسة تعلن Chrome 125 على Windows بلا أي رأس sec-ch-ua — ترتفع درجة الخطورة المحسوبة لدى reCAPTCHA أو Cloudflare Turnstile، فيظهر اختبار أصعب أو يتكرر أكثر من مرة.

أربع حالات تجعل User-Agent سبباً في تحديات إضافية

الحالة ما يحدث عملياً
ترويسة Python الافتراضية python-requests/2.31 تصنيف فوري كأتمتة غير معلنة
سلسلة Chrome قديمة — الإصدار 90 مثلاً إشارة «إصدار مهجور» ورفع مستوى التدقيق
تبدّل السلسلة داخل الجلسة الواحدة إبطال الجلسة وإعادة التحقق
ترويسة لا تطابق قدرات المتصفح المعلنة فحوصات إضافية على مستوى الصفحة

الحالات الأربع تشترك في سبب واحد: الفجوة بين ما يقوله الطلب عن نفسه وما يفعله. وإغلاقها لا يحتاج أدوات خاصة، بل ثلاث عادات — قائمة سلاسل محدّثة، وجلسة ثابتة، وترويسات كاملة.

ابنِ مجموعة User-Agent حديثة وحافظ على تحديثها

ابدأ بقائمة صغيرة ومضبوطة بدل مئات السلاسل المجمّعة من الإنترنت. خمس أو ست سلاسل حديثة لمتصفحي Chrome وFirefox على Windows وmacOS وLinux تغطي أغلب الحالات وتبقى قابلة للمراجعة يدوياً. والأهم أن تعكس القائمة توزيع الأجهزة الحقيقي لجمهور الموقع الذي تختبره: إن كان زوّاره من مستخدمي سطح المكتب، فلا معنى لإقحام سلاسل هواتف في الدفعة.

import random

# Updated Chrome UAs (keep current)
CHROME_UAS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
]

FIREFOX_UAS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0",
    "Mozilla/5.0 (X11; Linux x86_64; rv:126.0) Gecko/20100101 Firefox/126.0",
]


def get_random_ua(browser="chrome"):
    """Get a random modern user-agent."""
    if browser == "firefox":
        return random.choice(FIREFOX_UAS)
    return random.choice(CHROME_UAS)

احتفظ بالقائمة في ملف إعدادات واحد بدل نسخها داخل كل سكربت، وراجعها كل شهر أو شهرين مع إصدارات المتصفحات الجديدة.

ثبّت السلسلة نفسها طوال الجلسة الواحدة

الخطأ الأكثر تكلفة ليس اختيار سلسلة سيئة، بل تبديل السلسلة داخل الجلسة. الموقع يربط ملف تعريف الارتباط بهوية العميل التي رآها أول مرة؛ فإذا وصل الطلب التالي بالكوكي نفسه وسلسلة مختلفة، تُطلب إعادة التحقق. الحل أن تُحقن الترويسة مرة واحدة على مستوى كائن الجلسة:

import requests


class ConsistentSession:
    """Session that maintains consistent fingerprint."""

    def __init__(self, user_agent=None):
        self.session = requests.Session()
        self.user_agent = user_agent or get_random_ua()

        self.session.headers.update({
            "User-Agent": self.user_agent,
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
            "Accept-Language": "en-US,en;q=0.9",
            "Accept-Encoding": "gzip, deflate, br",
            "Connection": "keep-alive",
            "Upgrade-Insecure-Requests": "1",
        })

    def get(self, url, **kwargs):
        return self.session.get(url, **kwargs)

    def post(self, url, **kwargs):
        return self.session.post(url, **kwargs)


# Same UA for all requests in this session
session = ConsistentSession()
session.get("https://example.com/page1")
session.post("https://example.com/form")

بهذا الشكل ترث كل الطلبات — GET وPOST معاً — الترويسة نفسها تلقائياً. واجعل التدوير على مستوى الجلسة: جلسة جديدة تعني سلسلة جديدة، وداخل الجلسة الواحدة الثبات هو القاعدة.

مرّر السلسلة نفسها إلى CaptchaAI

هنا الجزء الذي يُغفل غالباً. عندما ترسل اختبار reCAPTCHA إلى CaptchaAI عبر in.php، تُنجَز المهمة خارج جلستك، ثم تُرفق أنت الرمز الناتج مع طلبك. فإذا حُلّ الاختبار في سياق سلسلة مختلفة عن التي سيُرسل بها النموذج، يظهر التناقض عند التحقق من الرمز. لذلك يقبل الطلب حقل userAgent: مرّر فيه السلسلة التي تتصفح بها حرفاً بحرف.

def solve_with_matching_ua(api_key, sitekey, pageurl, user_agent):
    """Send matching user-agent to CaptchaAI."""
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": pageurl,
        "userAgent": user_agent,  # Match your browser's UA
        "json": 1,
    }, timeout=30)
    return resp.json()


# Use the same UA everywhere
ua = get_random_ua()
session = ConsistentSession(user_agent=ua)

# Browse with this UA
session.get("https://example.com")

# Solve CAPTCHA with same UA
solve_with_matching_ua("YOUR_API_KEY", "SITEKEY", "https://example.com", ua)

والقاعدة نفسها تنطبق على reCAPTCHA v2 وv3 وعلى Cloudflare Turnstile — وكلها مدعومة في CaptchaAI: سلسلة واحدة للجلسة تُرسل كما هي، وأي تعديل ولو على رقم الإصدار يفقد المطابقة قيمتها.

اضبط User-Agent في Selenium وتحقّق منه فعلياً

مع Selenium لا يكفي تمرير الوسيط user-agent وافتراض أنه طُبّق؛ فبعض الإعدادات — ملف تعريف مخصص، إضافة مثبّتة، أو وضع headless — تعدّل السلسلة بعد الإقلاع. اقرأ navigator.userAgent من داخل المتصفح وتأكد أن ما وصل إلى الصفحة هو ما طلبته:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options


def create_driver_with_ua(user_agent):
    """Create Selenium driver with specific user-agent."""
    options = Options()
    options.add_argument(f"user-agent={user_agent}")
    options.add_argument("--disable-blink-features=AutomationControlled")

    driver = webdriver.Chrome(options=options)

    # Verify UA is set correctly
    actual_ua = driver.execute_script("return navigator.userAgent")
    assert user_agent in actual_ua, f"UA mismatch: {actual_ua}"

    return driver


# Use same UA for browser and API
ua = get_random_ua()
driver = create_driver_with_ua(ua)
# ... solve with captchaai using same ua ...

سطر التأكيد هذا يوفّر ساعات تصحيح: إن فشل فالمشكلة في إعداد المتصفح لا في منطق الحل. وامنح المتصفح والمهمة المرسلة إلى CaptchaAI السلسلة ذاتها.

User-Agent وحده لا يكفي — أكمل بقية الترويسات

المتصفح الحقيقي لا يرسل ترويسة واحدة. والطلب الذي يحمل سلسلة Chrome حديثة ثم يصل بلا Accept-Language وبلا رؤوس Sec-Fetch-* يبدو أغرب من طلب لا يدّعي شيئاً أصلاً. ابنِ الرؤوس في دالة واحدة، واجعل sec-ch-ua يعلن رقم الإصدار نفسه الوارد في السلسلة:

def get_realistic_headers(user_agent=None):
    """Build headers that match a real browser."""
    ua = user_agent or get_random_ua()

    headers = {
        "User-Agent": ua,
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
        "Accept-Language": "en-US,en;q=0.9",
        "Accept-Encoding": "gzip, deflate, br",
        "Connection": "keep-alive",
        "Upgrade-Insecure-Requests": "1",
        "Sec-Fetch-Dest": "document",
        "Sec-Fetch-Mode": "navigate",
        "Sec-Fetch-Site": "none",
        "Sec-Fetch-User": "?1",
        "Cache-Control": "max-age=0",
    }

    # Add Chrome-specific headers
    if "Chrome" in ua:
        headers["sec-ch-ua"] = '"Chromium";v="125", "Google Chrome";v="125", "Not.A/Brand";v="24"'
        headers["sec-ch-ua-mobile"] = "?0"
        headers["sec-ch-ua-platform"] = '"Windows"'

    return headers

انتبه إلى تفصيل يغفله كثيرون: الرأس sec-ch-ua هنا يعلن الإصدار 125، فإذا حدّثت سلاسل Chrome ونسيت هذا السطر، تكون قد صنعت بيدك التناقض الذي تحاول إغلاقه. وتفصيل ثانٍ يخص جمهورنا — Accept-Language: إن كنت تختبر واجهة عربية موجّهة لمستخدمين في السعودية أو مصر، فاجعل قيمة الرأس تعكس اللغة التي يعرضها المتصفح الذي تحاكيه فعلاً.

مثال تطبيقي: فريق QA لمتجر خليجي قبل موسم التخفيضات

تخيّل فريق ضمان جودة في متجر إلكتروني سعودي يستعد لموسم الجمعة البيضاء. يشغّل الفريق اختبارات انحدار ليلية على مسار إتمام الشراء كاملاً، وكل تشغيلة تمر باختبار reCAPTCHA عند خطوة الدفع.

في التصميم الأول كان كل سكربت يولّد سلسلة عشوائية عند بدايته وأحياناً في منتصفه، فجاءت النتائج متذبذبة: تشغيلة تمر وأخرى تتعثر عند الخطوة نفسها. بعد التعديل صار لكل تشغيلة هوية واحدة: سلسلة تُختار مرة واحدة، تُحقن في كائن الجلسة، تُرسل مع حقل userAgent إلى CaptchaAI، ويصاحبها Accept-Language بقيمة عربية تطابق واجهة المتجر. والمكسب الأكبر أن الفشل صار قابلاً للتفسير: إن تعثّرت التشغيلة فالسبب في التطبيق لا في هوية العميل.

وعلى مستوى التكلفة، الفوترة في CaptchaAI تقوم على عدد الـ threads المتزامنة لا على عدد عمليات الحل؛ فخطة BASIC بسعر $15 شهرياً توفّر 5 threads بعمليات حل غير محدودة، وهو حجم يناسب دفعة اختبارات ليلية.

قائمة تحقق قبل تشغيل الدفعة

  • سلسلة واحدة لكل جلسة، تُحقن في كائن الجلسة لا في كل طلب.
  • القائمة محدّثة خلال الشهرين الأخيرين وفي ملف إعدادات واحد.
  • رقم الإصدار في sec-ch-ua مطابق لرقم الإصدار في السلسلة.
  • قيمة Accept-Language تعكس لغة الواجهة التي تختبرها.
  • حقل userAgent في مهمة CaptchaAI يحمل السلسلة نفسها حرفياً.
  • في Selenium: تأكيد على navigator.userAgent بعد إنشاء الجلسة.
  • التدوير يجري بين الجلسات لا داخل الجلسة الواحدة.

اختبر ما تملكه أو ما لديك تصريح صريح باختباره، والتزم بشروط استخدام الموقع وبملف robots.txt.

الأسئلة الشائعة

هل يجب تمرير حقل userAgent في كل مهمة إلى CaptchaAI؟

ليس إلزامياً في كل الأنواع، لكنه مفيد كلما كنت تتحكم بالجلسة التي ستستخدم الرمز. القاعدة العملية: إن كنت تعرف السلسلة التي سيُرسل بها النموذج فمرّرها، وإن لم تكن تعرفها بعد فثبّتها أولاً في كائن الجلسة ثم مرّرها.

أيهما أهم: تدوير User-Agent أم البروكسي؟

لا يعوّض أحدهما الآخر. سمعة عنوان IP إشارة على مستوى الشبكة، والترويسة إشارة على مستوى العميل، وتُقرأ الإشارتان معاً: عشر سلاسل تخرج من عنوان IP واحد نمط واضح، وعنوان IP نظيف يحمل ترويسة python-requests لا يفيد كثيراً. ابدأ بالترويسة لأنها أرخص وأسرع.

لماذا ما زلت أرى تحديات كثيرة رغم أن السلسلة حديثة؟

غالباً لأن بقية الإشارات لم تُحدَّث معها: رؤوس ناقصة، sec-ch-ua بإصدار قديم، ملفات تعريف ارتباط من جلسة سابقة، أو معدل طلبات مرتفع من عنوان واحد. افحص الطلب كاملاً كما يصل إلى الخادم وقارنه بطلب حقيقي من أدوات مطوّري المتصفح.

كم مرة أحدّث قائمة السلاسل؟

كل شهر إلى شهرين مع دورة إصدارات Chrome وFirefox. اربط التحديث بمهمة دورية في التقويم بدل انتظار المشكلة، واحذف أي سلسلة تجاوز عمرها ثلاثة إصدارات رئيسية.

أدلة ذات صلة

ابدأ من حساب CaptchaAI وأرسل أول مهمة حل بترويسة مطابقة لمتصفحك.

التعليقات غير مفعّلة لهذا المقال.