مقدمه
در این مقاله یک راهنمای عملی و فنی برای توسعهدهندگان پایتون سطح متوسط آماده کردهایم تا تفاوتها، نقاط قوت و محدودیتهای دو رویکرد رایج در اسکریپینگ را بفهمند: فریمورک کامل Scrapy و ترکیب سبک و محبوب Requests بههمراه BeautifulSoup. هدف این است که پس از خواندن، بتوانید براساس مقیاس پروژه، نیاز به همزمانی، مدیریت خطا و الزامات نگهداری، ابزار مناسب را انتخاب کنید.
در پایان مقاله یاد میگیرید چگونه هر کدام را نصب و اجرا کنید، یک نمونه کاربردی (استخراج لیستهای GPU از eBay) را با هر دو روش پیادهسازی کنید، و بهترین روشهای عملکرد، خطایابی و رعایت اخلاق و قوانین سایتها را بهکار بگیرید.
چکیدهٔ سریع (TL;DR)
- Scrapy: فریمورکی کامل برای اسکریپینگ در مقیاس بزرگ؛ دارای پشتیبانی async، معماری اسپایدر/پایپلاین و مناسب برای پروژههای پایدار و قابل توسعه.
- Requests + BeautifulSoup: ترکیبی مینیمال، بسیار خوانا و عالی برای پروتوتایپها یا وظایف کوچک؛ اما بهصورت پیشفرض همزمانی و مدیریت درخواستها را ندارد.
معرفی Scrapy
Scrapy یک فریمورک کامل و با بهرهوری بالا برای خزیدن و اسکریپینگ است. معماری آن براساس اسپایدرها، صف درخواستها و پایپلاینها طراحی شده تا عملیات پیچیده و طولانیمدت را مدیریت کند.
مزایا و محدودیتهای Scrapy
- مزایا:
- پشتیبانی کامل از همزمانی (async) و مدیریت اتصالات؛ برای استخراج سریع صدها یا هزاران صفحه مناسب است.
- پایپلاینها و middlewares برای پردازش، ذخیرهسازی، و مدیریت پروکسی/هدرها دارد.
- مناسب برای نگهداری، لاگینگ و تست در پروژههای بزرگ.
- محدودیتها:
- یادگیری اولیه کمی دشوارتر است (پروژه، اسپایدرها، آیتمها، پایپلاینها).
- انتخاب المنتها با css/xpath گاهی کمسطح و نیازمند دقت است.
- برای کارهای خیلی کوچک و یکباره ممکن است اضافهکاری باشد.
نصب و شروع با Scrapy
نصب ساده است:
pip install scrapy
ایجاد پروژه اولیه:
scrapy startproject myproject
مثال: یک اسپایدر ساده با Scrapy
from pathlib import Path
import scrapy
class QuotesSpider(scrapy.Spider):
name = 'quotes'
def start_requests(self):
urls = ['https://quotes.toscrape.com/']
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response):
quotes = response.css('.quote > span::text').getall()
self.log('-------quotes---------')
for quote in quotes:
if quote.replace(' ', '') != 'by':
self.log(quote)
توضیح کد:
- ورودی: لیست آدرسها در start_requests.
- خروجی: لاگِ متون استخراجشده و امکان بازگرداندن آیتمها به پایپلاین برای ذخیرهسازی.
- خطبهخط: کلاس اسپایدر را تعریف میکنیم، در start_requests درخواستها را تولید میکنیم، و در parse با انتخابگر CSS عناصر مربوطه را میخوانیم و پردازش میکنیم.
نکتههای عملی:
- برای نوشتن نتایج بهتر است از آیتمها (items.py) و پایپلاینها (pipelines.py) استفاده کنید تا تبدیل و ذخیرهٔ دادهها جدا از استخراج باشد.
- برای استفاده از پروکسی، هدرها یا ریتلیمیت از middlewares بهره ببرید — این موضوع وقتی در مقیاس کار میکنید اهمیت پیدا میکند.
معرفی Requests + BeautifulSoup
این ترکیب برای مواقعی مناسب است که میخواهید سریع صفحهای را بخوانید و با حداقلورود به طراحی پروژه، نتایج را استخراج کنید. Requests وظیفهٔ ارسال درخواست HTTP را دارد و BeautifulSoup کار پارسینگ HTML را انجام میدهد.
مزایا و محدودیتهای Requests+BeautifulSoup
- مزایا:
- بسادگی و خوانایی بالا؛ راهاندازی و دیباگ سریع.
- کنترل ساده روی هدرها، سشنها و پارامترها با requests.Session.
- محدودیتها:
- بهصورت پیشفرض همزمانی ندارد؛ برای همزمانی باید از aiohttp یا threading/process استفاده کنید.
- در پروژههای بزرگ نگهداری و مدیریت خطا و retry بهصورت دستی اضافه میشود.
نصب و نمونهٔ ساده
pip install requests beautifulsoup4
مثال: اسکریپت ساده با Requests و BeautifulSoup
import requests
from bs4 import BeautifulSoup
response = requests.get('https://quotes.toscrape.com/')
soup = BeautifulSoup(response.content, 'html.parser')
quotes = soup.find_all('div', class_='quote')
for quote in quotes:
print(quote.text)
توضیح:
- ورودی: یک URL برای requests.get.
- خروجی: اشیاء BeautifulSoup که متن یا فیلدهای دیگر را ارائه میدهند.
- برای مقیاس بالاتر باید timeout، retry، session و سازوکار rate-limiting را دستی پیاده کنید.
مقایسهٔ فنی و نکات عملکردی
- سرعت و همزمانی: Scrapy دارای معماری async و concurrency داخلی است؛ Requests بهصورت sync عمل میکند و برای مقیاس باید از افزونهها یا تغییر معماری استفاده شود.
- پیچیدگی پروژه: Scrapy برای پروژههای چندمنبعه، مدیریت صف و ذخیرهٔ منظم داده مناسبتر است؛ Requests عالی برای یکبار استخراج یا پروتوتایپ است.
- خطا و پایداری: Scrapy ابزارهای لاگ و retry و پایپلاین دارد؛ با Requests باید اکسپشنها، وضعیتهای شبکه و retry را دستی مدیریت کنید.
- امنیت و رعایت قوانین: همیشه به robots.txt، سیاستهای سایت و محدودیتهای قانونی توجه کنید؛ نرخ درخواست را محدود کنید و از هدر User-Agent مناسب استفاده کنید.
- پرفورمنس: برای ارتباطهای زیاد از keep-alive، pooling و پروکسیهای چرخشی بهره ببرید؛ در Scrapy از CONCURRENT_REQUESTS و DOWNLOAD_DELAY بهره ببرید.
مطالعهٔ موردی: استخراج لیستهای GPU از eBay
این مطالعه نشان میدهد که در یک تست روی یک دستگاه معمولی، Scrapy بهطرز محسوسی سریعتر عمل میکند (مثلاً ~11 ثانیه در مقابل ~107 ثانیه برای Requests+BeautifulSoup در نمونه اولیه). در ادامه دو پیادهسازی تمیز را بازنویسی کردهایم.
نسخهٔ Scrapy برای eBay (GPU)
from pathlib import Path
import scrapy
from urllib.parse import urlencode
API_KEY = 'YOUR-SUPER-SECRET-API-KEY'
def get_proxy_url(url):
payload = {'api_key': API_KEY, 'url': url}
proxy_url = 'https://proxy.scrapeops.io/v1/?' + urlencode(payload)
return proxy_url
class GpuSpider(scrapy.Spider):
name = 'gpus'
def start_requests(self):
base = 'https://www.ebay.com/b/Computer-Graphics-Cards/27386/bn_661796?_pgn='
urls = [f'{base}{i}' for i in range(1, 11)]
for url in urls:
yield scrapy.Request(url=get_proxy_url(url), callback=self.parse)
def parse(self, response):
path = Path('gpu_results.txt')
if not path.is_file():
path.write_text('')
listings = response.css('h3::text').getall()
with path.open('a', encoding='utf-8') as outfile:
for listing in listings:
outfile.write(f'{listing}\n')
تحلیل کد:
- get_proxy_url: ورودی یک URL عادی میگیرد و آن را به URL پروکسی تبدیل میکند. این اجازه میدهد درخواستها از طریق سرویس پروکسی ارسال شوند.
- start_requests: لیست صفحات (1 تا 10) را میسازد و هر آدرس را با پروکسی فراخوانی میکند.
- parse: با CSS selector متن داخل تگ h3 را میگیرد و به فایل اضافه میکند. استفاده از Path و بازکردن فایل با encoding مناسب، پایداری را افزایش میدهد.
- اجرای Scrapy: از scrapy crawl gpus استفاده کنید.
نسخهٔ Requests + BeautifulSoup برای eBay (GPU)
import requests
from bs4 import BeautifulSoup
from urllib.parse import urlencode
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import time
API_KEY = 'YOUR-SUPER-SECRET-API-KEY'
def get_proxy_url(url):
payload = {'api_key': API_KEY, 'url': url}
return 'https://proxy.scrapeops.io/v1/?' + urlencode(payload)
# Session با Retry
session = requests.Session()
retries = Retry(total=3, backoff_factor=0.5, status_forcelist=[429, 500, 502, 503, 504])
session.mount('https://', HTTPAdapter(max_retries=retries))
base = 'https://www.ebay.com/b/Computer-Graphics-Cards/27386/bn_661796?_pgn='
with open('gpu_results.txt', 'w', encoding='utf-8') as f:
f.write('')
for page in range(1, 11):
url = get_proxy_url(f'{base}{page}')
try:
resp = session.get(url, timeout=10)
resp.raise_for_status()
except Exception as e:
print('Request failed for page', page, e)
time.sleep(1)
continue
soup = BeautifulSoup(resp.content, 'html.parser')
listings = soup.find_all('h3')
with open('gpu_results.txt', 'a', encoding='utf-8') as outfile:
outfile.write(f'Page {page}\n')
for listing in listings:
outfile.write(listing.get_text(strip=True) + '\n')
time.sleep(0.5) # rate limiting ساده
نکات فنی در این نسخه:
- استفاده از Session و Retry برای افزایش پایداری در برابر خطاهای گذرا.
- قرار دادن timeout، backoff و rate limiting برای جلوگیری از بلاک شدن.
- برای همزمانی واقعی میتوان از aiohttp یا مسیرهای چندپردازهای استفاده کرد، که کدنویسی پیچیدهتر اما سریعتر است.
نکات عملی، امنیت و بهترین روشها
- همیشه قبل از اسکریپ کردن، robots.txt و شرایط استفاده را بررسی کنید و نرخ درخواست (Rate) را متناسب با سرویس تنظیم کنید.
- برای افزایش شانس موفقیت و جلوگیری از بلاک شدن: هدر User-Agent، پراکسیهای چرخشی، و استراتژیهای ریت-لیمیت و بکآف را پیاده کنید.
- برای دادههای حساس از رمزنگاری و مدیریت امن کلیدها (مانند API_KEY) استفاده کنید و آنها را در کد هاردکد نکنید؛ از متغیرهای محیطی یا سرویسهای مدیریت اسرار بهره ببرید.
- لاگگیری و متریکها مهم است: تعداد درخواستها، نرخ خطا، تایم پاسخ و زمان اجرا را ثبت کنید تا نقاط گلوگاه را پیدا کنید.
- برای عملیات طولانیمدت از صفکاری (queue) و بررسی وضعیت (monitoring) استفاده کنید تا در صورت خطا بتوانید ادامه یا ریتراِی را انجام دهید.
جمعبندی و توصیهها
اگر پروژهٔ شما نیاز به اسکریپینگ در مقیاس، همزمانی، و نگهداری بلندمدت دارد، Scrapy اغلب گزینهٔ مناسبتری است. اگر میخواهید سریع یک پروتوتایپ بسازید یا دادهٔ محدودی استخراج کنید، ترکیب Requests و BeautifulSoup سادهتر و سریعتراست برای توسعه.
در هر دو حالت روی مدیریت خطا، نرخ درخواست، و رعایت قوانین سایتها تمرکز کنید؛ همچنین اگر عملکرد مهم است، از پروفایلینگ، استفاده از سشنها، و در Scrapy از تنظیمات concurrency و middlewareها بهره ببرید.
موفق باشید؛ اگر میخواهید میتوانم همین نمونهها را برای حالتهای پیشرفتهتر (AJAX، صفحهبندی نامتعارف، استخراج از API داخلی یا استفاده از ابزارهایی مانند aiohttp) توسعه دهم.





