در پروژههای علم داده، پیدا کردن دادههای مناسب و پاکسازی آنها حدود ۸۰ درصد از زمان و انرژی را میگیرد. بسیاری از اطلاعات موردنیاز به شکل فایلهای آماده در دسترس نیستند؛ این اطلاعات در صفحات وب و کدهای HTML قرار دارند. استخراج دستی آنها از وبسایتها کند و خستهکننده است و نمیتوان این روش را در مقیاس بزرگ به کار گرفت.
با وباسکرپینگ در پایتون و کتابخانه BeautifulSoup میتوانید این اطلاعات را بهصورت خودکار استخراج کنید. این کتابخانه کدهای HTML صفحات وب را تجزیه میکند و امکان بیرون کشیدن دادههای موردنظر را فراهم میکند. برای شروع کار، آشنایی مقدماتی با تگهای HTML و گذراندن مبانی پایه در مسیر آموزش پایتون کافی است.
در این آموزش، گامبهگام یاد میگیرید ساختار سایت را بررسی کنید، کد منبع صفحه را با کتابخانه requests دانلود کنید، اطلاعات متنی و تصویری را با متدهای مختلف از یک یا چند صفحه بیرون بکشید و خروجی پاکسازیشده را در یک فایل CSV ذخیره کنید.
وباسکرپینگ چیست و کتابخانه BeautifulSoup چه نقشی در آن دارد؟
وباسکرپینگ (Web Scraping) فرایند جمعآوری اطلاعات از اینترنت است. کپی و پیست کردن دستی متن یک آهنگ هم نوعی اسکرپینگ است، اما این اصطلاح معمولاً به خودکارسازی جمعآوری اطلاعات اشاره دارد. جستجوی دستی در صفحههایی که مدام بهروزرسانی میشوند، زمان میبرد و احتمال خطا دارد. با اتوماسیون کارها میتوانید یک بار کد بنویسید و دادههای موردنیاز را بارها از صفحات مختلف استخراج کنید.

BeautifulSoup یک کتابخانه پایتون است که برای تجزیه (Parse) اسناد HTML و XML طراحی شده است. این کتابخانه درختهای تجزیه (Parse Trees) میسازد تا استخراج دادههای مشخص از کدهای وب سادهتر شود. نام BeautifulSoup از شعری در کتاب «ماجراهای آلیس در سرزمین عجایب» اثر لوئیس کارول گرفته شده است. این نام به توانایی ابزار در مدیریت کدهای HTML نامرتب و ضعیف اشاره دارد؛ کدهایی که به آنها «tag soup» میگویند.
استفاده از خود کتابخانه BeautifulSoup قانونی است، زیرا فقط اسناد دانلودشده را تجزیه میکند. وباسکرپینگ نیز اگر برای اهداف آموزشی و محترمانه باشد، معمولاً مشکلی ایجاد نمیکند. بااینحال، پیش از اجرای پروژههای بزرگ باید قوانین استفاده از خدمات (Terms of Service) وبسایت هدف و قوانین کپیرایت را بررسی و رعایت کنید.
تفاوت سایتهای استاتیک و داینامیک در استخراج داده
سایتهای استاتیک (Static) محتوای HTML ثابتی ارائه میدهند و اطلاعات آنها مستقیماً در کد منبع صفحه قرار دارد. BeautifulSoup در فرایند وباسکرپینگ به استخراج اطلاعات مشخص از این صفحات کمک میکند، حتی اگر ساختار صفحه پیچیده باشد.
سایتهای داینامیک (Dynamic) برای بارگذاری و نمایش محتوا به اجرای کدهای جاوااسکریپت نیاز دارند. BeautifulSoup فقط اسناد دریافتشده را تجزیه میکند. به همین دلیل، برای اسکرپ کردن صفحاتی که با جاوااسکریپت رندر میشوند، باید از ابزارهای مکملی مانند Selenium یا Scrapy استفاده کنید که قابلیت اجرای جاوااسکریپت را دارند.
چالشهای وباسکرپینگ و زمان استفاده از API
منابع، سبکها و فناوریهای مختلف در اینترنت باعث میشوند استخراج داده با دو چالش اصلی روبهرو باشد:

- تنوع ساختار (Variety): هر وبسایت ساختار مخصوص خود را دارد. با وجود شباهتهای کلی، برای استخراج اطلاعات موردنظر از هر سایت باید اسکریپتی اختصاصی و متناسب با ساختار همان سایت بنویسید.
- ماندگاری و تغییرات مداوم (Durability): وبسایتها پیوسته توسعه پیدا میکنند و تغییر میکنند. ممکن است اسکریپت شما در اجرای اول بدون نقص کار کند، اما مدتی بعد با تغییر ساختار سایت خطا بدهد. به همین دلیل، اسکریپتهای وباسکرپینگ در فرایند آموزش برنامهنویسی به نگهداری و بهروزرسانی دورهای نیاز دارند.
اگر وبسایت هدف رابط برنامهنویسی کاربردی (API) دارد، استفاده از آن جایگزین پایدارتری برای وباسکرپینگ است. HTML برای نمایش بصری محتوا به کاربران طراحی شده است، اما APIها برای استفاده برنامهها ساخته میشوند و دادهها را مستقیماً در قالبهای استانداردی مانند JSON یا XML در اختیار آنها میگذارند. با API نیازی به تجزیه کدهای HTML ندارید. تغییر ظاهر گرافیکی (Front-end) سایت نیز ساختار دریافت داده شما را مختل نمیکند.
پیشنیازها و نصب کتابخانه BeautifulSoup
BeautifulSoup فقط کدهای HTML و XML دریافتشده را تحلیل میکند و بهتنهایی امکان اتصال به اینترنت و دانلود صفحات وب را ندارد. بنابراین، برای ارسال درخواست به آدرس سایت (URL) و دریافت کد منبع صفحه، در کنار آن به کتابخانه requests نیاز داریم.
پیش از شروع کدنویسی، ترمینال یا Command Prompt سیستم خود را باز کنید و هر دو کتابخانه را با ابزار pip نصب کنید:
pip install requests
pip install beautifulsoup4
گام اول؛ بررسی ساختار صفحه وب و کدهای HTML
پیش از نوشتن کد پایتون باید ساختار وبسایت هدف را بشناسید. صفحات سایت را در مرورگر باز کنید و الگوی آدرسدهی (URL) آنها را بررسی کنید. با جابهجایی بین بخشهای مختلف، ببینید کدام قسمت آدرس ثابت میماند و کدام قسمت تغییر میکند.

برای مشاهده کد منبع (HTML) صفحه، روی بخش یا متن موردنظر راستکلیک کنید. سپس گزینه Inspect یا Inspect Element را از ابزار توسعهدهندگان مرورگر (Developer Tools) انتخاب کنید.
پنل کدهای HTML باز میشود و میتوانید محل دقیق داده موردنظر را ببینید: داده در کدام تگ قرار گرفته است و آن تگ چه نام کلاس (class) یا شناسهای (id) دارد. با شناخت این خصوصیتهای HTML، میتوانید دستورهای استخراج داده را در مراحل بعدی بنویسید.
گام دوم؛ دریافت کدهای HTML صفحه با کتابخانه Requests
برای دانلود کد منبع یا HTML خام یک صفحه وب در پایتون، باید یک درخواست HTTP به آدرس آن صفحه بفرستید. کتابخانه requests این درخواست را ارسال میکند:
import requests
url = "https://www.passiton.com/inspirational-quotes"
response = requests.get(url)
print(response.text)
متد requests.get(url) یک درخواست از نوع GET به آدرس اینترنتی هدف میفرستد و پاسخ سرور را در متغیر response قرار میدهد.
با ویژگی response.text به کدهای HTML صفحه دسترسی پیدا میکنیم. این ویژگی محتوای HTML صفحه وب را به شکل یک رشته متنی (String) برمیگرداند؛ رشتهای که در مرحله بعد میتوانید آن را تجزیه کنید.
رفع خطای ۴۰۳ Forbidden با تنظیم User-Agent
در برخی سایتها، ارسال درخواست مستقیم با خطای دسترسی ۴۰۳ Forbidden روبهرو میشود. برای رفع این مشکل در مثال قبل، باید یک شناسه مرورگر (User-Agent) به درخواست اضافه کنید:

import requests
url = "https://www.passiton.com/inspirational-quotes"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
}
response = requests.get(url, headers=headers)
print(response.text)
در این کد، دیکشنری headers با کلید User-Agent تعریف شده است و بهعنوان آرگومان دوم به متد requests.get ارسال میشود. میتوانید رشته User-Agent متناسب با دستگاه و مرورگر خود را بهصورت آنلاین پیدا کنید و جایگزین این مقدار کنید.
گام سوم؛ تجزیه (Parse) کدهای HTML با BeautifulSoup
پس از دریافت محتوای خام HTML، باید آن را با BeautifulSoup تجزیه (Parse) کنید. کتابخانه این محتوا را به ساختاری درختی تبدیل میکند که میتوانید آن را پیمایش کنید. برای تکمیل کدهای مرحله قبل، کلاس اصلی کتابخانه را از پکیج bs4 با دستور import در پایتون وارد اسکریپت کنید:

import requests
from bs4 import BeautifulSoup
url = "https://www.passiton.com/inspirational-quotes"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
print(soup.prettify())
در این کد، رشته HTML دریافتشده (response.text) همراه با نام تجزیهگر به BeautifulSoup پاس داده شده است تا یک شیء یا وهله جدید ساخته شود. آرگومان دوم نوع پارسر را مشخص میکند. در این مثال از html.parser استفاده شده است که تجزیهگر داخلی پایتون است. BeautifulSoup از پارسرهای دیگری مانند lxml و html5lib هم پشتیبانی میکند. در صورت نیاز میتوانید نام آنها را در آرگومان دوم قرار دهید؛ برای مثال: BeautifulSoup(response.text, ‘html5lib’).
برای اطمینان از اینکه صفحه وب درست بارگذاری شده است، شیء ساختهشده را چاپ میکنیم. متد soup.prettify() کدهای درهمریخته HTML را با تورفتگیهای استاندارد مرتب میکند تا ساختار صفحه برای بررسی چشمی تمیزتر و خواناتر شود.
گام چهارم؛ استخراج دادهها از تگهای HTML
پس از تبدیل HTML خام به یک درخت تجزیه در شیء soup، میتوانید ساختار آن را پیمایش کنید و عناصر موردنیاز، مانند متنها، لینکها یا تصاویر را بیرون بکشید. اگر توسعهدهنده وب ساختار HTML را بر اساس اصول مشخصی نوشته باشد، میتوانید با تعیین نام تگها، کلاسها و شناسهها (ID)، دادهها را دقیق استخراج کنید.
برای تمرین پیمایش این درخت و دریافت اطلاعات، دو مثال عملی را بررسی میکنیم: استخراج مشخصات فردی و آمار بازیکنان جام جهانی از سایت fifa.com و دریافت متن، نام نویسنده و تصویر نقلقولها از سایت passiton.com.
جستجوی تگها با متدهای find و find_all (بر اساس نام تگ، کلاس و ID)
برای پیدا کردن تگهای مشخص در درخت تجزیهشده HTML، از دو متد اصلی find() و find_all() استفاده میکنیم. انتخاب متد به این بستگی دارد که چند عنصر را میخواهید از صفحه استخراج کنید.

متد find() سند HTML را جستجو میکند و فقط اولین تگی را برمیگرداند که با ویژگیهای دادهشده مطابقت دارد. برای مثال، صفحه پروفایل بازیکن در سایت فیفا تگهای div زیادی دارد، اما نام بازیکن در تگی با کلاس fi-p__name نوشته شده است. در سایت نقلقولها نیز بخش اصلی حاوی تمام نقلقولها شناسه (ID) مشخصی دارد. برای دریافت این عناصر یکتا، از کد زیر استفاده میکنیم:
import requests
from bs4 import BeautifulSoup
url = "https://www.passiton.com/inspirational-quotes"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
# یافتن اولین div بر اساس نام کلاس (مثال ساختار سایت فیفا)
player_name_div = soup.find('div', class_='fi-p__name')
# یافتن اولین div بر اساس ID با استفاده از آرگومان attrs (مثال سایت نقلقولها)
container = soup.find('div', attrs={'id': 'all_quotes'})
وقتی چند عنصر در صفحه تگ و کلاس یکسان دارند، متد find_all() تمام موارد منطبق را در قالب یک لیست برمیگرداند. در مثال سایت نقلقولها، برای دریافت تمام باکسهای نقلقول، نام تگ و کلاس را با آرگومان class_ به متد find_all() میدهیم. این متد را میتوان مستقیماً روی متغیر container که در مرحله قبل یافتیم نیز فراخوانی کرد:
import requests
from bs4 import BeautifulSoup
url = "https://www.passiton.com/inspirational-quotes"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
# استخراج لیستی از تمام باکسهای نقلقول
quote_boxes = soup.find_all('div', class_='col-6 col-lg-3 text-center margin-30px-bottom sm-margin-30px-top')
متد find_all() برای دادههایی که کلاس جداگانه ندارند نیز کاربرد دارد. مثلاً در پروفایل بازیکنان فیفا، مقادیر «قد»، «تعداد بازیهای بینالمللی» و «تعداد گلها» در تگهایی با یک کلاس مشترک قرار دارند. اجرای find_all() روی این کلاس، لیستی از تگها میسازد. سپس میتوانیم هر مقدار را با شماره اندیس ترتیبی آن، مانند [۰]، [۱] یا [۲]، از لیست فراخوانی کنیم.
استخراج متن تگها و تمیزکاری دادهها
برای ذخیره محتوای یک تگ به شکل متن و حذف نشانهگذاریهای HTML، ویژگی .text را به انتهای شیء یافتشده اضافه کنید. برای مثال، فراخوانی این ویژگی روی تگ نام بازیکن در سایت فیفا، رشته متنی داخل آن را استخراج میکند.
متن استخراجشده از صفحات وب اغلب کاراکترهای خط جدید (\n) و فاصلههای خالی دارد. برای پاکسازی آنها به تکنیکهای کار با دادههای متنی در پایتون نیاز داریم:
- برای حذف کاراکترهای خط جدید، کاراکترهای \n موجود در متن را با مقدار خالی جایگزین میکنیم.
- متد .strip() فاصلههای اضافی ابتدا و انتهای رشته را حذف میکند؛ مانند box.h5.text.strip() که در استخراج موضوع نقلقولها به کار میرود.
- اگر دو داده مختلف در یک رشته ترکیب شده باشند، متد .split() آنها را از هم جدا میکند. برای نمونه، دستور .split(” #”) متن نقلقول را از نام نویسنده جدا میکند.
برای اعمال این متدها روی اولین باکس نقلقول (box) در مثال قبل، کد را به این شکل مینویسیم:
import requests
from bs4 import BeautifulSoup
url = "https://www.passiton.com/inspirational-quotes"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
quote_boxes = soup.find_all('div', class_='col-6 col-lg-3 text-center margin-30px-bottom sm-margin-30px-top')
box = quote_boxes[0]
# استخراج متن و حذف فاصلههای ابتدا و انتها
theme = box.h5.text.strip()
# جداسازی متن نقلقول از نام نویسنده بر اساس کاراکتر #
quote_text = box.img['alt'].split(" #")
lines = quote_text[0]
author = quote_text[1] if len(quote_text) > 1 else 'Unknown'
print(theme, lines, author)
دسترسی به ویژگیهای (Attributes) تگها و جستجوی پیشرفته
در بسیاری از صفحات وب، دادههایی مانند آدرس تصویر یا متن جایگزین در ویژگیهای (Attributes) تگ قرار دارند. در BeautifulSoup، دسترسی به این مقادیر شبیه دسترسی به کلیدهای یک دیکشنری در پایتون است: نام ویژگی را داخل کروشه قرار میدهید تا مقدار آن را استخراج کنید. برای نمونه، در سایت نقلقولها (passiton.com)، پس از پیدا کردن هر باکس (box)، با دستور box.img[‘src’] آدرس تصویر را از تگ img دریافت میکنیم و با دستور box.img[‘alt’] متن جایگزین آن را میگیریم:
import requests
from bs4 import BeautifulSoup
url = "https://www.passiton.com/inspirational-quotes"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
quotes = []
quote_boxes = soup.find_all('div', class_='col-6 col-lg-3 text-center margin-30px-bottom sm-margin-30px-top')
for box in quote_boxes:
quote_text = box.img['alt'].split(" #")
quote = {
'theme': box.h5.text.strip(),
'image_url': box.img['src'],
'lines': quote_text[0],
'author': quote_text[1] if len(quote_text) > 1 else 'Unknown'
}
quotes.append(quote)
for q in quotes[:5]:
print(q)
وقتی ساختار صفحه پیچیدهتر است، علاوه بر استخراج ویژگیها میتوانید از دو قابلیت دیگر BeautifulSoup برای پیمایش و جستجوی دقیقتر استفاده کنید:
- پس از پیدا کردن یک عنصر مشخص، میتوانید در درخت HTML به سمت بالا حرکت کنید و به تگهای والد (Parent Elements) آن دسترسی پیدا کنید.
- برای فیلتر کردن عناصر بر اساس شرایط دلخواه، میتوانید یک تابع، مانند تابع لامبدا در پایتون، را مستقیماً بهعنوان آرگومان به متدهای جستجوی BeautifulSoup پاس بدهید.
گام پنجم؛ استخراج داده از چندین صفحه وب با استفاده از حلقه
وقتی اطلاعات موردنیاز، مانند پروفایل بازیکنان جام جهانی، در صفحات جداگانه قرار دارد، نوشتن اسکریپت مستقل برای هر صفحه منطقی نیست. برای استخراج همه دادهها با یک اسکریپت، ابتدا الگوی آدرس صفحات (URL) را بررسی کنید و بخشهای مشترک و متغیر را پیدا کنید. برای مثال، آدرس صفحه پروفایل رونالدو در سایت فیفا به این شکل است:
https://fifa.com/worldcup/players/player/201200/profile.html
در آدرس پروفایل تمام بازیکنان، بخشهای URL ثابت و مشترک است و فقط شناسه بازیکن (ID) تغییر میکند. این شناسه در نمونه بالا ۲۰۱۲۰۰ است. بنابراین، پیش از دریافت جزئیات هر پروفایل باید لیست ID تمام بازیکنان را به دست آورید.
بررسی کد منبع صفحه اصلی بازیکنان مشخص میکند که شناسهها در تگهای لینک، یعنی <a> یا همان تگ anchor، قرار گرفتهاند. برای استخراج آنها، یک لیست خالی تعریف کنید و مانند روشهای قبلی، کلاس تگهای <a> را پیدا کنید. سپس حلقهای به تعداد کل بازیکنان، یعنی ۷۳۶ بازیکن، اجرا کنید. در هر تکرار، شناسه استخراجشده را با متد append در پایتون به لیست اضافه کنید.
پس از جمعآوری شناسهها، یک حلقه نهایی روی لیست آنها تعریف کنید. در هر تکرار، شناسه جدید در URL مبنا قرار میگیرد و اسکریپت بهصورت خودکار صفحه مربوط را دانلود میکند و پارامترهای موردنظر را از تمام صفحات بیرون میکشد. برای بررسی صحت عملکرد اسکریپت هنگام اجرا، میتوانید در هر مرحله فقط نام بازیکن دریافتشده را چاپ کنید.
گام ششم؛ ذخیرهسازی دادههای استخراجشده در فایل CSV
پس از دریافت پارامترهای مختلف هر آیتم، آنها را در یک دیکشنری یا «دیکشنری مرتب» (Ordered Dictionary) قرار میدهیم تا دادهها به ترتیب استخراج ذخیره شوند. در طول اجرای حلقه، هر دیکشنری را به یک لیست اضافه میکنیم. این لیست مجموعهای ساختاریافته از دادههاست که میتوان به دو روش از آن خروجی CSV گرفت.

در روش اول، با ماژول داخلی csv پایتون، لیست دیکشنریها را مستقیماً در فایل مینویسیم. کد کامل زیر دادههای استخراجشده از سایت نقلقولها را در فایل quotes.csv ذخیره میکند:
import csv
import requests
from bs4 import BeautifulSoup
url = "https://www.passiton.com/inspirational-quotes"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
quotes = []
quote_boxes = soup.find_all('div', class_='col-6 col-lg-3 text-center margin-30px-bottom sm-margin-30px-top')
for box in quote_boxes:
quote_text = box.img['alt'].split(" #")
quote = {
'theme': box.h5.text.strip(),
'image_url': box.img['src'],
'lines': quote_text[0],
'author': quote_text[1] if len(quote_text) > 1 else 'Unknown'
}
quotes.append(quote)
filename = "quotes.csv"
with open(filename, mode='w', newline='', encoding='utf-8') as file:
writer = csv.DictWriter(file, fieldnames=['theme', 'image_url', 'lines', 'author'])
writer.writeheader()
for quote in quotes:
writer.writerow(quote)
در قسمت پایانی کد، دستور with open() یک فایل CSV جدید با حالت نوشتن (mode=’w’) و انکودینگ utf-۸ ایجاد میکند. سپس csv.DictWriter() شیء نویسنده را بر اساس نام ستونهای مشخصشده در fieldnames تنظیم میکند. متد writer.writeheader() ردیف سرستونها را در فایل مینویسد. حلقه for نیز با فراخوانی writer.writerow(quote)، هر دیکشنری موجود در لیست quotes را در یک سطر جداگانه ثبت میکند.
روش دوم در پروژه استخراج اطلاعات بازیکنان جام جهانی به کار رفت. در این روش میتوان لیست دیکشنریها را با تبدیل داده به دیتافریم در پایتون به یک قاب داده (Dataframe) تبدیل کرد و مستقیماً از آن خروجی CSV گرفت. یادگیری کتابخانه pandas به شما در ساخت دیتافریم و مدیریت دادههای جدولی کمک میکند.
تسلط بر دانلود صفحات وب با requests، تجزیه HTML با BeautifulSoup و ذخیرهسازی دادهها، یک مهارت کاربردی مهم است که میتواند در مصاحبههای شغلی جایگاه شما را نسبت به رقبا بهتر کند.
برای یادگیری پروژهمحور و تمرین عملی بیشتر در این زمینه، میتوانید در دورههای مکتبخونه شرکت کنید.