

در این مقاله گامبهگام میفهمیم چگونه با Scrapy یک اسکریپر عملی برای Walmart بسازیم. هدف این راهنما برای یک توسعهدهندهٔ پایتون سطح متوسط است که میخواهد از طراحی معماری تا ذخیرهسازی، مقابله با محافظت ضد ربات و استقرار را یاد بگیرد. در پایان این مطلب شما یک اسپایدر کشف محصولات (crawler) و یک اسکریپر صفحات محصول خواهید داشت، میدانید چطور دادهها را ذخیره کنید و چه روشهایی برای پایداری و عبور از محدودیتها استفاده کنید.
برای بیشتر موارد عملی، ترکیب دو مولفه ساده و قابل نگهداری کافی است:
تصمیمهای مهم معماری بر اساس این پرسشها گرفته میشوند: محدودهٔ داده، نرخ بهروزرسانی، حجم داده و توان فنی. برای مثال این راهنما فرض میکند حجم متوسط و نیاز روزانه وجود دارد و خروجی را بهصورت CSV (یا از طریق FEEDS به S3/DB) ذخیره میکنیم.
Walmart نتایج جستجو را با پارامترهای ساده در URL ارائه میدهد: q (کلمهٔ جستجو)، sort و page. هر صفحه تا 40 محصول بازمیگرداند و حداکثر 25 صفحه قابل دسترسی است—پس باید تعداد صفحات را محاسبه و صفحهبندی کنیم.
نکتهٔ کلیدی: دادهها در تگی با id __NEXT_DATA__ بهصورت JSON توکار قرار دارند. بهترین روش خواندن همین JSON و استخراج مسیر مناسب است.
import json
import math
import scrapy
from urllib.parse import urlencode
class WalmartSpider(scrapy.Spider):
name = "walmart"
def start_requests(self):
# ورودی: لیست کلمات کلیدی
keyword_list = ["ipad"]
for keyword in keyword_list:
payload = {"q": keyword, "sort": "best_seller", "page": 1, "affinityOverride": "default"}
walmart_search_url = "https://www.walmart.com/search?" + urlencode(payload)
# خروجی: درخواست به صفحهٔ اول نتایج
yield scrapy.Request(url=walmart_search_url, callback=self.parse_search_results, meta={"keyword": keyword, "page": 1})
def parse_search_results(self, response):
page = response.meta["page"]
keyword = response.meta["keyword"]
script_tag = response.xpath('//script[@id="__NEXT_DATA__"]/text()').get()
if script_tag is not None:
json_blob = json.loads(script_tag)
# ادامهٔ پردازش در بخشهای بعدی
توضیح مختصر: ورودی تابع start_requests لیست کلمات کلیدی است؛ هر درخواست به parse_search_results فرستاده میشود که متن تگ اسکریپت را میخواند و آنرا به JSON تبدیل میکند.
پس از بارگذاری JSON، مسیر دادهٔ نتایج معمولاً چیزی شبیه به این است (نمونه مسیر):
json_blob["props"]["pageProps"]["initialData"]["searchResult"]["itemStacks"][0]["items"]
# ادامهٔ داخل parse_search_results
product_list = json_blob["props"]["pageProps"]["initialData"]["searchResult"]["itemStacks"][0]["items"]
# استخراج آدرس محصول و درخواست صفحهٔ محصول
for product in product_list:
walmart_product_url = "https://www.walmart.com" + product.get("canonicalUrl", "").split("?")[0]
yield scrapy.Request(url=walmart_product_url, callback=self.parse_product_data, meta={"keyword": keyword, "page": page})
# صفحهبندی (فقط از صفحهٔ اول تعداد کل را میخوانیم)
if page == 1:
total_product_count = json_blob["props"]["pageProps"]["initialData"]["searchResult"]["itemStacks"][0].get("count", 0)
max_pages = math.ceil(total_product_count / 40)
if max_pages > 25:
max_pages = 25
for p in range(2, max_pages + 1):
payload = {"q": keyword, "sort": "best_seller", "page": p, "affinityOverride": "default"}
walmart_search_url = "https://www.walmart.com/search?" + urlencode(payload)
yield scrapy.Request(url=walmart_search_url, callback=self.parse_search_results, meta={"keyword": keyword, "page": p})
نکتهٔ عملی: حتماً پارامتر جستجو را URL-encode کنید و برای جلوگیری از تکرار URLها canonicalUrl را قبل از '?' برش بزنید.
پس از درخواست هر صفحهٔ محصول، بهجای ساخت سلکتورهای CSS/XPath برای تکتک فیلدها بهتر است همان JSON توکار (__NEXT_DATA__) را ببرید و از مسیر محصول استفاده کنید. مسیر معمول برای دادهٔ محصول:
json_blob["props"]["pageProps"]["initialData"]["data"]["product"]
def parse_product_data(self, response):
script_tag = response.xpath('//script[@id="__NEXT_DATA__"]/text()').get()
if script_tag is None:
return
json_blob = json.loads(script_tag)
raw_product_data = json_blob["props"]["pageProps"]["initialData"]["data"].get("product", {})
# خروجی: دیکشنری قابل ذخیره
item = {
'keyword': response.meta.get('keyword'),
'page': response.meta.get('page'),
'id': raw_product_data.get('id'),
'type': raw_product_data.get('type'),
'name': raw_product_data.get('name'),
'brand': raw_product_data.get('brand'),
'averageRating': raw_product_data.get('averageRating'),
'manufacturerName': raw_product_data.get('manufacturerName'),
'shortDescription': raw_product_data.get('shortDescription'),
'thumbnailUrl': (raw_product_data.get('imageInfo') or {}).get('thumbnailUrl'),
'price': (raw_product_data.get('priceInfo') or {}).get('currentPrice', {}).get('price'),
'currencyUnit': (raw_product_data.get('priceInfo') or {}).get('currentPrice', {}).get('currencyUnit')
}
yield item
توضیح: این تابع یک item تولید میکند که قابل ارسال به pipeline یا Scrapy FEEDS است. توجه کنید که فیلدهای توخالی را با گاردهای ساده (مثل .get و OR {}) محافظت کردهایم تا خطاهای KeyError کاهش یابد.
برای خروجی سریع و قابل تحلیل، از FEEDS در Scrapy استفاده کنید. این تنظیم یک فایل CSV با نام داینامیک بر اساس نام اسپایدر و زمان اجرا ایجاد میکند.
# settings.py
FEEDS = {
'data/%(name)s_%(time)s.csv': {
'format': 'csv',
},
}
برای اجرای اسپایدر از خط فرمان:
scrapy crawl walmart -o walmart_data.csv
اگر میخواهید به پایگاه داده بنویسید، یک pipeline بسازید که اتصال به MySQL/Postgres را مدیریت کند و هر آیتم را داخل جدول مربوطه درج نماید. مزیت استفاده از پایپلاین: جمعآوری خطا و بازسازی سریع دادههای نیمهکامل.
Walmart دارای محافظتهایی است که در صورت ارسال درخواستهای زیاد ممکن است شما را بلوک کنند. ترکیبی از روشهای زیر به افزایش پایداری کمک میکند:
مثال: یک API پراکسی که آدرس را میگیرد و صفحهٔ نهایی را برمیگرداند (curl نمونه):
curl 'https://proxy.example.com/v1/?api_key=YOUR_API_KEY&url=https://walmart.com'
برای ادغام سریع میتوانید از SDK پراکسی بهصورت Downloader Middleware استفاده کنید. مثال نصب و تنظیم (نمونهٔ کلی):
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,
}
هشدار امنیتی و اخلاقی: قبل از اسکریپ کردن هر سایت، قوانین سرویس، شرایط استفاده و قوانین محلی را بررسی کنید و بارگزاری روی سرور را تا حد امکان نرم و آگاهانه انجام دهید.
چند معیار مهم برای پایش عملیاتی: نرخ خطا، تعداد درخواستها، میانگین زمان پاسخ، تعداد CAPTCHAها و نرخ موفقیت ذخیرهسازی. ابزارهای مانیتورینگ مخصوص Scrapy میتوانند این متریکها را به داشبورد بفرستند و هشدار دهی روشن تنظیم کنند.
# settings.py (نمونهٔ ادغام با ScrapeOps Monitor)
EXTENSIONS = {
'scrapeops_scrapy.extension.ScrapeOpsMonitor': 500,
}
DOWNLOADER_MIDDLEWARES.update({
'scrapeops_scrapy.middleware.retry.RetryMiddleware': 550,
'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,
})
SCRAPEOPS_API_KEY = 'YOUR_API_KEY'
با افزودن مانیتور و middleware مناسب میتوانید خطاهای خودکار، retry هوشمند و گزارش رویدادها را مدیریت کنید.
گزینههای معمول برای اجرا و زمانبندی:
در محیط تولید حتماً موارد زیر را رعایت کنید: محدودیت نرخ، بازهٔ retry، مدیریت لاگها و مکانیزم ریاستارت خودکار برای اسپایدرها.
پیادهسازی یک اسکریپر Walmart با Scrapy شامل سه بخش اصلی است: کشف URLها از صفحات جستجو، استخراج داده از بلوک JSON صفحات محصول و ذخیرهٔ منظم نتایج. برای تولیدیسازی پروژه باید به ذخیرهسازی مناسب، مدیریت محافظت ضد-ربات و نظارت پرداخته و از بهترین روشهای retry، backoff و پراکسی چرخشی استفاده کنید. قدم بعدی: پیادهسازی pipeline برای ذخیره در DB، اضافهکردن لاگ و مانیتورینگ کامل و استقرار در محیطی با زمانبندی اتوماتیک.


