مراقبة أسعار المزادات وسجل العروض بشكل متواصل تعني إرسال طلبات متكررة إلى الموقع، وهذا تحديداً ما يستفزّ reCAPTCHA v2 على معظم منصات المزادات. الحلّ مباشر: اكتشِف ظهور الاختبار داخل استجابة الصفحة، أرسل مفتاح الموقع إلى CaptchaAI، ثم احقن التوكن الناتج في الطلب التالي لتستأنف المراقبة دون انقطاع. يوضّح هذا الدليل متى تظهر اختبارات CAPTCHA أثناء المراقبة، وكيف تبني مراقباً موثوقاً بلغة Python ونظام تنبيه أسعار بلغة JavaScript، مع ضبط وتيرة الفحص لتقليل ظهور الاختبار من الأساس.
أين تظهر اختبارات CAPTCHA على منصات المزادات
اختبار CAPTCHA ليس عشوائياً؛ فهو يظهر عند الأنماط التي تشبه سلوك الروبوت: عمليات بحث متسارعة، وتحميل متكرر لصفحة القائمة نفسها، وتحديثات متلاحقة لصفحة السعر. معرفة نقاط الظهور مسبقاً تساعدك على توزيع الطلبات وتقليل الاحتكاك.
| العمل | نوع التحقق | نمط الظهور |
|---|---|---|
| البحث في القوائم أو التصفح | reCAPTCHA v2 | عمليات بحث تسلسلية سريعة |
| عرض تفاصيل القائمة | reCAPTCHA v2 | حجم طلبات مرتفع من نفس عنوان IP |
| فحص سجل العروض | reCAPTCHA v2 | تحميل متكرر لصفحة التفاصيل |
| تصفح الفئة | Cloudflare Turnstile | سرعة تنقّل تشبه سلوك الروبوت |
| صفحات تنبيه الأسعار | reCAPTCHA v2 | تحديثات متكررة |
معظم النقاط تعتمد على reCAPTCHA v2، بينما قد تفرض بعض المنصات Cloudflare Turnstile على مستوى تصفح الفئات. كلا النوعين مدعوم في CaptchaAI، وسير العمل نفسه ينطبق عليهما مع اختلاف حقل التوكن فقط.
بناء مراقب المزادات مع حل reCAPTCHA v2 في Python
الفكرة الأساسية أن تُبقي جلسة واحدة حيّة، وتفحص كل استجابة بحثاً عن اختبار CAPTCHA. فإن ظهر الاختبار، تستخرج مفتاح الموقع، ترسله إلى CaptchaAI عبر in.php، تستطلع النتيجة من res.php، ثم تعيد إرسال الطلب مرفقاً بالحقل g-recaptcha-response. الفئة التالية تجمع البحث في القوائم، وقراءة تفاصيل عرض واحد، وتتبّع تغيّرات الأسعار عبر عدة قوائم في حلقة واحدة.
import requests
import time
import re
from datetime import datetime
class AuctionMonitor:
def __init__(self, api_key):
self.api_key = api_key
self.session = requests.Session()
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})
def search_listings(self, auction_url, query, category=None):
"""Search auction listings, solving CAPTCHAs when triggered."""
params = {"q": query}
if category:
params["category"] = category
response = self.session.get(
f"{auction_url}/search", params=params
)
if self._has_captcha(response.text):
site_key = self._extract_site_key(response.text)
token = self._solve_recaptcha(site_key, f"{auction_url}/search")
response = self.session.post(
f"{auction_url}/search",
data={**params, "g-recaptcha-response": token}
)
return self._parse_listings(response.text)
def monitor_listing(self, auction_url, listing_id):
"""Get current bid and listing details."""
url = f"{auction_url}/item/{listing_id}"
response = self.session.get(url)
if self._has_captcha(response.text):
site_key = self._extract_site_key(response.text)
token = self._solve_recaptcha(site_key, url)
response = self.session.post(url, data={
"g-recaptcha-response": token
})
return self._parse_listing_detail(response.text)
def track_bids(self, auction_url, listing_ids, interval=60):
"""Track bid changes across multiple listings."""
history = {lid: [] for lid in listing_ids}
while True:
for listing_id in listing_ids:
try:
detail = self.monitor_listing(auction_url, listing_id)
previous = history[listing_id]
if previous and detail["current_bid"] != previous[-1]["current_bid"]:
print(f"Bid change on {listing_id}: "
f"${previous[-1]['current_bid']} → ${detail['current_bid']}")
history[listing_id].append(detail)
except Exception as e:
print(f"Error checking {listing_id}: {e}")
time.sleep(interval)
def _has_captcha(self, html):
return "g-recaptcha" in html or "recaptcha" in html.lower()
def _extract_site_key(self, html):
match = re.search(r'data-sitekey="([^"]+)"', html)
if match:
return match.group(1)
match = re.search(r"sitekey['\"]?\s*[:=]\s*['\"]([^'\"]+)", html)
if match:
return match.group(1)
raise ValueError("Could not find reCAPTCHA site key")
def _solve_recaptcha(self, site_key, page_url):
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": self.api_key,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url,
"json": 1
})
task_id = resp.json()["request"]
for _ in range(60):
time.sleep(3)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": self.api_key,
"action": "get",
"id": task_id,
"json": 1
})
data = result.json()
if data["status"] == 1:
return data["request"]
raise TimeoutError("reCAPTCHA solve timed out")
def _parse_listings(self, html):
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
def text_or_none(node):
return node.text.strip() if node and node.text else None
def attr_or_none(node, attr):
return node.get(attr) if node else None
listings = []
for item in soup.select(".listing-item, .auction-item"):
listings.append({
"title": text_or_none(item.select_one(".title")),
"current_bid": text_or_none(item.select_one(".price, .bid")),
"time_left": text_or_none(item.select_one(".time-left")),
"url": attr_or_none(item.select_one("a"), "href")
})
return listings
def _parse_listing_detail(self, html):
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
def text_or_none(node):
return node.text.strip() if node and node.text else None
return {
"title": text_or_none(soup.select_one("h1, .item-title")),
"current_bid": text_or_none(soup.select_one(".current-bid, .price")),
"bid_count": text_or_none(soup.select_one(".bid-count")),
"time_left": text_or_none(soup.select_one(".time-remaining")),
"checked_at": datetime.now().isoformat()
}
# Usage
monitor = AuctionMonitor("YOUR_API_KEY")
listings = monitor.search_listings(
"https://auctions.example.com",
"vintage electronics",
category="collectibles"
)
ملاحظة مهمة: احقن التوكن فور الحصول عليه؛ فرمز reCAPTCHA v2 قصير العمر، وأي تأخير بين استطلاع النتيجة وإعادة الإرسال قد يجعله منتهي الصلاحية عند وصوله إلى الخادم.
نظام تنبيه الأسعار في JavaScript
عندما يكون هدفك تنبيهاً عند اقتراب السعر من سقف محدد بدلاً من جمع القوائم كاملة، يناسبك متتبّع أخفّ يحتفظ بقائمة مراقبة ويطلق تنبيهاً عند بلوغ 90% من الحد الأقصى. المنطق نفسه: افحص الصفحة، وإن ظهر اختبار reCAPTCHA v2 مرّر مفتاح الموقع إلى CaptchaAI ثم أعد الطلب بالتوكن.
class AuctionTracker {
constructor(apiKey) {
this.apiKey = apiKey;
this.watchList = new Map();
}
addWatch(listingId, url, maxPrice) {
this.watchList.set(listingId, { url, maxPrice, history: [] });
}
async checkAll() {
const alerts = [];
for (const [id, watch] of this.watchList) {
try {
const detail = await this.fetchListing(watch.url);
watch.history.push(detail);
const price = parseFloat(detail.currentBid.replace(/[^0-9.]/g, ''));
if (price >= watch.maxPrice * 0.9) {
alerts.push({
listing: id,
price,
threshold: watch.maxPrice,
message: `Price approaching limit: $${price} / $${watch.maxPrice}`
});
}
} catch (error) {
alerts.push({ listing: id, error: error.message });
}
}
return alerts;
}
async fetchListing(url) {
const response = await fetch(url);
const html = await response.text();
if (html.includes('g-recaptcha')) {
return this.solveAndFetch(url, html);
}
return this.parseDetail(html);
}
async solveAndFetch(url, html) {
const siteKeyMatch = html.match(/data-sitekey="([^"]+)"/);
if (!siteKeyMatch) throw new Error('No reCAPTCHA site key found');
const submitResp = await fetch('https://ocr.captchaai.com/in.php', {
method: 'POST',
body: new URLSearchParams({
key: this.apiKey,
method: 'userrecaptcha',
googlekey: siteKeyMatch[1],
pageurl: url,
json: '1'
})
});
const { request: taskId } = await submitResp.json();
for (let i = 0; i < 60; i++) {
await new Promise(r => setTimeout(r, 3000));
const result = await fetch(
`https://ocr.captchaai.com/res.php?key=${this.apiKey}&action=get&id=${taskId}&json=1`
);
const data = await result.json();
if (data.status === 1) {
// Resubmit with token
const response = await fetch(url, {
method: 'POST',
body: new URLSearchParams({ 'g-recaptcha-response': data.request })
});
return this.parseDetail(await response.text());
}
}
throw new Error('reCAPTCHA solve timed out');
}
parseDetail(html) {
// Parse auction listing details from HTML
return {
currentBid: html.match(/current.?bid[^>]*>([^<]+)/i)?.[1]?.trim(),
bidCount: html.match(/(\d+)\s*bids?/i)?.[1],
timeLeft: html.match(/time.?(?:left|remaining)[^>]*>([^<]+)/i)?.[1]?.trim(),
checkedAt: new Date().toISOString()
};
}
}
// Usage
const tracker = new AuctionTracker('YOUR_API_KEY');
tracker.addWatch('item-123', 'https://auctions.example.com/item/123', 500);
tracker.addWatch('item-456', 'https://auctions.example.com/item/456', 200);
const alerts = await tracker.checkAll();
اختيار وتيرة الفحص المناسبة
كلما زاد تكرار الفحص ارتفع معدل ظهور اختبار CAPTCHA. اضبط الفاصل الزمني حسب حساسية المهمة: مزايدة اللحظة الأخيرة تحتاج فحصاً كثيفاً مع بروكسيات، بينما تكفي فترات أطول لأبحاث الأسعار طويلة المدى.
| تكرار الفحص | حالة الاستخدام | معدل CAPTCHA المتوقع |
|---|---|---|
| كل 30 ثانية | مزايدة اللحظة الأخيرة | مرتفع — استخدم البروكسيات |
| كل 5 دقائق | تتبّع مزاد نشط | معتدل |
| كل 15 دقيقة | مراقبة قائمة المتابعة | منخفض |
| كل ساعة | أبحاث الأسعار طويلة المدى | الحد الأدنى |
تقليل معدل ظهور اختبار CAPTCHA
أفضل اختبار CAPTCHA هو الذي لا يظهر أصلاً. الحفاظ على حالة الجلسة، وتوزيع الطلبات عبر عناوين IP، وعشوائية الفواصل الزمنية، كلها تخفض معدل الظهور قبل أن تحتاج إلى الحل.
| التقنية | الأثر |
|---|---|
| إعادة استخدام ملفات تعريف الارتباط للجلسة | يحافظ على حالة المصادقة |
| تدوير الوكلاء السكنية | يوزّع الطلبات عبر عناوين IP |
| عشوائية الفواصل بين الطلبات | يتجنّب أنماط الفحص الدوري المنتظمة |
| استخدام حسابات موثّقة | يخفض عتبة تشغيل اختبار CAPTCHA |
كم عدد الـ threads الذي تحتاجه للمراقبة المتزامنة
تعتمد خطط CaptchaAI على عدد الـ threads المتزامنة، وكل thread يعالج اختباراً واحداً في كل مرة مع عدد غير محدود من عمليات الحل خلال الشهر — أي أن التكلفة تُحسب على التزامن لا على كل عملية حل. اختر السعة بحسب عدد المزادات التي تتابعها في وقت واحد.
على سبيل المثال، فريق في الخليج يراقب بضع قوائم لمزادات السيارات كل خمس دقائق تكفيه خطة BASIC ($15 شهرياً، 5 threads). أما من يتتبّع عشرات المزادات في دقائقها الأخيرة بالتوازي عبر عدة منصات إقليمية، فيستفيد من خطة ADVANCE ($90 شهرياً، 50 thread) أو أعلى لتفادي اصطفاف الطلبات في قائمة انتظار. ابدأ من الخطة الأصغر، وراقب زمن الاستجابة تحت الحمل الفعلي قبل الترقية.
معالجة الأخطاء الشائعة
| المشكلة | السبب | الإجراء |
|---|---|---|
| ظهور CAPTCHA عند كل بحث | عدم الحفاظ على الجلسة أو تصنيف IP عالي الخطورة | أعد استخدام requests.Session() وبدّل البروكسي وارفع التأخير |
| رفض توكن reCAPTCHA رغم حلّه | انتهت صلاحية الرمز قصير العمر قبل الإرسال | احقن التوكن فور الحصول عليه مباشرة قبل الإرسال |
| صفحة القوائم تُظهر 0 نتيجة | اختبار CAPTCHA خفيّ صفّى النتائج بصمت | افحص وجود عناصر reCAPTCHA مخفية في HTML |
| حظر IP بعد عدد كبير من الاختبارات | تجاوز حدّ معدل الطلبات | دوّر البروكسيات وارفع الفواصل الزمنية بين الطلبات |
أسئلة شائعة
هل مراقبة مواقع المزادات وحل اختبار CAPTCHA أمر مسموح؟
المراقبة الموثوقة تكون ضمن بيئة تملكها أو صرّح لك أصحابها باختبارها، وضمن شروط استخدام المنصة. استخدم هذا المسار لمراقبة أسعارك وقوائمك أو لأغراض اختبار الجودة، لا لجمع بيانات محمية خلف تسجيل دخول لا تملكه.
ما الفرق بين التعامل مع reCAPTCHA v2 وCloudflare Turnstile هنا؟
المنطق واحد: اكتشِف الاختبار، أرسله إلى CaptchaAI، ثم احقن التوكن. الفرق في حقل النتيجة فقط — reCAPTCHA v2 يستخدم g-recaptcha-response، بينما Turnstile يستخدم cf-turnstile-response. كلاهما مدعوم بشكل عام في CaptchaAI.
كم عدد الـ threads الذي أحتاجه لمتابعة عدة مزادات في آن واحد؟
يعتمد على عدد الاختبارات المتزامنة لا على إجمالي القوائم. متابعة خفيفة كل بضع دقائق تكفيها خطة BASIC ($15 شهرياً، 5 threads)، بينما تحتاج المراقبة الكثيفة في الدقائق الأخيرة إلى ADVANCE ($90 شهرياً، 50 thread) أو أعلى.
لماذا يُرفض التوكن أحياناً رغم أن الحل نجح؟
رمز reCAPTCHA v2 قصير العمر. إذا مرّ وقت طويل بين استطلاع النتيجة وإعادة إرسال الطلب، ينتهي الرمز قبل أن يصل. احرص على حقنه فوراً، وتجنّب إدراج خطوات بطيئة بين الحصول على التوكن واستخدامه.
خطوات تالية
- البدء السريع مع CaptchaAI: حلّ أول اختبار في خمس دقائق
- حلّ reCAPTCHA v2 عبر الـ API خطوة بخطوة
- حلّ Cloudflare Turnstile عبر واجهة API
- حلّ GeeTest v3 عبر الـ API