this is a test
اگر به استخراج داده از صفحات وب علاقهمند شدهای، نام Beautiful Soup یا بهاختصار BS4 بهگوشات خورده است. BS4 یک کتابخانهٔ پایتونی برای پارسکردن (parse) اسناد HTML و XML است که پیمایش درخت DOM، جستوجوی عناصر و استخراج اطلاعات را ساده و «پایتونپسند» میکند. نتیجه این است که بهجای درگیری با جزئیات ریز HTML، روی منطق استخراج داده تمرکز میکنی.
Beautiful Soup خودش یک پارسر نیست؛ بلکه روی پارسرهای موجود مثل html.parser
(داخلی پایتون)، lxml
و html5lib
سوار میشود و یک API ساده برای یافتن، پیمایش و دستکاری گرههای درخت HTML در اختیارت قرار میدهد. مزیت اصلی آن سادگی و تابآوری در برابر HTMLهای «کثیف» یا ناقص است؛ صفحاتی که اغلب در دنیای واقعی میبینیم.
برای شروع، به پایتون ۳ و چند بستهٔ زیر نیاز داری:
pip install beautifulsoup4 requests
# اختیاری برای سرعت/سازگاری بهتر:
pip install lxml html5lib
requests
برای دریافت HTML از وب استفاده میشود و BS4 برای پارس و استخراج. اگر lxml
نصب باشد، معمولاً سرعت بهتری نسبت به html.parser
میگیری؛ html5lib
هم استاندارد HTML5 را دقیقتر دنبال میکند.
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
به تو اجازه میدهند با انعطاف بالا در صفحه حرکت کنی.
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
نصب باشد، معمولاً پارسینگ سریعتر میشود.sleep
کوتاه بگذار تا سرور را اذیت نکنی.requests.Session()
هدرهای ثابت و کوکیها را نگه دار.Beautiful Soup روی HTML نهایی کار میکند. اگر صفحه با جاوااسکریپت داده را بعداً لود میکند، یکی از این رویکردها را امتحان کن:
همیشه robots.txt، شرایط استفادهٔ سایت و قوانین محلی را بررسی کن. به منابع فشار نیاور، نرخ درخواستها را محدود کن، هدرهای شفاف (مثل User-Agent
) بفرست و اگر لازم است قبل از استخراج دادهٔ حساس یا انبوه، مجوز بگیر. وباسکریپینگ قانونی یا غیرقانونی نیست؛ بستگی به نحوهٔ استفاده و منشأ داده دارد.
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
باعث لینکهای شکسته میشود.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])
این الگو بهراحتی برای وبلاگها، خبرخوانها، فهرست محصولات و جدولهای داده قابلتعمیم است. کافیست انتخابگرها را متناسب با صفحهٔ هدف تنظیم کنی.