مقدمه
در این راهنمای عملی برای توسعهدهندگان Python سطح متوسط، قدمبهقدم میسازیم یک سیستم وب اسکریپینگ با Scrapy که صفحات جستجو و محصول آمازون را پیدا و دادههای محصول را استخراج میکند. در انتها شما یک اسپایدر که فهرست محصولات را کشف میکند، صفحات محصول را پردازش میکند، دادهها را ذخیره میکند و چند نکته عملی برای عبور از محافظتهای ضدربات خواهید داشت.
آنچه یاد میگیرید:
- طراحی معماری ساده اما قابل تولید برای اسکریپ محصولات
- نوشتن crawler برای صفحات جستجو و استخراج URL یا ASIN
- نوشتن parser برای صفحه محصول و استخراج قیمت، تصاویر، ویژگیها و متادیتا
- ذخیرهسازی خروجی با Scrapy Feeds و نکات اتصال به DB/S3
- روشهای مقابله با بلاک شدن، مدیریت پروکسی و مانیتورینگ
طراحی معماری کلی
برای اغلب پروژههای مانیتورینگ قیمت یا رتبهبندی، معماری پیشنهادی ساده و مؤثر شامل دو وظیفه اصلی است: یک product discovery crawler که URLهای محصولات را از صفحات جستجو پیدا میکند، و یک product data scraper که صفحات محصول را باز کرده و دادهها را استخراج میکند. این جداسازی به شما اجازه میدهد میزان درخواست به صفحات محصول را کنترل کنید و مجدداً URLها را از منابع دیگر (مثلاً لیست ASIN) تغذیه کنید.
مولفههای کلیدی:
- لیست کلیدواژهها یا ASINها بهعنوان ورودی
- صفحهبندی (pagination) برای صفحات جستجو
- ذخیرهسازی مرحلهای (مثلاً ابتدا URLها، سپس داده)
- مدیریت خطا، retry و نرخ درخواست (rate limiting)
درک صفحات جستجو و ASIN
URL جستجوی آمازون معمولاً پارامترها مثل k (keyword) و page دارد؛ مثال ساده:
https://www.amazon.com/s?k=ipad&page=1هر صفحه معمولاً تا حدود 20 محصول نشان میدهد. دو رویکرد برای فهرست محصولات وجود دارد:
- استخراج URL کامل محصول از نتایج جستجو و دنبالکردن آن
- استخراج کد ASIN و ساختن آدرسهایی مثل https://www.amazon.com/dp/ASIN
استفاده از ASINها مزیت ثبات URL را دارد (کمتر پارامتر اضافه در URL) و گاهی برای بازیابی سریع صفحات محصول یا نظرات مناسبتر است.
نمونه: ساخت crawler صفحات جستجو (Scrapy)
ایده کلی: از start_requests برای ایجاد درخواست به صفحه اول هر کلیدواژه استفاده میکنیم، سپس در callback فهرست محصولات صفحه را میگیریم و درخواست برای صفحات بعد را زمانبندی میکنیم.
import scrapy
from urllib.parse import urljoin
class AmazonSearchProductSpider(scrapy.Spider):
name = "amazon_search_product"
def start_requests(self):
keyword_list = ['ipad']
for keyword in keyword_list:
url = f'https://www.amazon.com/s?k={keyword}&page=1'
yield scrapy.Request(url=url, callback=self.discover_product_urls, meta={'keyword': keyword, 'page': 1})
def discover_product_urls(self, response):
page = response.meta['page']
keyword = response.meta['keyword']
# استخراج محصولات از نتایج جستجو
search_products = response.css("div.s-result-item[data-component-type=s-search-result]")
for product in search_products:
rel = product.css("a::attr(href)").get()
if not rel:
continue
product_url = urljoin('https://www.amazon.com/', rel).split('?')[0]
# درخواست صفحه محصول و ارسال metadata مرتبط
yield scrapy.Request(url=product_url, callback=self.parse_product_data, meta={'keyword': keyword, 'page': page})
# اگر صفحه اول است، صفحات بعد را enqueue میکنیم (نمونه ساده)
if page == 1:
available_pages = response.xpath('//*[contains(@class, "s-pagination-item") and not(contains(@class, "s-pagination-separator"))]/text()').getall()
if available_pages:
last_page = available_pages[-1]
for page_num in range(2, int(last_page) + 1):
next_url = f'https://www.amazon.com/s?k={keyword}&page={page_num}'
yield scrapy.Request(url=next_url, callback=self.discover_product_urls, meta={'keyword': keyword, 'page': page_num})
شرح کد و ورودی/خروجی:
- ورودی: لیست کلمات کلیدی در start_requests.
- خروجی: درخواستهایی به صفحات محصول (که در callback بعدی پردازش میشوند).
- نقش هر تابع: start_requests نقاط شروع را میسازد، discover_product_urls URLهای محصول را استخراج و صفحهبندی را مدیریت میکند.
نکات عملی:
- در استخراج از CSS/XPath همیشه تست دستی روی HTML واقعی انجام دهید چون کلاسها و ساختار آمازون پویاست.
- قبل از استفاده در مقیاس، نرخ درخواست (DOWNLOAD_DELAY) و همزمانی را تنظیم کنید.
اضافه کردن callback و ساخت parser صفحه محصول
بعد از کشف URLها، برای هر صفحه محصول باید دادههای هدف (نام، قیمت، امتیاز، تعداد رأی، تصاویر، ویژگیها، و دادههای واریانت) را استخراج کنیم. در ادامه یک پیادهسازی نمونه و سپس توضیح آن آمده است.
import json
import re
import scrapy
from urllib.parse import urljoin
class AmazonSearchProductSpider(scrapy.Spider):
name = "amazon_search_product"
# ... start_requests و discover_product_urls مانند بالا ...
def parse_product_data(self, response):
# استخراج تصاویر که معمولاً در جاواسکریپت صفحه بهصورت JSON جاسازی شدهاند
image_json_match = re.findall(r"colorImages':.*'initial':\s*(\[.+?\])},\n", response.text)
image_data = []
if image_json_match:
try:
image_data = json.loads(image_json_match[0])
except Exception:
image_data = []
# مثال استخراج bullets
feature_bullets = [b.strip() for b in response.css("#feature-bullets li ::text").getall()]
# استخراج قیمت با چند selector احتمالی
price = response.css('.a-price span[aria-hidden="true"] ::text').get()
if not price:
price = response.css('.a-price .a-offscreen ::text').get()
product_data = {
"name": response.css("#productTitle::text").get(default="").strip(),
"price": price,
"stars": response.css("i[data-hook=average-star-rating] ::text").get(default="").strip(),
"rating_count": response.css("div[data-hook=total-review-count] ::text").get(default="").strip(),
"feature_bullets": feature_bullets,
"images": image_data,
}
self.logger.info(f"Scraped Data: {product_data}")
yield product_data
شرح:
- چون آمازون بخشی از دادهها را داخل اسکریپت JS قرار میدهد، از re برای یافتن JSON تصاویر استفاده میکنیم و سپس json.loads.
- برای قیمت و سایر فیلدها چند selector مختلف نوشتهایم تا در شرایط متفاوت جواب دهد.
- خروجی این تابع یک دیکشنری سادهٔ JSON-قابل-صادرات است که توسط Scrapy در خروجی Feeds ذخیره میشود.
نکات پایدارسازی:
- تمام نقاطی که احتمالاً None یا تغییر ساختار دارد را با مقدار پیشفرض بگیرید تا اسپایدر کرش نکند.
- برای فیلدهای حساس (مثلاً قیمت) چند استراتژی fallback داشته باشید.
ذخیرهسازی خروجی: Scrapy FEEDS و DB/S3
سادهترین روش ذخیرهسازی در زمان توسعه، استفاده از Scrapy Feed Exports است. نمونه تنظیمات در settings.py:
# settings.py
FEEDS = {
'data/%(name)s_%(time)s.csv': {'format': 'csv'},
}
توضیح: با این تنظیم هر بار که اسپایدر اجرا شود یک فایل CSV جدید در پوشه data ایجاد میشود. برای ذخیره به S3 یا دیتابیس باید backend مناسب را تنظیم یا pipeline بنویسید.
مثایل جایگزین:
- برای S3 از URI مثل s3://bucket-name/path/%(name)s_%(time)s.json در FEEDS استفاده کنید و دسترسی AWS را در محیط فراهم کنید.
- برای MySQL/Postgres بهتر است یک Item Pipeline بنویسید که اتصال به DB، تراکنش و بازگشت را مدیریت کند (batch insert برای performance).
مقابله با محافظتهای ضدربات
آمازون از تکنیکهای مختلف برای شناسایی رباتها استفاده میکند: تشخیص نرخ بالای درخواست، کپچا، بررسی هدرها و رفتار مرورگر. راهکارها به سه دسته کلی تقسیم میشوند:
- کاهش نشانههای ربات: تنظیم DOWNLOAD_DELAY، تغییر USER_AGENT و استفاده از headless browser تنها اگر لازم باشد.
- چرخش پروکسی و IP: استفاده از پروکسیهای روتیتشونده و انتخاب کشور مورد نظر.
- رندر جاوااسکریپت: برای صفحات پیچیده از headless browser (مثل Playwright یا Selenium) در محدودههایی که لازم است استفاده کنید.
نمونه سادهٔ CURL برای ارسال درخواست از طریق یک API پروکسی (نمونهٔ عمومی):
curl 'https://proxy.example.com/v1/?api_key=YOUR_API_KEY&url=https://amazon.com'
هشدارهای قانونی و اخلاقی: قبل از اسکریپ کردن مطمئن شوید قوانین سایت و سیاستهای استفاده را بررسی کردهاید. همیشه توجیۀ تجاری و اخلاقی داشته باشید و دادهپراکنی در مقیاس بزرگ بدون مجوز میتواند پیامد حقوقی داشته باشد.
تنظیمات عملی Scrapy برای پایداری و کارایی
چند تنظیم پیشنهادی در settings.py برای بهبود پایداری:
# settings.py (نمونه)
CONCURRENT_REQUESTS = 8
DOWNLOAD_DELAY = 1 # تاخیر بین درخواستها
RETRY_ENABLED = True
RETRY_TIMES = 3
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 1
AUTOTHROTTLE_MAX_DELAY = 10
# User agents: لیستی از user-agent های چرخشی بهتر است در middleware مدیریت شود
توضیح: AUTOThrottle بهصورت داینامیک تاخیرها را تنظیم میکند تا سرور هدف کمتر تحت فشار قرار گیرد و احتمال بلاک کاهش یابد.
مانیتورینگ و پایش
برای اجرا در تولید، حتماً مانیتورینگ داشته باشید: آمار اجرا، نرخ موفقیت درخواستها، زمان پاسخها و آلارم روی افزایش نرخ خطا یا CAPTCHA. میتوانید ابزارهای مانیتورینگ را بهعنوان Extension/Integration در Scrapy اضافه کنید یا لاگها را به یک سرویس خارجی ارسال کنید.
مواردی که باید پایش شوند:
- نرخ خطا (HTTP 4xx/5xx)
- تعداد پاسخهایی که محتوای غیرمنتظره دارند (مثلاً صفحه CAPTCHA)
- زمان اجرای Job و تعداد آیتمهای استخراجشده
استقرار و زمانبندی اجرا
برای اجرای منظم (روزانه/هفتگی) اسپایدرها، گزینههای متداول:
- یک سرور کوچک با cron یا systemd timer
- CI/CD ساده که job را اجرا کند
- پلتفرمهای زمانبندی ابری یا سرویسهای job scheduler که اجرای دورهای و مانیتورینگ را فراهم میکنند
در هر حالت، لاگگیری مرکزی و عادت به ثبت خروجیها (مثلاً آپلود CSV به S3 یا ذخیره در DB) باعث تسهیل واکنش به خطاها میشود.
نکات عملی، بهترین روشها و هشدارها
- Rate-limit و احترام: همیشه با تاخیر منطقی و محدودیت همزمانی کار کنید تا خطر بلاک شدن کمتر شود.
- Fallback و محافظت از کرش: در همه استخراجها مقادیر پیشفرض بدهید و exceptions را مدیریت کنید.
- لاگ و متادیتا: برای هر آیتم متادیتا مثل timestamp، URL منبع و keyword را ثبت کنید تا امکان ردیابی داشته باشید.
- امنیت: کلیدهای API و credentials را در محیط (env vars/secret manager) نگه دارید، نه در سورس کنترل.
- آزمون و نگهداری: صفحات آمازون مرتباً تغییر میکنند؛ برای هر چند هفته یکبار تستی از اسپایدرها بگیرید و سلکتورها را بازبینی کنید.
جمعبندی
در این مقاله از طرح کلی معماری تا نمونه کدهای عملی برای ساخت یک اسپایدر Scrapy برای اسکریپ کردن محصولات آمازون گذر کردیم. نکات کلیدی عبارتند از: جداسازی discovery و scraping برای انعطافپذیری، مدیریت خطا و fallback برای پایداری، استفاده از Feeds برای ذخیره خروجی و در نهایت اجرای محافظهکارانه و استفاده از پروکسیها و مانیتورینگ برای ادامه کار در تولید. حالا میتوانید پیادهسازی را بسازید، آن را با تنظیمات و پروکسیهای مناسب مقاوم کنید و بهتدریج مقیاس دهید.




