خانه/مقالات/اسکریپینگ با BeautifulSoup — راهنمای پایتون
وب اسکریپینگ
beautifulsoup
برگشت به مقاله‌ها

اسکریپینگ با BeautifulSoup — راهنمای پایتون

اسکریپینگ با BeautifulSoup — راهنمای پایتون
این مقاله یک راهنمای عملی و گام‌به‌گام برای اسکریپینگ با BeautifulSoup در پایتون است؛ از نصب و گرفتن HTML با requests تا جست‌و‌جوی DOM با .find/.find_all و .select. نکات مهم عملکردی، امنیتی و مدیریت خطا و مثال‌های کد واقعی هم پوشش داده شده‌اند تا یک اسکریپر قابل‌اعتماد و قابل نگهداری بسازید.
آسان اسکریپ آسان اسکریپ
1405-04-25

مقدمه

در این مقاله قدم‌به‌قدم می‌آموزیم چگونه با BeautifulSoup در پایتون صفحات HTML را پردازش و داده‌ها را استخراج کنیم. هدف این راهنما این است که پس از خواندن آن بتوانید HTML را از وب یا فایل محلی بارگذاری کنید، درخت DOM را جست‌و‌جو کنید و خروجی را به‌صورت ایمن و قابل نگهداری ذخیره کنید. مخاطب این متن توسعه‌دهنده‌ای با سطح متوسط در پایتون است، بنابراین مثال‌های کد و نکات عملی درباره مدیریت خطا، مسائل عملکردی و نکات امنیتی را خواهیم دید.

چیست و چرا BeautifulSoup

BeautifulSoup یک کتابخانهٔ پایتون برای پارس کردن HTML و XML است که سند را به درختی از آبجکت‌های پایتونی تبدیل می‌کند. مزیت اصلی آن سادگی API و تحمل بالا در مقابل HTML نامنظم است؛ اما برای صفحات بسیار بزرگ یا نیازهای پردازش تندتر ممکن است گزینه‌های دیگر (مثل lxml) مناسب‌تر باشند.

چه زمانی از BeautifulSoup استفاده کنیم:

  • HTML‌های ساده یا نیمه‌منظم که نیاز به استخراج ساختاریافته دارند.
  • وقتی می‌خواهیم سریع نمونه‌سازی کنیم یا CSS selectorها را مستقیم تست کنیم.

نصب و مقداردهی اولیه

نصب از طریق pip ساده است:

pip install beautifulsoup4

برای شروع در اسکریپت پایتون:

from bs4 import BeautifulSoup
# html_data می‌تواند رشتهٔ HTML یا bytes باشد
soup = BeautifulSoup(html_data, 'html.parser')

توضیح: ورودی html_data می‌تواند response.content از requests یا محتوای فایل باشد. پارسر پیش‌فرض html.parser است؛ اما برای عملکرد بهتر یا دقیق‌تر می‌توانید lxml را نصب و استفاده کنید (pip install lxml).

گرفتن HTML از وب (requests / httpx)

BeautifulSoup فقط پارسر است و خودش درخواست HTTP نمی‌فرستد. برای دریافت صفحه از اینترنت از کتابخانه‌هایی مثل requests یا httpx استفاده کنید. مثال پایه با requests:

import requests
from bs4 import BeautifulSoup

response = requests.get('https://quotes.toscrape.com/', timeout=10)
# از response.content استفاده می‌کنیم تا مشکلات رمزگذاری کمتر شوند
soup = BeautifulSoup(response.content, 'html.parser')

شرح و نکات:

  • ورودی: URL و پارامترهای اختیاری مثل هدرها و timeout.
  • خروجی: شیء BeautifulSoup که DOM را نگهداری می‌کند.
  • چرا response.content نه response.text: content bytes خام است که پارسر بهتر می‌تواند آن را decode کند، مخصوصاً در صفحات با charset غیرمعمول.
  • همیشه timeout مشخص کنید و برای درخواست‌های تولیدی از مکانیزم retry استفاده کنید (مثلاً requests.adapters یا کتابخانهٔ خارجی).

بارگذاری HTML از فایل

اگر HTML در فایل محلی دارید، کافی است آن را باز کنید و به BeautifulSoup بدهید:

from bs4 import BeautifulSoup
with open('index.html', 'rb') as fp:
    soup = BeautifulSoup(fp.read(), 'html.parser')

نکته: باز کردن با حالت بایت ('rb') و سپس دادن bytes به پارسر به شما کنترل بهتر روی رمزگذاری می‌دهد.

ساختار درختی و روش‌های جست‌وجو

BeautifulSoup سند را به درختی از تگ‌ها و نودها تبدیل می‌کند. سه رویکرد مرسوم برای پیدا کردن عناصر:

  • دسترسی از طریق صفات آبجکت (مثلاً soup.h1).
  • متدهای .find() و .find_all().
  • استفاده از CSS selectors با .select() (پشت صحنه از SoupSieve استفاده می‌شود).

هر روش مزایا و معایب خودش را دارد؛ در ادامه هر کدام را با جزئیات می‌بینیم.

دسترسی با صفات آبجکت

مثال ساده:

# فرض بر این است که soup قبلاً ساخته شده
print(soup.h1)            # اولین h1 را برمی‌گرداند
print(soup.h1.a.string)   # متن درون تگ a داخل h1

معایب این روش:

  1. فقط اولین همسان را برمی‌گرداند، مناسب برای موارد تک عنصری.
  2. برای کوئری‌های پیچیده یا ترکیبی قابل اعتماد نیست.

متدهای .find() و .find_all()

.find() برای گرفتن اولین المان مناسب است و .find_all() همهٔ المان‌های منطبق را در یک لیست برمی‌گرداند.

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

# گرفتن اولین تگ p با کلاس خاص
p = soup.find('p', class_='story')
# ورودی: نام تگ و آرگومان‌های شرطی
# خروجی: یک تگ یا None

# گرفتن همه‌ی لینک‌ها
links = soup.find_all('a')
# خروجی: لیستی از تگ‌ها که می‌توانید با حلقه پردازش کنید
for a in links:
    href = a.get('href')
    text = a.get_text(strip=True)
    # ذخیره یا پردازش مقدارها

استفاده از attrs برای فیلترهای دلخواه:

span = soup.find('span', attrs={'aria-hidden': 'true'})

کوئری‌های تابعی: اگر شرط پیچیده دارید، می‌توانید تابعی تعریف کرده و آن را به find یا find_all بدهید:

def custom_selector(tag):
    return tag.name == 'span' and tag.has_attr('class') and 'target_span' in tag.get('class')

matches = soup.find_all(custom_selector)

مثال عملی: استخراج نقل‌قول‌ها

فرض کنید می‌خواهیم متن همهٔ نقل‌قول‌ها را استخراج کنیم (مثال از quotes.toscrape):

quotes = soup.find_all('span', class_='text')
texts = [q.get_text(strip=True) for q in quotes]
# texts اکنون لیستی از رشته‌هاست

شرح: ورودی class_='text' شرطی است که روی attribute class اعمال می‌شود؛ خروجی لیستی از تگ‌هاست که با comprehension به متن خالص تبدیل می‌شوند.

کوئری‌های CSS با .select()

اگر به CSS selectorها عادت دارید، .select() خروجی لیستی از المان‌ها را برمی‌گرداند و برای مواردی که قبلاً در DevTools تست کرده‌اید مناسب است.

# متن داخل h1 a
h1_text = soup.select('h1 a')[0].get_text()
# تمام نقل‌قول‌ها با CSS
quotes = soup.select('span.text')
texts = [q.get_text(strip=True) for q in quotes]

مزایای CSS selectors:

  • قابل تست در ابزارهای توسعه مرورگر قبل از کدنویسی.
  • قابل انتقال به کتابخانه‌های دیگر که از selector پشتیبانی می‌کنند.
  • خواناتر و نگهداری‌پذیر اگر selectorها را به‌عنوان متغیر ذخیره کنید.

عملکرد، پردازش حجم زیاد و ریسک‌ها

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

  • زیربار پردازش بزرگ: BeautifulSoup برای فایل‌های خیلی بزرگ حافظه‌بر است چون کل DOM را در حافظه نگه می‌دارد. برای فایل‌های بسیار حجیم، از lxml.iterparse یا ابزارهای مبتنی بر stream استفاده کنید.
  • پارسرها: lxml معمولاً سریع‌تر و دقیق‌تر از html.parser است؛ اما نیاز به نصب دارد.
  • همزمانی: برای اسکریپینگ گسترده از چندین worker یا queue استفاده کنید، اما مراقب سیاست‌های سایت و بارگذاری سرور هدف باشید.

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

نکات مهم قبل از اجرای اسکریپر:

  • قوانین سایت را بررسی کنید (robots.txt و Terms of Service) و سیاست‌های قانونی را رعایت کنید.
  • اطلاعات حساس (توکن‌ها، کوکی‌ها) را در خروجی ذخیره نکنید و توجه داشته باشید که scraping ممکن است داده‌های خصوصی را فاش کند.
  • از ذخیرهٔ مستقیم HTMLهای دریافت‌شده در صفحات وب خود بدون اسکن و پاک‌سازی جلوگیری کنید تا خطر XSS کاهش یابد.

پایداری، نرخ درخواست و مدیریت خطا

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

  • هدر User-Agent معقول، timeout و مکانیزم retry با backoff پیاده‌سازی کنید.
  • درخواست‌ها را throttle کنید یا بین درخواست‌ها delay اضافه کنید تا بلاک شدن کاهش یابد.
  • از proxy rotation در صورت نیاز و با رعایت قوانین استفاده کنید.
  • برای مدیریت خطاها، وضعیت‌های HTTP مختلف و استثناهای شبکه را هندل کنید و لاگ‌برداری داشته باشید.

محتوای پویا (جاوااسکریپت)

BeautifulSoup محتوای تولیدشدهٔ سمت کلاینت (با JS) را اجرا نمی‌کند. اگر صفحه با جاوااسکریپت داده‌ها را بارگذاری می‌کند، گزینه‌ها:

  • استفاده از ابزارهایی مانند Selenium یا Playwright برای رندر کردن صفحه و دریافت HTML نهایی.
  • جست‌وجو در درخواست‌های شبکه در DevTools و استفاده مستقیم از APIهای JSON که صفحه مصرف می‌کند (در صورت وجود).

نمونهٔ نهایی: ترکیب همه چیز

import requests
from bs4 import BeautifulSoup

url = 'https://quotes.toscrape.com/'
headers = {'User-Agent': 'my-scraper/1.0'}
try:
    response = requests.get(url, headers=headers, timeout=10)
    response.raise_for_status()
    soup = BeautifulSoup(response.content, 'html.parser')
    quotes = [q.get_text(strip=True) for q in soup.select('span.text')]
    # ذخیرهٔ خروجی یا تبدیل به JSON
except requests.RequestException as e:
    # لاگ و retry یا fallback
    print('Request failed:', e)

شرح: این مثال هدر سفارشی، مدیریت خطا اولیه، استفاده از response.content و انتخاب با CSS را نشان می‌دهد. خروجی quotes لیستی از رشته‌ها خواهد بود.

جمع‌بندی

BeautifulSoup ابزاری ساده و قابل‌فهم برای استخراج داده از HTML است که به‌خوبی با requests یا httpx ترکیب می‌شود. برای کوئری‌های ساده .find()/.find_all() و برای نگهداری و انتقال‌پذیری کد .select() پیشنهاد می‌شود. همیشه به مسائل رمزگذاری، نرخ درخواست، خطاها و جنبه‌های اخلاقی توجه کنید و برای محتوای پویا از رندر سمت کلاینت یا APIها استفاده کنید.

  • نکات عملی: از response.content استفاده کنید، timeout و retry را پیاده کنید، و selectorها را به‌صورت متغیر تعریف کنید.
  • در صورت نیاز به سرعت یا حافظه بهتر، lxml را بررسی کنید.
  • برای صفحات شلوغ با جاوااسکریپت از ابزارهای رندر یا بررسی APIهای پنهان استفاده کنید.
مطالب مرتبط

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