بیوتیفول سوپ (Beautiful Soup) چیست و چرا برای وباسکریپینگ محبوب است؟
this is a test
اگر به استخراج داده از صفحات وب علاقهمند شدهای، نام Beautiful Soup یا بهاختصار BS4 بهگوشات خورده است. BS4 یک کتابخانهٔ پایتونی برای پارسکردن (parse) اسناد HTML و XML است که پیمایش درخت DOM، جستوجوی عناصر و استخراج اطلاعات را ساده و «پایتونپسند» میکند. نتیجه این است که بهجای درگیری با جزئیات ریز HTML، روی منطق استخراج داده تمرکز میکنی.
BS4 دقیقاً چه کاری انجام میدهد؟
Beautiful Soup خودش یک پارسر نیست؛ بلکه روی پارسرهای موجود مثل html.parser (داخلی پایتون)، lxml و html5lib سوار میشود و یک API ساده برای یافتن، پیمایش و دستکاری گرههای درخت HTML در اختیارت قرار میدهد. مزیت اصلی آن سادگی و تابآوری در برابر HTMLهای «کثیف» یا ناقص است؛ صفحاتی که اغلب در دنیای واقعی میبینیم.
نصب و پیشنیازها
برای شروع، به پایتون ۳ و چند بستهٔ زیر نیاز داری:
pip install beautifulsoup4 requests
# اختیاری برای سرعت/سازگاری بهتر:
pip install lxml html5librequests برای دریافت HTML از وب استفاده میشود و BS4 برای پارس و استخراج. اگر lxml نصب باشد، معمولاً سرعت بهتری نسبت به html.parser میگیری؛ html5lib هم استاندارد HTML5 را دقیقتر دنبال میکند.
اولین قدم: ساختن یک Soup از HTML
import requests
from bs4 import BeautifulSoup
url = "https://example.com"
resp = requests.get(url, timeout=15)
resp.raise_for_status() # اگر خطای HTTP بود، استثنا میاندازد
soup = BeautifulSoup(resp.text, "lxml") # یا "html.parser" / "html5lib"
print(soup.title.get_text(strip=True))شیء soup نمایندهٔ کل درخت HTML است. از این لحظه تو میتوانی بهسادگی سراغ بخشهای مختلف صفحه بروی.
انتخابگرها و جستوجو: find، find_all و select
سه راه محبوب برای پیدا کردن عناصر:
find: اولین عنصری که با شرایط میخواند.find_all: همهٔ عناصر مطابق شرایط.select: انتخابگرهای CSS (خیلی قدرتمند و خوانا).hi
# بر اساس نام تگ و کلاس
card = soup.find("div", class_="card")
# همهٔ لینکها
links = [a["href"] for a in soup.find_all("a", href=True)]
# با CSS Selector: تمام آیتمهای یک فهرست
items = [li.get_text(strip=True) for li in soup.select("ul.menu > li")]
# انتخاب پیچیده با ویژگیها
prices = [p.get_text(strip=True) for p in soup.select("span.price[data-currency='USD']")]متد select بهدلیل پشتیبانی از انتخابگرهای CSS بسیار محبوب است؛ بهویژه وقتی ساختار صفحه پیچیده باشد یا روی کلاسها و صفات تکیه کنی.
پیمایش درخت: والد، فرزند و همسایهها
article = soup.select_one("article.post")
title = article.select_one("h2").get_text(strip=True)
date = article.select_one("time").get("datetime")
body = article.select_one(".content")
# حرکت در درخت
parent = body.parent
first_p = body.find("p")
next_p = first_p.find_next_sibling("p")
all_text = body.get_text("\n", strip=True)خانوادهٔ متدهای find_next، find_previous و خصیصههایی مثل .parent و .children به تو اجازه میدهند با انعطاف بالا در صفحه حرکت کنی.
مدیریت HTML «ناقص» و کاراکترها
BS4 با HTMLهای «خراب» هم کنار میآید. اگر با رمزگذاری (Encoding) مشکل داشتی، به resp.encoding یا استفاده از resp.content (باینری) و تعیین دستی encoding توجه کن. متد get_text(strip=True) متن تمیز و بدون فاصلههای اضافه برمیگرداند.
کار با فرمها، جدولها و الگوهای رایج
- لیستها و کارتها: اغلب کارتها الگوی تکرارشونده دارند؛ با
select(".card")همه را بگیر و از هرکدام فیلدهای هدف را استخراج کن. - جدولها: سطرها را با
select("table tr")پیمایش کن و سلولها را ازtd/thبخوان. - تصاویر: با
img["src"]و مدیریت مسیرهای نسبی (باurllib.parse.urljoin) لینکها را کامل کن.
from urllib.parse import urljoin
rows = []
for tr in soup.select("table.data > tbody > tr"):
cols = [td.get_text(strip=True) for td in tr.select("td")]
rows.append(cols)
img_urls = [urljoin(url, img["src"]) for img in soup.select("img[src]")]
بهینهسازی عملکرد: انتخاب پارسر، نرخ درخواست و کش
برای پروژههای بزرگتر:
- پارسر سریعتر: اگر
lxmlنصب باشد، معمولاً پارسینگ سریعتر میشود. - Rate limiting: بین درخواستها
sleepکوتاه بگذار تا سرور را اذیت نکنی. - Session مشترک: با
requests.Session()هدرهای ثابت و کوکیها را نگه دار. - Retry با backoff: برای خطاهای موقت شبکه، تلاش مجدد برنامهریزی کن.
- کش نتایج: اگر صفحهها ثابتاند، HTML را محلی ذخیره کن تا دوباره دانلود نکنی.
وقتی BS4 کافی نیست: محتوای پویا و جاوااسکریپت
Beautiful Soup روی HTML نهایی کار میکند. اگر صفحه با جاوااسکریپت داده را بعداً لود میکند، یکی از این رویکردها را امتحان کن:
- بررسی تب «Network» مرورگر؛ شاید یک API JSON ساده وجود داشته باشد که مستقیماً قابلخواندن است.
- استفاده از Selenium یا Playwright برای رندر سمتکاربر، سپس دادن HTML رندرشده به BS4.
بهترینتمرینها و نکات حقوقی/اخلاقی
همیشه robots.txt، شرایط استفادهٔ سایت و قوانین محلی را بررسی کن. به منابع فشار نیاور، نرخ درخواستها را محدود کن، هدرهای شفاف (مثل User-Agent) بفرست و اگر لازم است قبل از استخراج دادهٔ حساس یا انبوه، مجوز بگیر. وباسکریپینگ قانونی یا غیرقانونی نیست؛ بستگی به نحوهٔ استفاده و منشأ داده دارد.
ذخیرهسازی خروجی: CSV، JSON یا پایگاهداده
import csv, json
# ذخیره به CSV
with open("items.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["title", "price"])
for card in soup.select(".card"):
t = card.select_one(".title").get_text(strip=True)
p = card.select_one(".price").get_text(strip=True)
writer.writerow([t, p])
# ذخیره به JSON
items = []
for card in soup.select(".card"):
items.append({
"title": card.select_one(".title").get_text(strip=True),
"price": card.select_one(".price").get_text(strip=True)
})
with open("items.json", "w", encoding="utf-8") as f:
json.dump(items, f, ensure_ascii=False, indent=2)دیباگ و خطاهای رایج
- انتخابگر اشتباه: اگر
select_oneچیزی برنگرداند، ساختار HTML واقعی را باprint(soup.prettify())بررسی کن. - محاسبهٔ نسبی لینکها: فراموشکردن
urljoinباعث لینکهای شکسته میشود. - HTML پویا: اگر در سورس HTML چیزی نمیبینی، احتمالاً بعداً با جاوااسکریپت لود میشود.
- رمزگذاری: متنهای ناخوانا معمولاً از
encodingاشتباه میآیند؛resp.apparent_encodingرا تست کن.
یک مثال سرراست از صفر تا خروجی
import time
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
BASE = "https://example.com/catalog"
with requests.Session() as s:
s.headers.update({"User-Agent": "Mozilla/5.0 (compatible; demo-bot/1.0)"})
r = s.get(BASE, timeout=20)
r.raise_for_status()
soup = BeautifulSoup(r.text, "lxml")
data = []
for card in soup.select(".product-card"):
title = card.select_one(".product-title").get_text(strip=True)
price = card.select_one(".price").get_text(strip=True)
link = urljoin(BASE, card.select_one("a[href]")["href"])
data.append({"title": title, "price": price, "url": link})
time.sleep(0.5) # رعایت نرخ درخواست
print(data[:3])این الگو بهراحتی برای وبلاگها، خبرخوانها، فهرست محصولات و جدولهای داده قابلتعمیم است. کافیست انتخابگرها را متناسب با صفحهٔ هدف تنظیم کنی.
چکلیست سریع هنگام کار با BS4
- هدف داده و ساختار صفحه را دقیق مشخص کن.
- اگر API موجود است، ابتدا آن را امتحان کن.
- انتخاب پارسر (سرعت/سازگاری) را آگاهانه انجام بده.
- انتخابگرهای





