عندما يتوقّف مسار التحقق الإعلاني فجأة عند صفحة ناشر بعينها، يكون السبب في أغلب الأحيان اختبار CAPTCHA ظهر بعد سلسلة زيارات آلية متتابعة. الحل ليس إيقاف الفحص أو تقليل عدد الصفحات، بل جعل كل طلب صفحة قادرًا على حل التحدي حين يظهر، ثم متابعة الفحص وتسجيل ما جرى. بهذه الطريقة يبقى CaptchaAI جزءًا من الجلسة نفسها بدلاً من أن يكون عقبة يتوقف عندها العمل.
لماذا تعترض اختبارات CAPTCHA مسار التحقق الإعلاني؟
التحقق الإعلاني يعني المرور على أعداد كبيرة من صفحات الناشرين للتأكد من موضع كل إعلان، وسلامة السياق المحيط به، وقابلية عرضه فعليًا للمستخدم. هذا النمط من الزيارات المكثفة — عشرات أو مئات الطلبات في وقت قصير، وغالبًا عبر بروكسيات أو متصفحات موحّدة البصمة — يشبه تمامًا ما تبحث عنه أنظمة الحماية لرصد النشاط الآلي. النتيجة أن الصفحة التي يفتحها فريق الجودة يدويًا دون مشكلة قد تعرض تحدي reCAPTCHA أو Cloudflare Turnstile أمام المسار الآلي.
ما الذي يفحصه فريق التحقق الإعلاني؟
الجدول التالي يلخّص العناصر التي يتابعها الفريق ولماذا قد يعترضه CAPTCHA عند كل منها:
| عنصر الفحص | الوصف | لماذا قد يظهر CAPTCHA؟ |
|---|---|---|
| موضع الإعلان | هل يظهر الإعلان داخل الجزء المرئي أو في المكان المتفق عليه؟ | الزيارات الآلية المتكررة تبدو كسلوك روبوتي واضح |
| سلامة العلامة التجارية | هل يظهر الإعلان بجوار محتوى حساس أو غير مناسب؟ | المسح الجماعي لعدد كبير من الصفحات يشبه أنشطة الكشط المكثف |
| قابلية العرض | هل تم تحميل الإعلان وعرضه فعلاً؟ | المتصفحات عديمة الواجهة أو البيئات الموحدة قد تُرصد سريعًا |
| الاستهداف الجغرافي | هل يظهر الإعلان الصحيح في الدولة أو المدينة المطلوبة؟ | استخدام البروكسيات أو تبديل المناطق يرفع احتمالية التحدي |
| مراقبة المنافسين | ما نوع الإعلانات التي يعرضها المنافسون وعلى أي صفحات؟ | ارتفاع عدد الزيارات والاستعلامات قد يفعّل أنظمة الحماية |
كلما اتسع نطاق التحقق، صار التحدي الحقيقي ليس مجرد فتح الصفحة، بل المحافظة على جلسة قابلة للتنبؤ: حل CAPTCHA عند الحاجة، وإعادة إرسال الطلب بالطريقة الصحيحة، وتوثيق ما حدث لكل صفحة جرى فحصها.
بناء مسار يحلّ CAPTCHA ويسجّل كل فحص
النمط العملي بسيط: افتح الصفحة داخل جلسة ثابتة، وابحث عن مفتاح الموقع إن وُجد تحدٍّ، ثم أرسل بيانات التحدي إلى CaptchaAI عبر نقطتَي in.php وres.php، واستخدم الرمز الناتج لإعادة إرسال الطلب. بعد ذلك افحص وسوم الشبكات الإعلانية ومؤشرات سلامة العلامة التجارية على الصفحة. الشيفرة التالية تجمع هذه الخطوات في دالة واحدة قابلة للتكرار:
import os
import json
import re
import time
from datetime import datetime
import requests
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
def solve_captcha(method, params):
params["key"] = API_KEY
params["method"] = method
resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
if not resp.text.startswith("OK|"):
raise Exception(resp.text)
task_id = resp.text.split("|")[1]
for _ in range(60):
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id,
})
if result.text == "CAPCHA_NOT_READY":
continue
if result.text.startswith("OK|"):
return result.text.split("|", 1)[1]
raise Exception(result.text)
raise TimeoutError()
def verify_ad_placement(url, session):
"""Verify ad placement on a publisher page."""
resp = session.get(url)
# Solve CAPTCHA if present
match = re.search(r'data-sitekey=["\']([A-Za-z0-9_-]+)["\']', resp.text)
if match:
token = solve_captcha("userrecaptcha", {
"googlekey": match.group(1),
"pageurl": url,
})
resp = session.post(url, data={"g-recaptcha-response": token})
html = resp.text
# Check for ad elements
result = {
"url": url,
"timestamp": datetime.utcnow().isoformat(),
"ads_found": [],
"brand_safety": True,
"captcha_solved": match is not None,
}
# Detect ad tags
ad_patterns = [
(r'googletag\.pubads', "Google Ad Manager"),
(r'doubleclick\.net', "DFP/DoubleClick"),
(r'ad\.doubleclick', "DoubleClick"),
(r'amazon-adsystem', "Amazon Ads"),
(r'criteo\.com/.*\.js', "Criteo"),
]
for pattern, name in ad_patterns:
if re.search(pattern, html):
result["ads_found"].append(name)
# Brand safety check - flag problematic content
safety_keywords = [
"violence", "hate speech", "explicit",
"gambling", "illegal",
]
page_text = re.sub(r'<[^>]+>', '', html).lower()
for keyword in safety_keywords:
if keyword in page_text:
result["brand_safety"] = False
break
return result
def run_verification(urls, output_file="verification_report.json"):
"""Run ad verification across multiple publisher URLs."""
session = requests.Session()
session.headers["User-Agent"] = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 Chrome/120.0.0.0"
)
results = []
for i, url in enumerate(urls):
try:
result = verify_ad_placement(url, session)
results.append(result)
ads = ", ".join(result["ads_found"]) or "None"
safe = "SAFE" if result["brand_safety"] else "UNSAFE"
print(f" [{i+1}/{len(urls)}] {url}: {ads} [{safe}]")
except Exception as e:
results.append({
"url": url,
"error": str(e),
"timestamp": datetime.utcnow().isoformat(),
})
print(f" [{i+1}/{len(urls)}] {url}: ERROR - {e}")
time.sleep(2)
with open(output_file, "w") as f:
json.dump(results, f, indent=2)
# Summary
total = len(results)
safe = sum(1 for r in results if r.get("brand_safety"))
captchas = sum(1 for r in results if r.get("captcha_solved"))
errors = sum(1 for r in results if "error" in r)
print(f"\n Total: {total} | Safe: {safe} | CAPTCHAs solved: {captchas} | Errors: {errors}")
return results
# Publisher URLs to verify
publisher_urls = [
"https://publisher1.com/article/tech-news",
"https://publisher2.com/sports/latest",
"https://publisher3.com/finance/markets",
]
run_verification(publisher_urls)
النقطة الأهم في هذا التصميم أن نتيجة كل صفحة ليست "نجاح" أو "فشل" فحسب. احفظ ما إذا ظهر تحدٍّ وتم حله، وأنواع الشبكات الإعلانية التي رُصدت مثل Google Ad Manager أو Criteo، وهل ظهرت مؤشرات خطر على سلامة العلامة التجارية. هذا السجل المفصّل هو ما يحوّل الفحص من عملية عابرة إلى دليل موثّق قابل للمراجعة من فريق العمليات أو فريق الامتثال لاحقًا.
التعامل مع الناشرين خلف Cloudflare
يعتمد عدد كبير من الناشرين الكبار على Cloudflare، ما يعني مسارين مختلفين تحتاج إلى فصلهما: تحدي Turnstile الخفيف الذي يظهر ضمن الصفحة، والتحدي الكامل أو صفحة التحقق التي تعيد الرمز 403. الدالة التالية تميّز بين الحالتين وتتعامل مع كل منهما على حدة:
def handle_cloudflare(url, session):
"""Handle Cloudflare-protected publisher pages."""
resp = session.get(url)
if "cf-turnstile" in resp.text:
match = re.search(r'data-sitekey=["\']([^"\']+)', resp.text)
if match:
token = solve_captcha("turnstile", {
"sitekey": match.group(1),
"pageurl": url,
})
return session.post(url, data={
"cf-turnstile-response": token,
})
if resp.status_code == 403 and "cf-browser-verification" in resp.text:
data = solve_captcha("cloudflare_challenge", {
"pageurl": url,
"proxy": "user:pass@proxy:port",
"proxytype": "HTTP",
})
# Parse cf_clearance and use same proxy
return data
return resp
إذا كنت تنفّذ تحققًا جغرافيًا أو تقارن الحملات بين أسواق متعددة، فاحرص على أن يبقى البروكسي المستخدم أثناء التصفح هو نفسه المستخدم عند تمرير التحدي إلى CaptchaAI. أي اختلاف في المنطقة أو في نوع البروكسي بين الخطوتين قد يعيد ظهور التحدي أو يجعل سياق الحل غير متطابق مع سياق الجلسة الفعلية، خصوصًا في صفحات الحماية المشددة.
تخطيط السعة عبر الخطط المعتمدة على الـ Threads
سرعة التحقق الإعلاني لا تتحدد بعدد الصفحات وحده، بل بعدد التحديات التي يمكنك حلّها بالتوازي. يحاسب CaptchaAI على أساس الـ Thread — أي التحدي الواحد قيد المعالجة — مع حلول غير محدودة لكل thread خلال الشهر، دون رسوم لكل عملية حل ودون سقف يومي. لتقدير الخطة المناسبة، ابدأ من ذروة عدد الصفحات التي تفحصها في وقت واحد، وكم منها يعرض CAPTCHA فعلًا.
خذ مثالاً محليًا: وكالة إعلام رقمي في الرياض أو دبي تراقب مواضع إعلانات حملة خليجية عبر عشرات مواقع الناشرين قبل موسم رمضان. إذا كانت تفحص نحو خمسين صفحة بالتوازي وتظهر التحديات على جزء منها، فقد تكفيها خطة ADVANCE ($90 شهريًا، 50 thread). أما عمليات المراقبة الأوسع التي تغطي عدة أسواق في وقت واحد فقد تحتاج إلى PREMIUM ($170 شهريًا، 100 thread) أو أعلى. الميزة أن التكلفة تبقى ثابتة ومتوقعة بصرف النظر عن عدد عمليات الحل داخل كل thread.
ما الذي يمكن حلّه في هذا المسار وما لا يمكن
على صفحات الناشرين ستقابل غالبًا reCAPTCHA v2 وv3، وCloudflare Turnstile وCloudflare Challenge، وأحيانًا اختبارات صورية أو شبكية — وكلها ضمن ما يحلّه CaptchaAI. في المقابل، إذا اعتمد ناشر معيّن على hCaptcha أو FunCaptcha فمن المهم معرفة أنهما غير مدعومين حاليًا، لذا يُفضّل استثناء تلك الصفحات من المسار الآلي أو التعامل معها بمتصفح فعلي بدلاً من افتراض أنها ستُحلّ تلقائيًا. تحديد ما تستطيع أتمتته مسبقًا يجعل تقرير التغطية أدق ويمنع فجوات صامتة في الفحص.
الأسئلة الشائعة
هل التحقق الإعلاني الآلي عبر حل CAPTCHA مسموح؟
نعم طالما تُجري الفحص على مواضع إعلاناتك أو ضمن اتفاق مع الناشرين، ولأغراض مشروعة مثل قياس الأداء وسلامة العلامة التجارية. اجعل المراقبة ضمن نطاق مصرّح به، وسجّل ما تفحصه، وتجنّب الوصول إلى محتوى محمي لا تملكه أو لا تملك إذنًا بفحصه.
كيف أحافظ على سجل قابل للمراجعة لكل صفحة؟
خزّن لكل صفحة الطابع الزمني، ورابطها، وهل ظهر تحدٍّ وتم حله، والشبكات الإعلانية المكتشفة، ونتيجة فحص سلامة العلامة التجارية — تمامًا كما في حقل النتيجة داخل الشيفرة أعلاه. حفظ هذه الحقول في ملف JSON منظم يجعل التقرير جاهزًا لمراجعة فريق العمليات أو الامتثال دون إعادة تشغيل الفحص.
هل يصلح هذا الأسلوب للتحقق من إعلانات الفيديو؟
يناسب هذا المسار الإعلانات الصورية والمدمجة مع المحتوى لأنه يقرأ وسوم الصفحة مباشرة. أما إعلانات الفيديو أو الوحدات الديناميكية المعقدة فتحتاج غالبًا إلى تشغيل فعلي عبر Selenium أو Playwright لالتقاط حالة العرض بدقة، مع استدعاء CaptchaAI عند ظهور تحدٍّ داخل تلك الجلسة.
ماذا أفعل إذا عاد التحدي بالظهور رغم إرسال الرمز؟
الأسباب الشائعة أن البروكسي تغيّر بين الطلب الأول وإعادة الإرسال، أو أن سياق المنطقة لم يعد متطابقًا، أو أن مفتاح الموقع الملتقط غير صحيح. ثبّت البروكسي عبر خطوتَي الحل والإرسال، وتأكد من التقاط sitekey الصحيح، وأعد المحاولة مع مهلة قصيرة قبل اعتبار الصفحة فاشلة.
أدلة ذات صلة
إذا أردت تشغيل التحقق الإعلاني على نطاق أوسع مع سجلات يمكن مراجعتها، فأنشئ مفتاح CaptchaAI وابنِ مسارًا يجمع بين حل CAPTCHA والتسجيل التشغيلي لكل فحص داخل بيئة مصرّح بها.