خانه/مقالات/Scrapy در برابر Requests+BeautifulSoup برای اسکریپینگ
استخراج داده
beautifulsoup
برگشت به مقاله‌ها

Scrapy در برابر Requests+BeautifulSoup برای اسکریپینگ

Scrapy در برابر Requests+BeautifulSoup برای اسکریپینگ
این مقاله به‌صورت عملی و فنی تفاوت‌های Scrapy و ترکیب Requests+BeautifulSoup را برای اسکریپینگ در پایتون بررسی می‌کند؛ شامل نصب، کد نمونه، مطالعه موردی استخراج لیست‌های GPU از eBay، و نکات عملکردی و امنیتی برای انتخاب مناسب‌ترین ابزار بر اساس مقیاس و نیاز پروژه.
آسان اسکریپ آسان اسکریپ
1405-05-24

مقدمه

در این مقاله یک راهنمای عملی و فنی برای توسعه‌دهندگان پایتون سطح متوسط آماده کرده‌ایم تا تفاوت‌ها، نقاط قوت و محدودیت‌های دو رویکرد رایج در اسکریپینگ را بفهمند: فریم‌ورک کامل 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) توسعه دهم.

مطالب مرتبط

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