مقدمة: لماذا هذا الدليل مهم وماهي حدوده
لينكدإن هو أكبر شبكة مهنية في العالم، بأكثر من مليار عضو وملايين الشركات والوظائف المدرجة. بالنسبة لفرق التوظيف وشركات أبحاث السوق والمطورين، تمثل هذه البيانات ثروة هائلة من المعلومات العامة. لكن استخراج هذه البيانات يتطلب فهماً عميقاً للحدود القانونية والأخلاقية والتقنية.
إخلاء مسؤولية مهم: هذا الدليل للأغراض التعليمية فقط ولا يشكل استشارة قانونية. استشر محامياً متخصصاً قبل أي نشاط استخراج بيانات. احترم دائماً شروط خدمة المنصة وقوانين الخصوصية المطبقة مثل GDPR في الاتحاد الأوروبي وCCPA في كاليفورنيا.
الإطار القانوني: قضية hiQ Labs ضد LinkedIn
في عام 2017، رفعت LinkedIn دعوى قضائية ضد شركة hiQ Labs، وهي شركة تحليلات بيانات كانت تستخرج بيانات الملفات الشخصية العامة من لينكدإن. هذه القضية تقدم سابقة مهمة في النقاش حول استخراج البيانات العامة.
ماذا قررت المحكمة؟
- 2019: محكمة المقاطعة الفيدرالية حكمت لصالح hiQ، معتبرة أن البيانات العامة المتاحة دون تسجيل دخول لا يحميها قانون الاحتيال وإساءة استخدام الكمبيوتر (CFAA).
- 2022: محكمة الاستئناف التاسعة أيدت الحكم بشكل جزئي، مؤكدة أن الوصول للبيانات العامة لا ينتهك CFAA.
- 2023: المحكمة العليا رفضت الاستئناف، مما يعني بقاء حكم محكمة الاستئناف سارياً.
النقطة الأساسية: القضية تشير إلى أن البيانات المتاحة للجمهور دون حاجة لتسجيل دخول قد تكون قابلة للاستخراج قانونياً في بعض الولايات القضائية الأمريكية. لكن هذا لا يعني إذناً مطلقاً — كل حالة تُقيّم على حدة.
ما الذي لا تغطيه هذه السابقة؟
- البيانات خلف جدار تسجيل الدخول
- بيانات Sales Navigator أو الحسابات المميزة
- الاستخدام التجاري الذي قد ينتهك شروط الخدمة
- الولايات القضائية خارج الولايات المتحدة
ما هي البيانات العامة المتاحة دون تسجيل دخول؟
فهم ما هو متاح فعلاً للجمهور هو الخطوة الأولى في أي مشروع استخراج بيانات أخلاقي.
1. الملفات الشخصية العامة
عندما يختار المستخدم إعدادات "عامة" لملفه الشخصي، تصبح بعض المعلومات متاحة لأي زائر:
- الاسم والصورة الشخصية
- العنوان الوظيفي الحالي
- الموقع الجغرافي
- الشركة الحالية
- التعليم (في بعض الحالات)
- ملخص المهارات الرئيسية
ما هو غير متاح: التوصيات، الاتصالات، المنشورات التفصيلية، معلومات الاتصال المباشرة.
2. صفحات الشركات العامة
صفحات الشركات المتاحة للجمهور تشمل:
- اسم الشركة وشعارها
- الصناعة وحجم الشركة
- المقر الرئيسي
- الموقع الإلكتروني للشركة
- عدد الموظفين (تقريبي)
- الوظائف المدرجة公开اً
3. إعلانات الوظائف العامة
صفحة البحث عن الوظائف في لينكدإن تعرض إعلانات متاحة للجمهور:
- المسمى الوظيفي
- اسم الشركة
- الموقع
- نوع التوظيف
- وصف الوظيفة
- المهارات المطلوبة
لماذا البروكسي السكني ضروري لاستخراج بيانات لينكدإن؟
لينكدإن تطبق بعضاً من أكثر أنظمة مكافحة البوت تطوراً في الصناعة. استخدام بروكسي مركز البيانات (Datacenter) سيؤدي حتماً للحظر.
بصمة IP مركز البيانات
لينكدإن تحتفظ بقاعدة بيانات ضخمة لعناوين IP مراكز البيانات المعروفة. هذه الـ IPs تشمل:
- نطاقات AWS وGoogle Cloud وAzure
- مزودي VPS المشهورين
- شبكات البروكسي التجارية الرخيصة
عند اكتشاف طلب من هذه الـ IPs، تطبق لينكدإن استراتيجيات متعددة:
- CAPTCHA فوري: خاصة على صفحات الملفات الشخصية
- تحديات JavaScript: تتطلب تفاعل متصفح حقيقي
- حظر الـ IP: قد يستمر من ساعات إلى أيام
- تقييد المعدل: السماح بعدد قليل جداً من الطلبات
ميزات البروكسي السكني
| الميزة | بروكسي سكني | بروكسي مركز بيانات |
|---|---|---|
| بصمة IP | تبدو كمستخدم منزلي عادي | سهل الكشف والتعرف عليه |
| معدل النجاح | 85-95% مع إدارة صحيحة | 10-30% قبل الحظر |
| دوران IP | مئات الآلاف من IPs المتاحة | محدود وسهل التتبع |
| الاستمرارية | يمكن العمل لساعات/أيام | حظر في دقائق |
| التكلفة | أعلى لكن ضرورية | رخيصة لكن عديمة الفائدة |
لماذا البروكسي المتنقل أفضل؟
البروكسي المتنقل (Mobile Proxy) يوفر ميزة إضافية: IPs من شبكات الهواتف المحمولة. لينكدإن تتردد في حظر هذه الـ IPs لأنها تخدم ملايين المستخدمين الشرعيين على الهواتف.
مثال عملي: Python + Playwright مع البروكسي السكني
فيما يلي مثال كامل لاستخراج صفحة شركة عامة من لينكدإن باستخدام Playwright مع بروكسي سكني.
import asyncio
from playwright.async_api import async_playwright
import random
import time
class LinkedInScraper:
def __init__(self, proxy_url):
self.proxy_url = proxy_url
self.base_url = "https://www.linkedin.com"
async def create_browser_context(self, playwright):
"""إنشاء سياق متصفح واقعي"""
browser = await playwright.chromium.launch(
headless=True,
proxy={
"server": self.proxy_url
}
)
# سياق المتصفح مع بصمة واقعية
context = await browser.new_context(
viewport={"width": 1920, "height": 1080},
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",
locale="en-US",
timezone_id="America/New_York",
geolocation={"latitude": 40.7128, "longitude": -74.0060},
permissions=["geolocation"],
)
return browser, context
async def scrape_company_page(self, company_slug):
"""استخراج بيانات صفحة شركة عامة"""
async with async_playwright() as p:
browser, context = await self.create_browser_context(p)
page = await context.new_page()
try:
url = f"{self.base_url}/company/{company_slug}"
# انتظار عشوائي لمحاكاة السلوك البشري
await asyncio.sleep(random.uniform(2, 5))
await page.goto(url, wait_until="networkidle", timeout=30000)
# التحقق من وجود CAPTCHA
if await page.locator("[data-testid='challenge-form']").count() > 0:
print("CAPTCHA detected. Consider reducing request rate.")
return None
# انتظار تحميل المحتوى
await page.wait_for_selector(".org-top-card", timeout=10000)
# استخراج البيانات
company_data = {
"name": await page.locator(".org-top-card__primary-content h1").inner_text(),
"industry": await page.locator(".org-top-card__primary-content .org-top-card-summary__list-item--industry").inner_text() if await page.locator(".org-top-card__primary-content .org-top-card-summary__list-item--industry").count() > 0 else None,
"headquarters": await page.locator(".org-top-card__primary-content .org-top-card-summary__list-item--location").inner_text() if await page.locator(".org-top-card__primary-content .org-top-card-summary__list-item--location").count() > 0 else None,
"url": url
}
return company_data
except Exception as e:
print(f"Error scraping {company_slug}: {e}")
return None
finally:
await browser.close()
# استخدام البروكسي السكني من ProxyHat
PROXY_URL = "http://user-country-US:your_password@gate.proxyhat.com:8080"
async def main():
scraper = LinkedInScraper(PROXY_URL)
# قائمة الشركات المستهدفة
companies = ["google", "microsoft", "amazon"]
for company in companies:
result = await scraper.scrape_company_page(company)
if result:
print(f"Company: {result['name']}")
print(f"Industry: {result.get('industry', 'N/A')}")
print(f"HQ: {result.get('headquarters', 'N/A')}")
print("-" * 40)
# معدل محدود: طلب واحد كل 30-60 ثانية
await asyncio.sleep(random.uniform(30, 60))
if __name__ == "__main__":
asyncio.run(main())نقاط مهمة في الكود
- البروكسي السكني: نستخدم
gate.proxyhat.com:8080مع علم الدولة - بصمة المتصفح: إعدادات واقعية للـ viewport وuser agent وtimezone
- معدل الطلبات: انتظار 30-60 ثانية بين الطلبات
- كشف CAPTCHA: التحقق من وجود تحديات والتعامل معها
استخراج بيانات الوظائف: تقنيات متقدمة
صفحة البحث عن الوظائف في لينكدإن تقدم بيانات قيمة للمستخدمين العامين. إليك كيفية استخراجها بشكل مسؤول.
هيكل URL البحث عن الوظائف
# URL الأساسي للبحث عن الوظائف
BASE_URL = "https://www.linkedin.com/jobs/search/"
# المعاملات المتاحة
params = {
"keywords": "software engineer",
"location": "United States",
"f_JT": "F", # F=Full-time, P=Part-time, C=Contract, I=Internship
"f_E": "2", # مستوى الخبرة: 1=Entry, 2=Associate, 3=Mid-Senior, 4=Director, 5=Executive
"f_WT": "2", # نوع العمل: 1=On-site, 2=Remote, 3=Hybrid
"start": "0" # pagination: 0, 25, 50, 75...
}مثال Node.js لاستخراج الوظائف
const { chromium } = require('playwright');
const PROXY_URL = 'http://user-country-US:your_password@gate.proxyhat.com:8080';
async function scrapeLinkedInJobs(searchQuery, location, maxPages = 5) {
const browser = await chromium.launch({
headless: true,
proxy: { server: PROXY_URL }
});
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
locale: 'en-US',
});
const page = await context.newPage();
const allJobs = [];
try {
for (let pageNum = 0; pageNum < maxPages; pageNum++) {
const start = pageNum * 25;
const url = `https://www.linkedin.com/jobs/search/?keywords=${encodeURIComponent(searchQuery)}&location=${encodeURIComponent(location)}&start=${start}`;
console.log(`Scraping page ${pageNum + 1}: ${url}`);
await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
// انتظار تحميل قائمة الوظائف
await page.waitForSelector('.jobs-search__results-list', { timeout: 15000 });
// استخراج بيانات الوظائف من الصفحة الحالية
const jobs = await page.evaluate(() => {
const jobElements = document.querySelectorAll('.jobs-search__results-list li');
return Array.from(jobElements).map(job => {
const titleEl = job.querySelector('h3.base-search-card__title');
const companyEl = job.querySelector('h4.base-search-card__subtitle a');
const locationEl = job.querySelector('.job-search-card__location');
const linkEl = job.querySelector('a.base-card__full-link');
return {
title: titleEl ? titleEl.textContent.trim() : null,
company: companyEl ? companyEl.textContent.trim() : null,
location: locationEl ? locationEl.textContent.trim() : null,
url: linkEl ? linkEl.href : null,
};
}).filter(job => job.title && job.url);
});
allJobs.push(...jobs);
console.log(`Found ${jobs.length} jobs on page ${pageNum + 1}`);
// انتظار بين الصفحات (مهم جداً)
const waitTime = 30000 + Math.random() * 30000; // 30-60 ثانية
await new Promise(resolve => setTimeout(resolve, waitTime));
}
return allJobs;
} catch (error) {
console.error('Error during scraping:', error.message);
return allJobs; // إرجاع ما تم استخراجه قبل الخطأ
} finally {
await browser.close();
}
}
// استخدام الدالة
(async () => {
const jobs = await scrapeLinkedInJobs('data scientist', 'United States', 3);
console.log(`\nTotal jobs scraped: ${jobs.length}`);
console.log('Sample job:', jobs[0]);
})();استراتيجيات التصفح المتقدم
| الفلتر | القيم المتاحة | الاستخدام |
|---|---|---|
| نوع الوظيفة (f_JT) | F, P, C, I, T | Full-time, Part-time, Contract, Internship, Temporary |
| مستوى الخبرة (f_E) | 1, 2, 3, 4, 5 | Entry إلى Executive |
| نوع العمل (f_WT) | 1, 2, 3 | On-site, Remote, Hybrid |
| تاريخ النشر (f_TPR) | r86400, r604800, r2592000 | آخر 24 ساعة، أسبوع، شهر |
| الشركة (f_C) | Company ID | تصفية حسب شركة محددة |
متى لا يجب عليك استخراج البيانات؟
هناك خطوط حمراء واضحة يجب احترامها. تجاوزها يعرضك لمخاطر قانونية وأخلاقية.
1. البيانات خلف جدار تسجيل الدخول
أي بيانات تتطلب تسجيل دخول لا تعتبر "عامة" بالمعنى الذي ناقشناه. هذا يشمل:
- ملفات تعريف الأعضاء غير العامة
- رسائل InMail
- المجموعات الخاصة
- معلومات الاتصال المخفية
تحذير: استخدام بيانات اعتماد مستخدم حقيقي لاستخراج البيانات ينتهك شروط خدمة لينكدإن بوضوح وقد يعرض الحساب للحظر الدائم.
2. Sales Navigator وPremium Data
بيانات Sales Navigator محمية بموجب:
- اشتراك مدفوعة مع شروط استخدام محددة
- حماية تقنية إضافية
- قوانين حقوق الطبع والنشر للقاعدة البيانات
استخراج هذه البيانات يختلف جوهرياً عن البيانات العامة المتاحة مجاناً.
3. البيانات الشخصية الحساسة
حتى لو كانت البيانات "عامة"، بعض أنواع البيانات تتطلب حذراً إضافياً:
- معلومات الاتصال المباشرة (بريد إلكتروني، هاتف)
- المعلومات الصحية أو الدينية أو السياسية
- بيانات القاصرين
- المعلومات المالية الشخصية
تحت GDPR، معالجة هذه البيانات تتطلب أساساً قانونياً واضحاً.
4. الاستخدامات المحظورة
- بناء قواعد بيانات للمبيعات الباردة
- التنصت على المنافسين بشكل عدواني
- إنشاء ملفات تعريف وهمية
- بيع البيانات لطرف ثالث
استراتيجيات إدارة معدل الطلبات
الاستخراج الناجح يتطلب توازناً دقيقاً بين الكفاءة وعدم إثارة الشكوك.
القواعد الذهبية
- ابدأ ببطء: طلب واحد كل 30-60 ثانية
- راقب الاستجابات: أي تغيير في السلوك يشير لمشكلة
- استخدم جلسات لاصقة: نفس الـ IP لعدة طلبات متتالية
- فترات راحة: توقف لساعات كل بضع مئات من الطلبات
- دوران IP ذكي: غيّر IP فقط عند الضرورة
مثال على مدير معدل الطلبات
import time
import random
from collections import deque
class RateLimiter:
def __init__(self, min_interval=30, max_interval=60, burst_limit=10):
self.min_interval = min_interval
self.max_interval = max_interval
self.burst_limit = burst_limit
self.request_times = deque(maxlen=100)
self.burst_count = 0
def wait(self):
"""انتظار قبل الطلب التالي"""
now = time.time()
# التحقق من الحد الأقصى للطلبات المتتالية
if self.burst_count >= self.burst_limit:
print(f"Burst limit reached. Cooling down for 5 minutes...")
time.sleep(300) # 5 دقائق راحة
self.burst_count = 0
# حساب وقت الانتظار
if self.request_times:
last_request = self.request_times[-1]
elapsed = now - last_request
wait_time = random.uniform(self.min_interval, self.max_interval)
if elapsed < wait_time:
actual_wait = wait_time - elapsed
time.sleep(actual_wait)
self.request_times.append(time.time())
self.burst_count += 1
def report_error(self, error_type):
"""الإبلاغ عن خطأ لضبط السلوك"""
if error_type == 'captcha':
print("CAPTCHA detected. Increasing wait time.")
self.min_interval *= 1.5
self.max_interval *= 1.5
elif error_type == 'block':
print("IP blocked. Need to rotate.")
self.burst_count = self.burst_limit # فرض راحة طويلة
# استخدام مدير المعدل
limiter = RateLimiter(min_interval=30, max_interval=60, burst_limit=20)
for company in companies:
limiter.wait()
result = scrape_company(company)
if result.get('captcha'):
limiter.report_error('captcha')
elif result.get('blocked'):
limiter.report_error('block')البدائل: APIs الرسمية من LinkedIn
في كثير من الحالات، استخدام API الرسمي هو الخيار الأفضل والأكثر استدامة.
LinkedIn Marketing API
للشركات والمسوقين، توفر لينكدإن:
- إدارة الإعلانات
- تحليلات الصفحات
- بيانات الجمهور للتسويق
المتطلبات: حساب مسجل كمطور، موافقة LinkedIn، التزام بحدود الاستخدام.
LinkedIn Talent Solutions API
لفرق التوظيف والتوظيف:
- البحث عن المرشحين (للحسابات المميزة)
- إدارة الوظائف
- تكامل ATS
المتطلبات: اشتراك Recruiter أو Talent Hub.
مقارنة بين الخيارات
| المعيار | استخراج البيانات العامة | API الرسمي |
|---|---|---|
| التكلفة | منخفضة (بروكسي فقط) | متوسطة إلى عالية |
| الاستدامة | محدودة (تتطلب صيانة) | عالية (مدعومة رسمياً) |
| نطاق البيانات | عام فقط | أوسع (حسب الخطة) |
| المخاطر القانونية | موجودة | معدومة تقريباً |
| الجهد التقني | عالي | منخفض إلى متوسط |
| معدل التحديث | حسب قدرتك | مضمون |
الاعتبارات الأخلاقية والقانونية
قانون الخصوصية الأوروبي (GDPR)
إذا كنت تستهدف بيانات مواطني الاتحاد الأوروبي:
- البيانات الشخصية محمية حتى لو كانت "عامة"
- يجب وجود أساس قانوني للمعالجة
- حقوق الأشخاص في الاعتراض والشطب
- غرامات قد تصل 4% من الإيرادات السنوية
قانون كاليفورنيا (CCPA/CPRA)
- حق المستهلكين في معرفة البيانات المجمعة
- حق الحذف والاعتراض على البيع
- إشعارات الخصوصية مطلوبة
أفضل الممارسات الأخلاقية
- الشفافية: كن واضحاً حول ما تفعله بالبيانات
- الحد من الاستخدام: لا تجمع أكثر مما تحتاج
- الأمان: حماية البيانات المجمعة
- الاحترام: احترم ملكية المنصة وحقوق المستخدمين
- البدائل: استخدم APIs الرسمية عندما يكون ذلك ممكناً
خلاصة النقاط الرئيسية
النقاط الأساسية:
- البيانات العامة المتاحة دون تسجيل دخول قد تكون قابلة للاستخراج في بعض الولايات القضائية، لكن القانون لا يزال يتطور.
- البروكسي السكني ضروري — بروكسي مركز البيانات سيُحظر فوراً.
- معدل الطلبات هو العامل الأهم: 30-60 ثانية بين الطلبات، فترات راحة منتظمة.
- لا تستخرج أبداً بيانات خلف جدار تسجيل الدخول أو Sales Navigator.
- فكر في APIs الرسمية للمشاريع التجارية طويلة الأمد.
- احترم دائماً قوانين الخصوصية وحقوق المستخدمين.
الخطوات التالية
إذا كنت تخطط لمشروع استخراج بيانات لينكدإن:
- استشر محامياً: تأكد من الامتثال القانوني في ولايتك القضائية
- ابدأ صغيراً: اختبر مع عشرات الصفحات قبل التوسع
- استثمر في بروكسي سكني موثوق: مثل ProxyHat الذي يوفر IPs سكنية حقيقية
- راقب باستمرار: راقب معدلات النجاح والأخطاء
- فكر في البدائل: APIs الرسمية قد تكون أفضل على المدى الطويل
للمزيد من المعلومات حول استخدام البروكسي السكني، راجع دليلنا الشامل للبروكسي السكني أو حالات استخدام استخراج الويب.






