استخراج بيانات قوائم أعمال Yelp ومراجعاتها هو مهملة شائعة لمطوّري قواعد بيانات الأنشطة التجارية المحلية وباحثي آراء المستهلكين. لكن Yelp يُطبّق دفاعات متقدمة مضادة للبوتات تجعل الوصول الآلي تحديًا حقيقيًا. هذا الدليل يشرح كيفية الوصول إلى البيانات العامة بشكل مشروع، ويتعمّق في البنية التقنية، ويقدّم أمثلة كود قابلة للتشغيل باستخدام بروكسي ProxyHat السكني.
تنبيه قانوني: هذه المقالة تغطّي الوصول إلى البيانات العامة فقط. عليك احترام شروط خدمة Yelp والقوانين المعمول بها، بما فيها قانون الاحتيال وإساءة استخدام الكمبيوتر (CFAA) في الولايات المتحدة واللائحة العامة لحماية البيانات (GDPR) في الاتحاد الأوروبي. لا تتجاوز أي إجراءات تقنية أو تسحب محتوى يتطلب تسجيل دخول. استخدم واجهة Yelp الرسمية (Fusion API) حيثما تغطّي حاجتك.
ما البيانات العامة المتاحة على Yelp دون تسجيل دخول؟
صفحات الأعمال في Yelp على مسار /biz/<slug> متاحة للعامة دون تسجيل دخول. كل صفحة تعرض اسم النشاط، التقييم النجمي (1–5)، الفئات، ساعات العمل، الموقع الجغرافي، رقم الهاتف (إن وُجد)، ومقتطفات من المراجعات. هذه البيانات كافية لبناء قاعدة بيانات أساسية للأنشطة المحلية.
المراجعات الكاملة متاحة أيضًا في الصفحة العامة، لكن Yelp يعرض عددًا محدودًا منها في عرض HTML الأولي (عادةً 3 مراجعات بارزة)، بينما بقية المراجعات تُحمّل ديناميكيًا عبر نقطة نهاية review_feed التي تُعيد JSON. هذا يعني أنك تحتاج إما إلى محاكاة طلبات AJAX أو إلى استخراج كائن __INITIAL_STATE__ المضمّن في HTML.
بالمقارنة، واجهة Yelp Fusion API الرسمية توفر وصولًا منظّمًا لبيانات الأعمال، لكنها تفرض حدًا أقصى يبلغ 3 مراجعات لكل نشاط تجاري، وحدًا يوميًا على الطلبات يعتمد على نوع الحساب. هذا القيد يدفع بعض المطورين إلى استخراج البيانات من الواجهة الأمامية للحصول على مجموعة مراجعات أكبر.
| المصدر | عدد المراجعات | يتطلب تسجيل دخول | معدّل الطلبات | البيانات المتاحة |
|---|---|---|---|---|
| Fusion API الرسمي | 3 مراجعات كحد أقصى | نعم (مفتاح API) | حد يومي حسب الخطة | اسم، تقييم، عنوان، فئات، مراجعات (3) |
| صفحة /biz/ العامة | كل المراجعات (عبر ترقيم الصفحات) | لا | غير موثّق، يخضع لمضاد البوتات | كل ما يظهر للزائر العادي |
| نقطة review_feed | كل المراجعات (JSON) | لا | غير موثّق | مراجعات كاملة + بيانات وصفية |
السياق التقني: لماذا يصعب استخراج Yelp؟
Yelp لا يقدّم صفحات ثابتة بسيطة. الموقع يُدار بواسطة بنية تطبيق ويب معقدة تعتمد على إطار عمل JavaScript لتقديم الحالة الأولية، ثم تُحمّل المراجعات الإضافية عبر طلبات AJAX. هذا يعني أن أدوات الاستخراج البسيطة مثل requests.get() لن تحصل إلا على جزء من الصورة ما لم تُحلّل كائن الحالة المضمّن.
إضافة إلى ذلك، يستخدم Yelp طبقة حماية مضادة للبوتات تُعرف باسم PerimeterX (التي أصبحت الآن جزءًا من HUMAN Security). هذه الطبقة تُحدّد الحركة الآلية من خلال عدة إشارات:
- ملف تعريف TLS: فحص بصمة TLS للعميل (مثل JA3/JA4) لمطابقتها مع المتصفحات الحقيقية. مكتبات مثل Python
requestsلها بصمة TLS مختلفة عن Chrome أو Firefox. - ملف تعريف المتصفح: فحص User-Agent، الترويسات، وترتيبها، ووجود ميزات مثل WebGL وCanvas.
- ملف تعريف الـ IP: تقييم سمعة عنوان IP. عناوين مراكز البيانات تُعطى درجة سمعة منخفضة، بينما عناوين ISP السكنية تحصل على درجة أعلى.
- تحدّي المستشعر (Sensor Challenge): إطلاق JavaScript يجمع بيانات سلوكية عن المتصفح ويُخزّنها في كوكي
_px3. الطلبات بدون هذا الكوكي تُحجب أو تُعاد توجيهها إلى صفحة CAPTCHA.
النتيجة العملية: بعد عدد قليل من الطلبات من عنوان IP تابع لمركز بيانات (غالبًا أقل من 10 طلبات)، يبدأ PerimeterX في حجب الطلب أو إظهار CAPTCHA. هذا هو السبب الأساسي الذي يجعل البروكسي السكني الدوّار ضروريًا لاستخراج بيانات Yelp على نطاق واسع.
لماذا البروكسي السكني الدوّار مع تحديد جغرافي للولايات؟
Yelp هو دليل أعمال محلية. نتائج البحث وترتيب الأعمال يتأثر بالموقع الجغرافي للمستخدم. إذا كنت تستخرج بيانات أعمال في أوستن، تكساس، فأنت تحتاج إلى طلبات تظهر وكأنها تأتي من عنوان IP في الولايات المتحدة — ومن الأفضل أن يكون في أوستن نفسها أو قريبًا منها — للحصول على نتائج محلية دقيقة.
البروكسي السكني يوفّر عنوان IP حقيقيًا مخصصًا من مزود خدمة إنترنت (ISP)، مما يمنحك درجة سمعة IP عالية لدى PerimeterX. بالمقارنة، عناوين مراكز البيانات تُكتشف بسرعة وتُحجب. الجدول التالي يلخّص الفرق:
| نوع البروكسي | سمعة IP لدى PerimeterX | دقة التحديد الجغرافي | مناسب لـ Yelp؟ |
|---|---|---|---|
| بروكسي مركز بيانات | منخفضة جدًا (يُحجب بعد ~5–10 طلبات) | محدودة | لا |
| بروكسي سكني دوّار | عالية | دقيقة حتى مستوى المدينة | نعم |
| بروكسي موبايل | عالية جدًا | دقيقة | نعم، لكن أغلى |
مع ProxyHat، يمكنك تحديد الدولة والمدينة في اسم المستخدم. على سبيل المثال، user-country-US-city-austin يوجّه طلباتك عبر عنوان IP سكني في أوستن. هذا لا يُحسّن دقة النتائج المحلية فحسب، بل يُقلّل أيضًا من احتمال تفعيل PerimeterX لتحدي المستشعر لأن الطلب يأتي من IP ذي سمعة عالية في المنطقة المتوقعة.
التنفيذ العملي: مثال Python باستخدام ProxyHat HTTP
المثال التالي يستخدم مكتبة requests في Python للوصول إلى صفحة عمل في Yelp عبر ProxyHat، ثم يستخرج كائن __INITIAL_STATE__ المضمّن في HTML. هذا الكائن يحتوي على بيانات الأعمال والمراجعات بصيغة JSON متسلسلة.
import requests
import re
import json
# ProxyHat residential proxy with US geo-targeting
proxy_url = "http://user-country-US-city-austin:YOUR_PASSWORD@gate.proxyhat.com:8080"
proxies = {"http": proxy_url, "https": proxy_url}
headers = {
"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",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
}
url = "https://www.yelp.com/biz/some-business-slug"
response = requests.get(url, headers=headers, proxies=proxies, timeout=30)
if response.status_code == 200:
html = response.text
# Extract __INITIAL_STATE__ embedded JSON
match = re.search(r'__INITIAL_STATE__\s*=\s*({.*?});', html)
if match:
state = json.loads(match.group(1))
# Navigate to business data
biz_data = state.get("biz", {})
name = biz_data.get("name", "")
rating = biz_data.get("rating", 0)
review_count = biz_data.get("reviewCount", 0)
print(f"Business: {name}")
print(f"Rating: {rating} stars ({review_count} reviews)")
# Parse first review (truncated example)
reviews = biz_data.get("reviews", [])
if reviews:
first_review = reviews[0]
print(f"Review by: {first_review.get('user', {}).get('name', 'N/A')}")
print(f"Rating: {first_review.get('rating', 'N/A')}")
print(f"Text: {first_review.get('comment', {}).get('text', '')[:200]}...")
else:
print("Could not find __INITIAL_STATE__")
else:
print(f"Request failed with status: {response.status_code}")
هذا المثال يستخرج البيانات من HTML الأولي. للحصول على مراجعات إضافية، ستحتاج إلى استدعاء نقطة نهاية review_feed مباشرةً:
# Fetch additional reviews via the review_feed endpoint
review_feed_url = f"https://www.yelp.com/biz/{business_slug}/review_feed?rl=&sort_by=relevance_desc&start=20"
# Use sticky session to maintain IP continuity
session_proxy = "http://user-country-US-city-austin-session-yelp01:YOUR_PASSWORD@gate.proxyhat.com:8080"
session_proxies = {"http": session_proxy, "https": session_proxy}
feed_response = requests.get(
review_feed_url,
headers={
**headers,
"X-Requested-With": "XMLHttpRequest",
"Referer": f"https://www.yelp.com/biz/{business_slug}",
},
proxies=session_proxies,
timeout=30,
)
if feed_response.status_code == 200:
feed_data = feed_response.json()
reviews = feed_data.get("reviews", [])
print(f"Retrieved {len(reviews)} additional reviews")
else:
print(f"Feed request failed: {feed_response.status_code}")
مثال Node.js باستخدام SOCKS5 على المنفذ 1080
إذا كنت تعمل في بيئة Node.js وتحتاج إلى استخدام بروتوكول SOCKS5، إليك مثال باستخدام مكتبة socks-proxy-agent:
const { SocksProxyAgent } = require('socks-proxy-agent');
const fetch = require('node-fetch');
const socksUrl = 'socks5://user-country-US-city-austin:YOUR_PASSWORD@gate.proxyhat.com:1080';
const agent = new SocksProxyAgent(socksUrl);
const headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.9',
};
async function scrapeYelpBiz(slug) {
const url = `https://www.yelp.com/biz/${slug}`;
const response = await fetch(url, {
agent,
headers,
timeout: 30000,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const html = await response.text();
const stateMatch = html.match(/__INITIAL_STATE__\s*=\s*({[\s\S]*?});/);
if (stateMatch) {
const state = JSON.parse(stateMatch[1]);
const biz = state.biz || {};
const reviews = (biz.reviews || []).map(r => ({
author: r.user?.name || 'N/A',
rating: r.rating || 0,
text: (r.comment?.text || '').substring(0, 200),
}));
console.log({
name: biz.name,
rating: biz.rating,
reviewCount: biz.reviewCount,
sampleReviews: reviews.slice(0, 2),
});
}
}
scrapeYelpBiz('some-business-slug').catch(console.error);
ترقيم الصفحات، تنظيم معدّل الطلبات، والجلسات اللاصقة
ترقيم صفحات المراجعات
نقطة review_feed تدعم معامِل start الذي يُحدّد إزاحة المراجعات. القيمة الافتراضية هي 0، وتزيد عادةً بمقدار 20 مراجعة لكل صفحة. للحصول على كل المراجعات، حلّق على قيم start من 0 إلى أن تُعيد نقطة النهاية مصفوفة فارغة.
all_reviews = []
start = 0
while True:
feed_url = f"https://www.yelp.com/biz/{slug}/review_feed?start={start}"
resp = requests.get(feed_url, headers=headers, proxies=proxies, timeout=30)
if resp.status_code != 200:
break
batch = resp.json().get("reviews", [])
if not batch:
break
all_reviews.extend(batch)
start += 20
time.sleep(2) # Be polite: 2-second delay between pages
تنظيم معدّل الطلبات (Rate-Limit Pacing)
Yelp وPerimeterX يراقبان معدّل الطلبات لكل عنوان IP. القاعدة الأساسية: لا تتجاوز طلبًا واحدًا كل 2–3 ثوانٍ لكل IP. مع البروكسي الدوّار، يمكنك زيادة التزامن، لكن بحذر. يُنصح بـ:
- تأخير عشوائي بين 1.5 و 4 ثوانٍ بين الطلبات لتفادي أنماط ثابتة.
- حد أقصى 50 طلبًا لكل جلسة IP قبل تدوير عنوان جديد.
- الزامن المتزامن (Concurrency): لا تتجاوز 5–10 طلبات متزامنة من عناوين IP مختلفة.
الجلسات اللاصقة (Sticky Sessions)
عند استخراج صفحة عمل ثم استدعاء review_feed لنفس العمل، من المهم استخدام نفس عنوان IP لكلا الطلبين. هذا لأن PerimeterX قد يربط جلسة المتصفح بـ IP محدد، وتغيير IP بين الطلبين قد يُفعّل تحدي المستشعر.
مع ProxyHat، استخدم معامِل -session- في اسم المستخدم للحفاظ على نفس IP:
# Sticky session: same IP for all requests in this session
sticky_proxy = "http://user-country-US-session-biz12345:YOUR_PASSWORD@gate.proxyhat.com:8080"
تدوير User-Agent
استخدام نفس User-Agent لكل الطلبات يُسهّل على PerimeterX اكتشاف النمط الآلي. دوّر بين عدة User-Agents حقيقية وحديثة:
import random
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
]
headers["User-Agent"] = random.choice(USER_AGENTS)
للحصول على أفضل النتائج، ادمج تدوير User-Agent مع تدوير IP السكني. هذا يُقلّل من خطر بصمة المتصفح الثابتة.
أخطاء شائعة وحالات حدية
1. الاعتماد على User-Agent واحد
الطلبات المتكررة بنفس User-Agent ونفس IP تُفعّل PerimeterX بسرعة. الحل: دوّر كلاهما.
2. تجاهل كوكي _px3
عند زيارة صفحة Yelp لأول مرة، يُعيّن PerimeterX كوكي _px3. الطلبات اللاحقة بدون هذا الكوكي قد تُحجب. استخدم requests.Session() للحفاظ على الكوكيز تلقائيًا:
session = requests.Session()
session.headers.update(headers)
# First request sets _px3 cookie
session.get(f"https://www.yelp.com/biz/{slug}", proxies=proxies, timeout=30)
# Subsequent requests reuse the same session and cookies
session.get(f"https://www.yelp.com/biz/{slug}/review_feed?start=20", proxies=proxies, timeout=30)
3. طلبات سريعة جدًا من نفس IP
حتى مع بروكسي سكني، الطلبات المتتابعة السريعة تُفعّل الحماية. اترك وقتًا كافيًا بين الطلبات.
4. تجاهل بصمة TLS
مكتبة requests تستخدم مكدس TLS مختلف عن المتصفحات الحقيقية. للحصول على نتائج أفضل، فكّر في استخدام مكتبة مثل curl_cffi التي تُحاكي بصمة TLS لـ Chrome، أو استخدم متصفحًا لا رأسياً (headless) مثل Playwright مع البروكسي.
إعداد ProxyHat والروابط الداخلية
للبدء مع ProxyHat، سجّل الدخول إلى dashboard.proxyhat.com واحصل على بيانات اعتمادك. ثم استخدم gate.proxyhat.com:8080 لـ HTTP أو gate.proxyhat.com:1080 لـ SOCKS5.
موارد ProxyHat المفيدة:
- خطط الأسعار — اختر الخطة المناسبة لحجم الاستخراج.
- حالة استخدام: استخراج الويب — دليل شامل لاستخدام البروكسي في الاستخراج.
- حالة استخدام: تتبّع نتائج البحث — لتتبّع ترتيب الأعمال في نتائج البحث.
- مواقع البروكسي — قائمة الدول والمدن المتاحة للتحديد الجغرافي.
- وثائق ProxyHat — مرجع كامل لمعامِلات الاتصال.
الاستخراج الأخلاقي ومتى تستخدم واجهة API الرسمية
الاستخراج الأخلاقي يعني احترام حدود المنصة وحقوق المستخدمين. إليك المبادئ الأساسية:
- احترم robots.txt: تحقّق من robots.txt الخاص بـ Yelp قبل البدء. إذا منعت صفحة معينة، لا تستخرجها.
- لا تستخرج بيانات شخصية: المراجعات قد تحتوي على أسماء مستخدمين وصور. في الاتحاد الأوروبي، تخضع هذه البيانات لـ GDPR. لا تجمع أو تُخزّن بيانات شخصية دون أساس قانوني.
- لا تتجاوز تسجيل الدخول: المحتوى الذي يتطلب حساب Yelp (مثل المراجعات المفلترة أو الرسائل الخاصة) يجب عدم استخراجه. الـ CFAA في الولايات المتحدة يُعرّف الوصول غير المصرّح به كجريمة محتملة.
- استخدم Fusion API حيثما أمكن: إذا كانت 3 مراجعات كافية لاحتياجك، استخدم واجهة Fusion API الرسمية. إنها أسرع، أكثر موثوقية، ولا تُخاطر بحظر IP.
- حدّد معدّل طلباتك: لا تُحمّل خوادم Yelp بشكل مفرط. تأخير 2–3 ثوانٍ بين الطلبات هو الحد الأدنى المقبول.
القاعدة الذهبية: إذا كانت واجهة API الرسمية تغطّي حاجتك، استخدمها. الاستخراج من الواجهة الأمامية يجب أن يكون الملاذ الأخير، وأن يتم باحترام كامل لشروط الخدمة والقوانين.
النقاط الرئيسية (Key Takeaways)
- البيانات العامة دون تسجيل دخول: صفحات
/biz/<slug>تعرض الاسم، التقييم، الفئات، الساعات، ومقتطفات المراجعات. المراجعات الكاملة متاحة عبرreview_feedأو كائن__INITIAL_STATE__.- الفusion API محدود: يُعيد 3 مراجعات فقط لكل نشاط تجاري، مما يجعله غير كافٍ لمجموعات بيانات المراجعات الكبيرة.
- PerimeterX هو الحاجز الرئيسي: كوكي
_px3، بصمة TLS، وسمعة IP هي الإشارات الرئيسية. عناوين مراكز البيانات تُحجب بعد ~5–10 طلبات.- البروكسي السكني الدوّار ضروري: استخدم
user-country-US-city-austinللحصول على نتائج محلية دقيقة ودرجة سمعة IP عالية.- الجلسات اللاصقة مهمة: استخدم
-session-للحفاظ على نفس IP خلال جلسة استخراج صفحة عمل واحدة.- الاستخراج الأخلاقي أولاً: احترم robots.txt، لا تجمع بيانات شخصية، لا تتجاوز تسجيل الدخول، واستخدم Fusion API حيثما أمكن.






