خانه/مقالات/آموزش اسکریپینگ لینکدین با Scrapy
وب اسکریپینگ
پروکسی و چرخش IP
ضد بلاک (Anti-bot)
برگشت به صفحه مقاله ها
آموزش اسکریپینگ لینکدین با Scrapy

آموزش اسکریپینگ لینکدین با Scrapy

راهنمای عملی ساخت اسپایدر Scrapy برای پروفایل‌های عمومی شرکت در LinkedIn: شامل کد نمونه، نکات استخراج با response.css، روش‌های مقابله با سیستم‌های ضدربات، نمونه تنظیم پراکسی و مانیتورینگ، و توصیه‌هایی برای اجرا و زمان‌بندی در کلود.
امیر حسین حسینیان
امیر حسین حسینیان
1404-12-05

مقدمه

در این مقاله قدم‌به‌قدم می‌خواهیم یک اسپایدر عملی با Scrapy بسازیم تا پروفایل‌های عمومی شرکت‌ها در LinkedIn را استخراج کند. اگر توسعه‌دهنده پایتون در سطح متوسط هستید، پس از خواندن این راهنما باید بتوانید:

  • اسپایدری بسازید که فیلدهای کلیدی شرکت را بخواند.
  • با مشکلات رایج بلاک و fingerprinting آشنا شوید و استراتژی‌های مقابله را پیاده کنید.
  • اسپایدر را مانیتور و در محیط‌های ابری زمان‌بندی کنید.

ایده کلی و ساخت اسپایدر پایه

ایده ساده است: فهرستی از آدرس‌های پروفایل شرکت داشته باشیم، برای هر آدرس یک درخواست ارسال کنیم و در پاسخ HTML، با response.css داده‌ها را استخراج کنیم. در عمل باید با مواردی مثل المان‌های گمشده، تغییرات ساختاری صفحه و محدودیت سرعت نیز برخورد کنیم.

import scrapy

class LinkedCompanySpider(scrapy.Spider):
    name = 'linkedin_company_profile'

    # فهرست اولیه آدرس‌های شرکت (ورودی)
    company_pages = [
        'https://www.linkedin.com/company/usebraintrust/',
        'https://www.linkedin.com/company/centraprise/',
    ]

    def start_requests(self):
        # هر URL را درخواست می‌کنیم؛ می‌توانید هدر، کوکی یا روتر پراکسی را اینجا اضافه کنید
        for url in self.company_pages:
            yield scrapy.Request(url=url, callback=self.parse_response, meta={'source_url': url})

    def parse_response(self, response):
        # خروجی: یک دیکشنری با فیلدهای شرکت
        company_item = {}

        # فیلدهای پایه
        company_item['name'] = response.css('.top-card-layout__entity-info h1::text').get(default='').strip()
        company_item['summary'] = response.css('.top-card-layout__entity-info h4 span::text').get(default='').strip()

        try:
            details = response.css('.core-section-container__content .mb-2')
            company_item['industry'] = details[1].css('.text-md::text').getall()[1].strip()
            company_item['size'] = details[2].css('.text-md::text').getall()[1].strip()
            company_item['founded'] = details[5].css('.text-md::text').getall()[1].strip()
        except Exception:
            # بررسی‌ها و هشدارها؛ در تولید بهتر است لاگ و metric ارسال شود
            self.logger.warning('Some company fields missing for %s', response.url)

        yield company_item

توضیح اجزای مهم:

  • start_requests: ورودی‌ها (فهرست URL) را می‌گیرد و درخواست‌ها را ایجاد می‌کند. می‌توانید در اینجا هدرها (مانند User-Agent)، کوکی‌ها یا پراکسی را تنظیم کنید.
  • parse_response: پاسخ HTML را دریافت و با селکتورهای CSS مقادیر را استخراج می‌کند. خروجی یک دیکشنری است که قابل ذخیره در JSON Lines یا پایگاه داده است.
  • خطاها: از تلاش برای دسترسی ایندکس‌های خارج از محدوده پرهیز کنید و از get(default='') استفاده کنید تا Null-safe باشد.

نکات عملی برای استخراج دقیق‌تر

چند توصیه مهم که عملکرد و پایداری اسپایدر را بالا می‌برد:

  • قبل از انتشار، ساختار صفحه را با چند نمونه بررسی کنید؛ گاهی ایندکس‌ها یا کلاس‌ها تغییر می‌کنند.
  • خروجی‌ها را در فرمت قابل ایندکس نگه دارید؛ مثلاً JSON Lines برای وارد کردن به دیتابیس راحت است.
  • برای داده‌های حجیم از استریم نتایج به فایل یا سرویس ابری استفاده کنید تا حافظه کنترل شود.
  • همیشه فرض کنید بعضی فیلدها موجود نیستند؛ لاگ و متریک برای این حالات ثبت کنید.

نمونه خروجی

{
    'name': 'Braintrust',
    'summary': 'Braintrust is the first decentralized Web3 talent network ...',
    'industry': 'Software Development',
    'size': '11-50 employees',
    'founded': '2018'
}

مقابله با سیستم‌های ضدربات LinkedIn

LinkedIn از ترکیبی از کنترل IP، هدرها، fingerprinting مرورگر و CAPTCHA برای شناسایی ربات‌ها استفاده می‌کند. برای کاهش احتمال بلاک شدن می‌توانید از ترکیب راهکارهای زیر استفاده کنید:

  • پراکسی‌های چرخشی با کیفیت (ترجیحاً residential/mobile) تا تغییر IP و توزیع درخواست‌ها.
  • چرخاندن User-Agent و دیگر هدرهای مرورگر به همراه تطابق با کوکی‌ها و رفتار واقعی مرورگر.
  • استفاده از headless browser (مثل Playwright یا Puppeteer) با پروفایل‌های واقعی و تاخیرهای انسانی برای رندرهای جاوااسکریپت‌دار.
  • پیاده‌سازی backoff و محدودیت نرخ درخواست (rate limiting) و تنظیمات concurrency در Scrapy.
  • تشخیص بلاک (مثلاً برگشت صفحه لاگین یا صفحه CAPTCHA) و ثبت متریک برای تحلیل.

اگر نخواهید همه این موارد را خودتان پیاده کنید، می‌توانید از راهکارهای پراکسیِ هوشمند استفاده کنید که مدیریت روتیشن، تشخیص بن و بای‌پس CAPTCHA را تسهیل می‌کنند. برای تست سریع، یک نمونه درخواست curl به API پراکسی می‌تواند به شکل زیر باشد:

curl 'https://proxy.example.io/v1/?api_key=YOUR_API_KEY&url=https://www.linkedin.com/in/reidhoffman/'

برای ادغام پراکسی با Scrapy معمولاً یک Downloader Middleware نصب و فعال می‌شود؛ همچنین باید کلید API و تنظیمات مرتبط را در محیط یا فایل تنظیمات نگهداری کنید.

پلاگین‌ها و تنظیمات نمونه (پراکسی و مانیتورینگ)

نمونه‌ای از نصب بسته‌ها و تنظیمات اولیه در settings.py:

pip install scrapeops-scrapy
pip install scrapeops-scrapy-proxy-sdk
# settings.py (نمونه)
SCRAPEOPS_API_KEY = 'YOUR_API_KEY'
SCRAPEOPS_PROXY_ENABLED = True

DOWNLOADER_MIDDLEWARES = {
    'scrapeops_scrapy_proxy_sdk.scrapeops_scrapy_proxy_sdk.ScrapeOpsScrapyProxySdk': 725,
}

EXTENSIONS = {
    'scrapeops_scrapy.extension.ScrapeOpsMonitor': 500,
}

# در صورت نیاز، جایگزین کردن RetryMiddleware برای گزارش‌گیری بهتر
DOWNLOADER_MIDDLEWARES.update({
    'scrapeops_scrapy.middleware.retry.RetryMiddleware': 550,
    'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,
})

توضیح: در نمونه بالا SCRAPEOPS_API_KEY را از متغیر محیطی یا سیستم مدیریت اسرار بارگذاری کنید تا کلید در کد ذخیره نشود.

مانیتورینگ اسپایدر

مانیتورینگ در تولید بسیار مهم است: خطاهای 4xx/5xx، نرخ موفقیت صفحه‌ها، میانگین زمان پاسخ و نرخ تبدیل (pages → items) را مانیتور کنید. ابزارهای مانیتورینگ مخصوص Scrapy می‌توانند این متریک‌ها را جمع‌آوری و هشدار ارسال کنند.

  • همه لاگ‌ها و exception ها را با سطح مناسب ثبت کنید.
  • متریک‌های سفارشی (مثلاً count of CAPTCHAs) تولید کنید و به داشبورد بفرستید.
  • برای هشدار از Threshold و نرخ افزایشی خطا استفاده کنید تا قبل از گسترده شدن مشکل مطلع شوید.

اجرای اسپایدر و زمان‌بندی در کلود

برای تولید معمولاً اسپایدر را در سرور یا سرویس‌های ابری اجرا و زمان‌بندی می‌کنند. گزینه‌ها:

  • اجرای دوره‌ای در یک سرور با cron یا systemd timers.
  • کانتینرایز کردن با Docker و اجرای آن در کلاستر یا سرویس‌های زمان‌بندی شغلی.
  • استفاده از سرویس‌های Job Scheduler که اجرا، مانیتور و لاگ را مدیریت می‌کنند.

مثال اجرای محلی:

scrapy crawl linkedin_company_profile

نکات عملی برای اجرا در کلود:

  • کلیدهای API و اسرار را از طریق متغیر محیطی یا سرویس‌های مدیریت اسرار تزریق کنید.
  • نتایج را به صورت قابل بازیابی ذخیره کنید: JSON Lines، پایگاه داده رابطه‌ای یا ذخیره‌ساز شیء مثل S3.
  • مکانیسم‌های idempotency و deduplication برای جلوگیری از پردازش تکراری اضافه کنید (مثلاً با چک کردن URL یا شناسه یونیک).
  • مقیاس‌پذیری: محدودیت‌های API و ظرفیت پراکسی را در طراحی لحاظ کنید و از صف‌ها برای کنترل نرخ استفاده کنید.

جمع‌بندی

در این راهنما یک اسپایدر پایه برای استخراج پروفایل‌های عمومی شرکت در LinkedIn با Scrapy نوشتیم، به روش‌های مقابله با سیستم‌های ضدربات پرداختیم و نکات عملی برای مانیتورینگ و عملیات در کلود را مرور کردیم. مهم‌ترین نکات:

  • ابتدا اسپایدر را ساده و مستحکم بسازید (خطایاب و لاگ‌گذاری) و سپس قابلیت‌های پیچیده‌تر مثل پراکسی و headless browser را اضافه کنید.
  • برای مقابله با بلاک، از پراکسی‌های با کیفیت، چرخش هدر و رفتار شبیه‌سازی مرورگر استفاده کنید.
  • مانیتورینگ و مدیریت اسرار را فراموش نکنید—این موارد در محیط تولید تفاوت زیادی ایجاد می‌کنند.

اگر می‌خواهید، می‌توانم نسخه‌ای از این اسپایدر را طوری تغییر دهم که خروجی در PostgreSQL یا یک فایل NDJSON بنویسد، یا نشان دهم چطور Playwright را برای رندر جاوااسکریپت به جریان کاری اضافه کنید.

مقاله‌های مرتبط