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

وب اسکریپینگ با Scrapy: اسکریپ کردن نظرات Amazon

در این مقاله عملی با Scrapy می‌آموزید چگونه نظرات محصولات Amazon را با روشی پایدار اسکریپ کنید: از استفاده از ASIN و جستجو برای یافتن آدرس محصول تا استخراج نظرات، مدیریت صفحه‌بندی، ذخیره‌سازی خروجی و مقابله با محافظت‌های ضدبات با پراکسی و مانیتورینگ.
امیر حسین حسینیان
امیر حسین حسینیان
1404-12-03

مقدمه

در این مقاله قدم‌به‌قدم می‌فهمیم چگونه با Scrapy یک اسپایدر بسازیم که «نظرات محصول» در Amazon را اسکریپ کند. هدف این راهنما تولید خروجی قابل استفاده (CSV/DB)، عبور از صفحات نظرات (pagination)، مدیریت بلاک و مانیتورینگ است. در پایانِ این مقاله شما: 1) ساختار یک اسپایدر برای پیدا کردن صفحه محصول و استخراج نظرات را می‌دانید، 2) روش ذخیره‌سازی داده و پیکربندی FEEDS را بلدید، و 3) با روش‌های معمول عبور از محافظت‌های ضدبات آشنا می‌شوید.

ایده کلی و استراتژی

چالش اصلی در اسکریپ کردن نظرات Amazon این است که دسترسی مستقیم به مسیر «product-reviews» معمولاً به ریدایرکت لاگین یا صفحات محافظت‌شده منجر می‌شود. راه حل پایدارتر این است که ابتدا از صفحهٔ جستجو (search) برای پیدا کردن آدرس واقعی محصول استفاده کنیم و سپس صفحهٔ نظرات آن محصول را دنبال کنیم. این روش باعث می‌شود آدرس صحیح محصول را گرفته و ریدایرکت‌های محافظتی را دور بزنیم.

سرمایه‌گذاری اولیه: لیست ASIN و ساخت URL جستجو

ورودی اصلی اسپایدر، فهرستی از ASIN است. به ازای هر ASIN ابتدا یک درخواست به صفحهٔ جستجو می‌فرستیم تا لینک محصول در نتایج را استخراج کنیم. سپس با آن لینک به صفحهٔ نظرات می‌رویم.

مثال کامل اسپایدر (نمونه پایتون)

import scrapy
from urllib.parse import urljoin

class AmazonReviewsSpider(scrapy.Spider):
    name = 'amazon_reviews'

    def start_requests(self):
        # ورودی: لیست ASINها
        asin_list = ['B09G9FPHY6']
        AMAZON_BASE = 'AMAZON_BASE_URL'  # مقدار واقعی را در پروژه قرار دهید

        for asin in asin_list:
            # ساخت URL جستجو؛ این رشته‌ها نمونه هستند و باید با دامنه واقعی جایگزین شوند
            search_url = f"{AMAZON_BASE}/s?k={asin}"
            yield scrapy.Request(url=search_url,
                                 callback=self.discover_product_urls,
                                 meta={'asin': asin, 'page': 1})

    def discover_product_urls(self, response):
        # ورودی: response صفحهٔ نتایج جستجو
        # خروجی: درخواست به صفحهٔ محصول (که به parse_reviews متصل است)
        asin = response.meta['asin']

        # انتخاب نتایج جستجو؛ سلکتورها ممکن است بسته به قالب صفحه تغییر کنند
        search_products = response.css('div.s-result-item[data-component-type=s-search-result]')
        for product in search_products:
            relative_url = product.css('a::attr(href)').get()
            if not relative_url:
                continue

            product_url = urljoin('AMAZON_BASE_URL', relative_url).split('?')[0]
            yield scrapy.Request(url=product_url,
                                 callback=self.parse_reviews,
                                 meta={'asin': asin, 'retry_count': 0})

    def parse_reviews(self, response):
        # ورودی: response صفحهٔ محصول یا صفحهٔ نظرات
        # خروجی: آیتم‌های دیکشنری هر نظر
        asin = response.meta.get('asin')

        # انتخاب عناصر نظر؛ سلکتورها را براساس HTML واقعی صفحه تطبیق دهید
        review_elements = response.css('li[data-hook="review"]')
        for review_element in review_elements:
            product_data = {
                'asin': asin,
                'text': ''.join(review_element.css('span[data-hook=review-body] ::text').getall()).strip(),
                'title': review_element.css('*[data-hook=review-title]>span::text').get(),
                'location_and_date': review_element.css('span[data-hook=review-date] ::text').get(),
                'verified': bool(review_element.css('span[data-hook=avp-badge-linkless] ::text').get()),
                'rating': review_element.css('*[data-hook*=review-star-rating] ::text').re_first(r"(\d+\.*\d*)")
            }

            # نقش هر بخش:
            # - asin: ASIN ورودی تا مرجع را نگه داریم
            # - text: متن کامل نظر، با حذف فاصله‌های اضافی
            # - title: عنوان نظر
            # - location_and_date: متن تاریخ/مکان
            # - verified: بولین برای نشان دادن اینکه خرید تاییدشده است یا خیر
            # - rating: نمره عددی استخراج‌شده از متن

            print(product_data)  # برای دیباگ؛ در تولیدی می‌توانید این را حذف کنید
            yield product_data

        # مدیریت صفحه‌بندی: اگر لینک صفحهٔ نظرات بعدی وجود داشت، آن را دنبال کنید
        next_page = response.css('ul.a-pagination li.a-last a::attr(href)').get()
        if next_page:
            next_url = urljoin('AMAZON_BASE_URL', next_page)
            yield scrapy.Request(url=next_url, callback=self.parse_reviews, meta={'asin': asin})

توضیح مرحله‌ای کد:

  • start_requests: ورودی‌ها — لیست ASIN؛ خروجی — درخواست‌هایی به صفحهٔ جستجو برای هر ASIN.
  • discover_product_urls: ایده — از نتایج جستجو URL محصول را استخراج کند و درخواست جدیدی به آن بفرستد. در این مرحله رفع ابهامات ناشی از ریدایرکت انجام می‌شود.
  • parse_reviews: عناصر مربوط به هر نظر را جمع‌آوری و یک دیکشنری خروجی تولید می‌کند؛ سپس اگر لینک صفحهٔ بعدی وجود داشت ادامه می‌دهد.
  • نکته: سلکتورهای CSS بسته به منطقه، زبان و تغییرات صفحه ممکن است متفاوت باشند؛ همواره با inspector مرورگر خود، سلکتورها را تست کنید.

نکات مهم دربارهٔ pagination و پایداری

صفحات نظرات معمولاً به صورت صفحه‌ای ارائه می‌شوند؛ برای پوشش کامل باید لینک «بعدی» را دنبال کنید. اگر API یا پارامترهای جاوااسکریپتی وجود دارد که صفحه‌بندی را کنترل می‌کنند، می‌توان از rendering با headless browser یا از آن پارامترها استفاده کرد.

برای پایداری بهتر:

  • حداکثر تلاش (retry) را به ازای هر درخواست کنترل کنید و از backoff نمایی استفاده کنید.
  • درخواست‌ها را با محدودیت نرخ (rate limit) و تأخیر تصادفی ارسال کنید تا الگوهای انسانی تقلید شود.
  • حواستان به تغییرات DOM باشد و تست‌های خودکار برای سلکتورها بنویسید.

ذخیره‌سازی: پیکربندی FEEDS در settings.py

با قابلیت Feed Export در Scrapy می‌توانید خروجی را مستقیم به فایل CSV، JSON یا به سیستمی مانند S3 ارسال کنید. نمونهٔ پیکربندی ساده:

FEEDS = {
    'data/%(name)s_%(time)s.csv': {'format': 'csv'},
}

توضیح: این تنظیم هنگام اجرای اسپایدر یک فایل در پوشهٔ data می‌سازد که نام آن شامل نام اسپایدر و زمان اجراست. برای ارسال به S3 یا دیتابیس باید تنظیمات مخصوص آن سرویس و کلیدها را در پروژه اضافه کنید (در محیط‌های تولیدی از متغیرهای محیطی برای نگهداری کلیدهای محرمانه استفاده کنید).

عبور از محافظت‌های ضدبات (Anti-bot)

Amazon از مکانیزم‌های مختلف برای تشخیص بات‌ها استفاده می‌کند: نرخ درخواست، کپچا، fingerprint مرورگر و ریدایرکت‌های لاگین. چند راهکار عملی:

  • استفاده از پراکسی‌های چرخشی (rotating proxies) تا IP تغییر کند.
  • چرخش User-Agent و هدرهای مرورگر برای هر درخواست.
  • در صورت نیاز رندر کردن جاوااسکریپت با headless browser و مدیریت cookie/session.
  • پیاده‌سازی تشخیص بن شدن (ban detection) و منطق آبشاری برای پراکسی‌ها.

نمونهٔ دستور آزمایشی برای تست پروکسی (از نام‌گذاری جایگزین برای URLها استفاده کنید):

curl "PROXY_API_ENDPOINT?api_key=YOUR_API_KEY&url=TARGET_URL"

نکته امنیتی: هرگز کلیدهای API را در کد منبع عمومی قرار ندهید؛ از متغیرهای محیطی یا سرویس‌های مدیریت اسرار استفاده کنید.

ادغام با پکیج‌های پروکسی و مانیتورینگ

اگر از یک SDK یا middleware آماده استفاده می‌کنید (مثلاً یک Proxy SDK برای Scrapy)، معمولاً به این شکل آن را در 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,
}

DOWNLOADER_MIDDLEWARES.update({
    'scrapeops_scrapy.middleware.retry.RetryMiddleware': 550,
    'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,
})

توضیح: با این تنظیمات، منطق retry و پروکسی از SDK استفاده می‌کند و داده‌های مانیتورینگ ارسال می‌شود. مقداردهی صحیح اولویت‌ها (اعداد) مهم است؛ ترتیب اجرای میدلورها به این مقادیر وابسته است.

مدیریت خطا، همزمانی و performance

برای بهینه‌سازی عملکرد و کاهش ریسک بن شدن:

  • مقادیر CONCURRENT_REQUESTS و DOWNLOAD_DELAY را تنظیم کنید.
  • برای هر پراکسی نرخ مجاز جداگانه در نظر بگیرید.
  • از پردازش استریم (streaming) برای نوشتن مستقیم به فایل/DB استفاده کنید تا حافظه مصرف نشود.
  • در صورتی که نیاز به پردازش متن (NLP، sentiment) بعد از جمع‌آوری دارید، آن را در یک خط لوله‌ی جدا اجرا کنید تا اسکریپر سبک بماند.

اجرای لوکال و در کلود؛ زمانبندی

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

  • اجرای دوره‌ای با Cron یا job scheduler داخلی سرویس ابری
  • استفاده از سرویس‌های CI/CD یا job schedulers که اجرا و مانیتورینگ را یکپارچه می‌کنند

نکته‌ی عملی: ابتدا اجرای آزمایشی با حجم کم و مانیتورینگ کامل راه‌اندازی کنید و سپس تعداد همزمانی و دفعات اجرا را افزایش دهید تا رفتار محافظت‌های سایت مشخص شود.

نکات اخلاقی و حقوقی

هنگام اسکریپ کردن داده‌ها به قوانین مالکیت محتوا، شرایط استفاده سایت و حریم خصوصی کاربران توجه کنید. برای دسترسی خودکار در مقیاس بالا بهتر است از API رسمی (در صورت وجود) استفاده کنید یا مجوزهای لازم را کسب کنید.

جمع‌بندی

این راهنما یک مسیر عملی برای اسکریپ کردن نظرات Amazon با Scrapy نشان داد: از استفاده از ASINها و جستجو برای کشف آدرس محصول تا استخراج نظرات، مدیریت صفحه‌بندی، ذخیره‌سازی خروجی و عبور از مکانیزم‌های ضدبات. توصیه‌های کلیدی: سلکتورهای خود را مرتباً تست کنید، از پراکسی و چرخش هدرها استفاده کنید، خروجی را با FEEDS مدیریت کنید و مانیتورینگ برای پایداری تولیدی فراهم کنید. با رعایت این موارد می‌توانید یک pipeline قابل اتکا برای اسکریپ کردن نظرات بسازید.

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