خانه/مقالات/اسکریپینگ با BeautifulSoup: استفاده حرفه‌ای از find_all
استخراج داده
beautifulsoup
برگشت به مقاله‌ها

اسکریپینگ با BeautifulSoup: استفاده حرفه‌ای از find_all

اسکریپینگ با BeautifulSoup: استفاده حرفه‌ای از find_all
این مقاله یک راهنمای عملی برای استفاده از متد find_all در BeautifulSoup ارائه می‌دهد: از جست‌وجوی ساده با تگ تا فیلتر بر اساس کلاس، id، متن و Regex، روش‌های ترکیبی و توابع دلخواه؛ همچنین نکات عملکرد، خطاگیری، و نمونهٔ کد مقاوم با requests و lxml را پوشش می‌دهد.
آسان اسکریپ آسان اسکریپ
1405-04-23

مقدمه

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

روش پایه: پیدا کردن همه عناصر از یک نوع

ایده کلی: وقتی چند عنصر با یک تگ وجود دارد و می‌خواهید همه را بگیرید از find_all استفاده کنید. خروجی یک لیست از عناصر (شیء Tag) است که می‌توانید تک‌تک پردازش کنید.

نمونه پایه — ورودی: یک سند HTML ساده؛ خروجی: لیستی از تگ‌های <a>.

from bs4 import BeautifulSoup

html_doc = """

    
        

Hello, BeautifulSoup!

""" soup = BeautifulSoup(html_doc, 'html.parser') # Find all tags links = soup.find_all('a') print(links) # خروجی: [Link 1, Link 2]

شرح خط‌به‌خط: ابتدا سند را با BeautifulSoup پارس می‌کنیم؛ سپس find_all('a') لیستی از تگ‌های <a> برمی‌گرداند.

استخراج متن و حلقه‌زنی روی نتایج

برای به‌دست‌آوردن متن یا صفت‌ها باید لیست را پیمایش کنید. مثال زیر متن هر لینک و مقدار صفت href را استخراج می‌کند.

# ورودی: لیست تگ‌ها از find_all
for tag in links:
    text = tag.get_text()        # متن داخل تگ
    href = tag.get('href')       # مقدار صفت href یا None
    print(text, href)

# خروجی: 'Link 1' 'http://example.com'  و سپس 'Link 2' 'http://scrapy.org'

نکته عملی: همیشه فرض نکنید که صفت (مثل href) وجود دارد؛ برای جلوگیری از خطا از get استفاده کنید یا وجود صفت را چک کنید.

محدود کردن نتایج با پارامتر limit

اگر فقط به تعدادی از نتایج نیاز دارید، می‌توانید از پارامتر limit استفاده کنید که مشابه LIMIT در SQL عمل می‌کند و پس از یافتن تعداد مشخص، جست‌وجو را متوقف می‌کند.

first_two = soup.find_all('a', limit=2)
# خروجی: تنها دو تگ اول را برمی‌گرداند

جست‌وجو بر اساس کلاس، id و attrs

برای فیلتر دقیق‌تر می‌توان پارامترهای پیش‌فرض مثل class_ و id یا دیکشنری attrs را استفاده کرد.

# یافتن تمام 

هایی که کلاس مشخص دارند soup.find_all('p', class_='class_name') # یافتن بر اساس id soup.find_all('p', id='id_name') # هر ویژگی دلخواهی با attrs soup.find_all('p', attrs={'aria-hidden': 'true'})

توضیح: class_ به‌خاطر کلمه رزرو شده class نام‌گذاری شده؛ attrs برای ترکیبات پیچیده‌تر مناسب است.

جست‌وجو بر اساس متن و کاربرد Regex

اگر هدف شما یافتن رشته‌هایی است که دقیقاً با یک مقدار تطبیق دارند از پارامتر string استفاده کنید. برای تطبیق‌های جزئی یا الگوها از Regex بهره ببرید.

import re

# رشته‌هایی که دقیقاً 'Link 1' هستند
soup.find_all(string="Link 1")  # --> ['Link 1']

# رشته‌هایی که شامل 'Link' هستند (Regex)
soup.find_all(string=re.compile("Link"))  # --> ['Link 1', 'Link 2']

# پیدا کردن تگ‌هایی که نام‌شان با b شروع می‌کند
for tag in soup.find_all(re.compile(r"^b")):
    print(tag.name)  # مثال: 'body' 

نکته: هنگام استفاده از Regex دقت کنید که پیچیدگی الگوها باعث کاهش سرعت شود؛ اگر ممکن است الگوها را ساده نگه دارید.

چند شرطی و ترکیب چند ویژگی

برای پرس‌وجوهای چندشرطی از attrs با چند کلید استفاده کنید یا چند فیلتر را به‌صورت جداگانه ترکیب نمایید.

# 

با class و id مشخص soup.find_all('p', attrs={'class': 'class_name', 'id': 'id_name'})

در سناریوهای پیچیده‌تر ممکن است بخواهید نتایج را ابتدا با یک فیلتر سریع محدود کنید و سپس درون آن‌ها جست‌وجوهای دقیق‌تری انجام دهید (فیلتر دو مرحله‌ای برای بهبود کارایی).

استفاده از توابع دلخواه در find_all

اگر نیاز دارید منطق پیچیده‌ای را اعمال کنید می‌توانید یک تابع پایتون را به find_all پاس دهید؛ تابع باید برای هر تگ True/False برگرداند.

def custom_selector(tag):
    # مثال: برگرداندن span هایی که کلاس 'target_span' را دارند
    return (
        tag.name == 'span' and
        tag.has_attr('class') and
        'target_span' in tag.get('class')
    )

results = soup.find_all(custom_selector)

شرح: ورودی تابع یک شیء Tag است؛ این تابع دربارهٔ نام تگ، وجود اtributeها و محتویات آن تصمیم می‌گیرد و True/False برمی‌گرداند.

نکات عملکردی، پایداری و امنیت

  • انتخاب پارسر: پارسر پیش‌فرض html.parser ساده است، اما برای سرعت و تحمل بیشتر در صفحات پیچیده از lxml استفاده کنید: BeautifulSoup(html, 'lxml').
  • درخواست HTTP امن و پایدار: از requests.Session، تعیین timeout و هدر مناسب (User-Agent) استفاده کنید و وضعیت‌های HTTP غیرمنتظره را هندل کنید.
  • Retries و backoff: برای پایداری از مکانیزم retry با افزایش فاصله (exponential backoff) بهره ببرید تا در مواجهه با خطاهای موقتی سرور شکست کمتری داشته باشید.
  • رعایت رفتارهای اخلاقی/قانونی: قبل از اسکریپینگ فایل robots.txt را بررسی کرده و نرخ درخواست‌ها (rate limit) و استراحت بین درخواست‌ها را رعایت کنید.
  • هشدار درباره JavaScript: اگر محتوای مورد نظر دینامیک است و با BeautifulSoup قابل دریافت نیست، از موتورهایی که جاوااسکریپت اجرا می‌کنند (مثلاً ابزارهای هدلس) استفاده کنید، اما توجه کنید که این روش هزینه و پیچیدگی بیشتری دارد.
  • پراکسی و مدیریت IP: در پروژه‌های بزرگ از پراکسی‌ها، چرخش آی‌پی و سرویس‌های مدیریت پراکسی برای کاهش بلوکه شدن استفاده کنید.
  • محدودیت منابع: هنگام پردازش حجم زیاد داده، حافظه و زمان پردازش را مدیریت کنید؛ پردازشِ استریم (line-by-line) یا ذخیره میانی به فایل کمک می‌کند.

نمونهٔ یک اسکریپر کوچک مقاوم

ترکیب requests و BeautifulSoup با timeout، retries و انتخاب parser.

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from bs4 import BeautifulSoup

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))

url = 'https://example.com'
try:
    resp = session.get(url, timeout=10, headers={'User-Agent': 'my-scraper/1.0'})
    resp.raise_for_status()
    soup = BeautifulSoup(resp.text, 'lxml')  # lxml برای کارایی بهتر
    items = soup.find_all('div', class_='item')  # مثال: انتخاب بلوک‌های محتوا
    for it in items:
        title = it.find('h2') and it.find('h2').get_text(strip=True)
        link = it.find('a') and it.find('a').get('href')
        print(title, link)
except requests.RequestException as e:
    # لاگ‌کردن و هندل کردن خطا
    print('Request failed:', e)

شرح کوتاه: این الگو retry، timeout و بررسی وضعیت HTTP را ترکیب می‌کند؛ سپس با find_all بلوک‌ها را می‌گیرد و با find زیرعناصر را استخراج می‌کند.

جمع‌بندی و توصیه‌های نهایی

متد find_all ابزاری منعطف و قدرتمند برای اسکریپینگ صفحات HTML است. نکات کلیدی:

  • قبل از هر چیز ساختار صفحه را بررسی کنید و فیلترها را ساده و دقیق بنویسید.
  • برای سرعت و پایداری پارسر مناسب را انتخاب کنید (lxml برای عملکرد بهتر).
  • همیشه ورودی‌ها و خروجی‌ها را اعتبارسنجی کنید و خطاها را هندل کنید (timeouts، retries).
  • برای جست‌وجوی متن از پارامتر string و برای الگوها از Regex استفاده کنید.
  • در مواقع نیاز از توابع دلخواه برای اعمال منطق پیچیده بهره ببرید.

اگر دنبال مجموعه‌ابزارهای آماده برای پروژه‌های بزرگ اسکریپینگ هستید، می‌توانید نام‌هایی مثل ScrapeOps را بررسی کنید تا مدیریت پراکسی، مانیتورینگ و زمان‌بندی کارها ساده‌تر شود. اما برای شروع، تمرکز روی نوشتن کد تمیز، هندل کردن خطا و رعایت اخلاقی/قانونی بودن اسکریپ‌ها کافی است.

مطالب مرتبط

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