مقدمه
در وب اسکریپینگ و پردازش فایلهای HTML اغلب لازم است تگهای خاصی را حذف، جایگزین یا محتوای متنی آنها را استخراج کنیم؛ مثلاً پاکسازی <span> یا حذف <script> برای آنالیز متن یا تولید خروجی تمیز. در این راهنما با روشهای پراستفاده در BeautifulSoup آشنا میشوید: unwrap()، decompose()، replace_with() و get_text()؛ هر کدام با مثال، توضیح ورودی/خروجی و نکات عملی.
عملیات پایه با unwrap()
ایده کلی: unwrap() تگ انتخابشده را حذف میکند اما محتوای داخلی (children) را در جای خودش نگه میدارد؛ برای زمانی که میخواهید فقط برچسب بیرونی پاک شود و متن/عنصرهای داخلی حفظ شوند.
کد نمونه و توضیح:
from bs4 import BeautifulSoup
html_doc = """
Hello, BeautifulSoup!
"""
soup = BeautifulSoup(html_doc, 'html.parser')
li_tags = soup.find_all('li')
for li in li_tags:
span = li.find('span') # ورودی: یک تگ span یا None
if span:
span.unwrap() # خروجی: تگ span حذف شده، محتوای داخلی (Link) میماند
print(soup)
خطبهخط: ابتدا سند را پارس میکنیم؛ با find_all('li') همه عناصر لیست را میگیریم؛ برای هر <li> تلاش میکنیم <span> را پیدا کنیم و اگر موجود بود unwrap() فراخوانی میشود. نتیجه: متن «(Link)» درون <a> باقی میماند بدون تگ <span>.
نکتهی مهم: قبل از فراخوانی unwrap() همیشه از None نبودن شی اطمینان حاصل کنید تا از خطای AttributeError جلوگیری شود.
حذف کامل با decompose()
ایده کلی: decompose() تگ مورد نظر را از درخت حذف کرده و آن را از حافظه آزاد میکند؛ مناسب وقتی میخواهید عنصر و همه محتویات آن کاملاً پاک شوند، مثلاً حذف اسکریپتها یا تبلیغات.
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')
# حذف تمام تگهای span داخل لیست
for span in soup.select('li span'):
span.decompose() # این مقدار را حذف میکند؛ دیگر به درخت تعلق ندارد
print(soup)
کارکرد: decompose() باعث میشود آن تگ و زیرشاخههایش از حافظه و درخت حذف شوند؛ اگر سند بزرگ و تگهای زیاد دارید این روش از نظر مصرف حافظه بهتر از نگهداری نسخههای موقتی است. با این حال اگر بعداً لازم داشته باشید محتوای حذفشده را بازگردانید باید قبل از decompose یک کپی از آن ذخیره کنید.
مثال امنیتی/عملی: حذف <script> و <noscript> قبل از استخراج متن یا تبدیل به JSON میتواند از تزریق ناخواسته جاوااسکریپت جلوگیری کند.
جایگزینی با replace_with()
ایده کلی: گاهی لازم است تگ را با یک تگ یا متن دیگر جایگزین کنیم—مثلاً تبدیل <span> به بولد یا لینک متفاوت. نکته مهم: اگر از یک شی Tag یکسان برای چند جایگزینی استفاده کنید، آن شی از درخت جابهجا خواهد شد (move) و فقط در یک مکان نهایی خواهد نشست؛ بنابراین برای هر جایگزینی یک تگ جدید ایجاد کنید.
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')
for li in soup.find_all('li'):
span = li.find('span')
if span:
# ایجاد تگ جدید برای هر بار جایگزینی
new_tag = soup.new_tag('b')
new_tag.string = '[Click Here]'
span.replace_with(new_tag) # span با [Click Here] جایگزین میشود
print(soup)
توضیح: هر بار که new_tag را میسازیم، یک عنصر جدید به درخت افزوده میشود و جایگزینی ایمن انجام میشود. اگر از یک Tag آماده مکرراً استفاده کنید نتیجه ناخواسته خواهد بود (فقط یک ارجاع در نهایت).
استخراج متن با get_text()
ایده کلی: اگر نیازی به تغییر HTML ندارید و تنها میخواهید متن خام از داخل تگها استخراج شود از get_text() استفاده کنید. این متد متن همه زیرعنصرها را جمعآوری میکند.
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'html.parser')
for li in soup.find_all('li'):
text = li.get_text(separator=' ', strip=True) # ورودی: separator و strip
print(text) # خروجی: 'Example.com (Link)' و 'Scrapy.com (Link)'
پارامترها: separator مشخص میکند بین بخشهای متن چه جداکنندهای قرار گیرد؛ strip=True فضاهای اضافی را حذف میکند. اگر بخواهید متنی بدون محتوای درون برخی تگها استخراج شود، ابتدا آنها را decompose() کنید یا با انتخاب دقیقتر از select() استفاده کنید.
نکات عملی، خطاها و بهترین روشها
- بررسی None: قبل از فراخوانی unwrap/decompose/replace_with همیشه بررسی کنید که تگ مورد نظر وجود داشته باشد.
- اجتناب از استفاده دوبارهی یک Tag: هنگام استفاده از replace_with برای هر جایگزینی یک تگ جدید بسازید تا جابهجایی ناخواسته رخ ندهد.
- انتخاب پارسر: lxml در پردازش اسناد بزرگ سریعتر و مقاومتر است؛ اگر سرعت یا توانایی پردازش HTML نامنظم مهم است از features='lxml' استفاده کنید.
- عملکرد در اسناد بزرگ: برای اسناد بسیار بزرگ از پردازش تدریجی (streaming parser) یا تقسیم سند به بخشهای کوچک استفاده کنید تا حافظه بیش از حد مصرف نشود.
- همزمانی و پردازش: دانلود صفحات میتواند همزمان انجام شود (مثلاً با aiohttp)، اما خودِ پردازش با BeautifulSoup CPU‑bound است؛ برای مقیاس بالا از multiprocessing یا پردازش دستهای پس از جمعآوری صفحات استفاده کنید.
- امنیت: هرگونه اجرای کد یا تفسیر جاوااسکریپت را حذف کنید؛ پیش از استخراج متن، <script> و تگهای مشابه را حذف کنید تا از اجرای محتوای مخرب جلوگیری شود.
- ذخیرهسازی و پشتیبان: اگر میخواهید نسخه اصلی سند را نگه دارید، قبل از تغییرات یک کپی از رشته HTML بسازید: original = str(soup) یا مجدداً یک BeautifulSoup از آن رشته بسازید.
مثالهای تکمیلی و الگوها
حذف همه اسکریپتها و استخراج متن تمیز از کل سند:
soup = BeautifulSoup(html_doc, 'lxml')
for script in soup.find_all('script'):
script.decompose()
clean_text = soup.get_text(separator='\n', strip=True)
print(clean_text)
استفاده از انتخابگرهای CSS برای هدفگذاری دقیقتر:
# حذف spanهایی که داخل a با کلاس خاص هستند
for span in soup.select('a.some-class span'):
span.decompose()
مزایا و معایب روشها (خلاصه)
- unwrap(): مزیت—حفظ متن داخلی؛ عیب—حذف فقط تگ بیرونی، ممکن است ساختار نهایی بهم بریزد.
- decompose(): مزیت—حذف کامل و آزادسازی حافظه؛ عیب—دادهها غیرقابلبازیابی مگر از قبل ذخیره شده باشند.
- replace_with(): مزیت—انعطاف برای جایگزینی محتوا؛ عیب—باید مراقب استفاده مجدد از یک Tag باشید.
- get_text(): مزیت—استخراج سریع متن؛ عیب—از دست رفتن ساختار و اطلاعات صفات (attributes).
جمعبندی
برای پاکسازی و مدیریت تگها در اسکریپینگ با BeautifulSoup چهار ابزار اصلی دارید: unwrap() برای برداشتن تگ ولی حفظ محتوا، decompose() برای حذف کامل، replace_with() برای جایگزینی هدفمند و get_text() برای استخراج متن. انتخاب روش صحیح بسته به هدف (حفظ ساختار، آزادسازی حافظه، یا استخراج متن) و مقیاس پروژه است. همیشه قبل از تغییر درخت، از وجود تگها مطمئن شوید، در اسناد بزرگ از parser سریعتر استفاده کنید و برای عملیات موازی، دانلود و پردازش را به طور جداگانه طراحی کنید.





