مقدمه
این مقاله یک راهنمای عملی و آموزشی برای توسعهدهندگان Python در سطح متوسط است که میخواهند از Selenium برای وب اسکریپینگ با تکیه بر XPath استفاده کنند. هدف این راهنما این است که با مفاهیم DOM و نحو XPath آشنا شوید، تفاوت مسیرهای مطلق و نسبی را درک کنید، تکنیکهای پیشرفته مثل contains()، محورها (axes) و شروط (predicates) را ببینید و در نهایت چند الگوی واقعی و مقاوم برای استخراج داده در پروژههای تولیدی یاد بگیرید. در پایان این مقاله شما قادر خواهید بود انتخاب بهتری بین روشها داشته باشید و نمونهکدهای کاربردی را در پروژهتان اعمال کنید.
چیست Selenium و چرا برای اسکریپینگ مفید است
Selenium یک فریمورک برای اتوماسیون مرورگر است که امکان تعامل با صفحات پویا (جاوااسکریپتمحور) را شبیه رفتار کاربر واقعی فراهم میکند. وقتی با درخواستهای ساده HTTP (مثل requests) به هدف نرسیدید—مثلاً محتوای صفحه توسط JS بارگذاری میشود—Selenium به کمک WebDriver و مرورگر واقعی میآید تا DOM نهایی را استخراج کنید.
نکات کوتاه درباره نصب و آمادهسازی:
- نسخهٔ WebDriver باید با نسخهٔ مرورگر شما همخوانی داشته باشد.
- با pip میتوانید Selenium را نصب کنید.
- برای محیطهای سرور، از حالت headless، تنظیم user-agent و مدیریت کرنلهای مرورگر استفاده کنید.
آزمایش نصب — اسکریپت تست سریع
یک اسکریپت کوچک که فقط مرورگر را باز میکند، صفحهای را میگیرد و عنوان را چاپ میکند. این نمونه ورودیها و خروجیها را نشان میدهد:
from selenium import webdriver
from selenium.webdriver.common.by import By
# ورودی: آدرس هدف در متغیر TARGET_URL
TARGET_URL = "YOUR_TARGET_URL"
# باز کردن Chrome (فرض شده chromedriver نصب و در PATH قرار دارد)
driver = webdriver.Chrome()
# خروجی: عنوان صفحه
driver.get(TARGET_URL)
title = driver.title
print(f"Page title: {title}")
# پایان: بستن مرورگر
driver.quit()
توضیح خطبهخط:
- webdriver.Chrome(): یک نمونه مرورگر باز میکند.
- driver.get: آدرس صفحه را بارگذاری میکند.
- driver.title: عنوان صفحه را برمیگرداند (نوع: str).
- driver.quit(): مرورگر را کامل میبندد تا منابع آزاد شوند.
XPath چیست و مروری بر DOM
XPath یک زبان برای نشانهگذاری مسیر در اسناد XML/HTML است. هر صفحه HTML یک درخت (DOM) است؛ XPath راهی برای پیمایش این درخت و انتخاب دقیق گرهها (nodes) فراهم میکند. بههمین دلیل XPath برای استخراج ساختاریافتهٔ عناصر صفحه عالی است.
اصطلاحات کلیدی:
- گِرهٔ ریشه (root): معمولاً <html>
- فرزند (child) و نیاکان (ancestor)
- مسیرهای مطلق (با /) و نسبی (با //)
سینتکس پایهای XPath و اپراتورها
نمونهٔ یک XPath مطلق:
/html/body/div/div[1]/div[1]/h1/a
معنای قطعات:
- /html: شروع از ریشه html
- /div[1]: اولین div در آن سطح
- //a: هر a که در هر نقطه از درخت یافت شود (مسیر نسبی)
اپراتورهای رایج:
- //: جستجوی در هر جایی از درخت
- /: فرزند مستقیم (مسیر مطلق)
- . و ..: گره فعلی و والد
- @: دسترسی به صفتها، مثلاً //*[@class='quote']
پریدیکتها (Predicates) و توابع مفید
پریدیکتها داخل کروشه قرار میگیرند و به شما اجازه میدهند روی مقدار و صفتها فیلتر بگذارید:
- //div[@class='quote'] — همهٔ divهایی که کلاسشان quote است.
- //*[contains(text(), 'Einstein')] — هر گرهای که متنش حاوی کلمهٔ «Einstein» باشد.
- starts-with(@id, 'user') — صفاتی که با الگوی خاصی شروع میشوند.
این توابع ترکیبپذیر و بسیار قدرتمندند و به شما امکان میدهند حتی بدون داشتن کلاس یا id مشخص، محتوای منطقی را انتخاب کنید.
مسیر مطلق در مقابل مسیر نسبی — مزایا و معایب
مسیر مطلق (/html/...):
- مزایا: مشخص و دقیق برای یک عنصر خاص.
- معایب: شکننده است؛ هر تغییر کوچک در DOM آن را از کار میاندازد.
مسیر نسبی (//tag):
- مزایا: مقاومتر، کوتاهتر و مناسب صفحات بزرگ.
- معایب: ممکن است نتایج متعدد برگرداند و نیاز به فیلتر دقیقتر داشته باشد.
محورها (Axes) و پیمایش پیچیده
محورها به شما امکان میدهند در جهتهای مختلف درخت حرکت کنید:
- ancestor:: — جستجو به سمت بالا (والدها)
- child:: — جستجوی فرزندان
- following-sibling:: و preceding-sibling:: — عناصر همسطح بعدی/قبلی
مثال عملی برای بدستآوردن والدها و فرزندان یک div:
# پیدا کردن همهٔ والدهای یک div
ancestors = driver.find_elements(By.XPATH, "//ancestor::div")
# پیدا کردن همهٔ فرزندان یک div
children = driver.find_elements(By.XPATH, "//child::div")
توضیح: این عبارات معمولاً با شرایط بیشتر ترکیب میشوند تا خروجی پراکنده نشود. در مثالهای واقعی غالباً ابتدا با یک XPath فیلترینگ سطح بالا انجام میدهیم و سپس محورها را برای پیمایش دقیقتر استفاده میکنیم.
تکنیکهای مقاوم برای پیدا کردن عناصر در Selenium
در عمل باید از ترکیبی از روشها استفاده کنید تا ربات شما پایدار و قابل نگهداری باشد:
- استفاده از WebDriverWait به جای sleep() برای منتظر ماندن تا عنصر ظاهر شود.
- ترجیح دادن پریدیکتهایی که بر پایهٔ متن یا صفتهای پایدار هستند (مثلاً contains(text(), '...') یا @data-*).
- استفاده از try/except برای مدیریت استثناهایی مثل NoSuchElementException.
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
wait = WebDriverWait(driver, 10)
try:
# منتظر بمان تا اولین هدر h1 در DOM ظاهر شود
h1 = wait.until(EC.presence_of_element_located((By.XPATH, "//h1")))
print(h1.text)
except TimeoutException:
print("عنصر مورد نظر ظاهر نشد؛ بررسی کنید که selector درست است یا اتصال شبکه مشکل دارد")
توضیح: WebDriverWait بارها DOM را چک میکند و به جای منتظر ماندن ثابت، سریعتر عمل میکند؛ این باعث پایداری و بهبود سرعت میشود.
الگوی واقعی: استخراج قیمتها و ذخیرهسازی مقاوم
الگوی پیشنهادی برای یک ربات تولیدی:
- آمادهسازی WebDriver با گزینههای مناسب (headless/disable-automation).
- قرار دادن proxy/rotating proxies (مثلاً سرویسهایی مانند ScrapeOps) و تنظیم user-agent.
- بارگذاری صفحه و استفاده از WebDriverWait برای المانهای هدف.
- استخراج متن با XPathهای مقاوم مانند //*[contains(text(), '$')] و فیلتر کردن نتایج زائد.
- ذخیرهٔ خروجی در فرمت مناسب (CSV/JSON) و بستن صحیح مرورگر.
نمونهٔ سادهٔ تابعی برای تبدیل URL به یک URL پروکسی (بدون درج آدرس واقعی) و استخراج قیمتها:
from urllib.parse import urlencode
import csv
API_KEY = "YOUR_API_KEY"
SCRAPEOPS_PROXY = "SCRAPEOPS_PROXY_URL" # مقدار واقعی را در تولید قرار دهید
def get_proxied_url(url):
payload = {"api_key": API_KEY, "url": url}
return SCRAPEOPS_PROXY + urlencode(payload)
# نمونهٔ کلی استخراج: ورودی URL و خروجی فایل CSV
TARGET_URL = "YOUR_TARGET_URL"
proxied = get_proxied_url(TARGET_URL)
# driver.get(proxied) # در محیط تولید از آدرس پروکسی استفاده کنید
# سپس موارد حاوی $ را پیدا و به CSV منتقل کنید
prices = driver.find_elements(By.XPATH, "//*[contains(text(), '$')]")
with open('prices.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['price_text'])
for p in prices:
txt = p.text.strip()
if txt and txt != '$':
writer.writerow([txt])
توضیح ورودی/خروجی و نقشها:
- get_proxied_url(url): ورودی یک URL، خروجی رشتهای است که باید به WebDriver داده شود تا درخواست از طریق پروکسی ارسال شود.
- بلوک CSV: خروجی نهایی یک فایل prices.csv شامل قیمتهای استخراجشده است.
عملکرد، امنیت و محدودیتها
مواردی که در تولید باید به آنها توجه کنید:
- مدیریت منابع: همیشه driver.quit() را فراخوانی کنید تا حافظه آزاد شود.
- ریسک بلاک شدن: صفحات ممکن است اسکریپرها را شناسایی کنند—از پراکسی، تغییر user-agent و تاخیرهای طبیعی استفاده کنید.
- قوانین و اخلاق: قبل از اسکریپینگ شرایط سرویس و قوانین سایت را بررسی کنید و به robots.txt احترام بگذارید.
- امنیت: کلیدهای API را در متغیر محیطی نگه دارید، آنها را در سورسکد نبافید.
چکلیست بهترین روشها (Best Practices)
- از XPathهای نسبی و با پریدیکتهای پایدار استفاده کنید.
- بهجای time.sleep() از WebDriverWait استفاده کنید.
- Exceptions را مدیریت کنید و لاگبرداری داشته باشید.
- پراکسی و ریتلیمیتینگ را برای پروژههای طولانیمدت لحاظ کنید.
- فرمت خروجی سازگار و تست شده (CSV/JSON/DB) را انتخاب کنید.
خلاصه و جمعبندی
XPath به شما کنترل دقیقی روی انتخاب عناصر در DOM میدهد و در ترکیب با Selenium توانایی اسکریپینگ صفحات پویا را فراهم میکند. مسیرهای نسبی، پریدیکتها، توابعی مثل contains() و محورها ابزارهای کلیدی برای نوشتن selectors مقاوم هستند. در محیطهای تولیدی، حتماً از استراتژیهایی مثل waits، پراکسی و مدیریت خطا استفاده کنید تا رباتتان پایدار، قابل نگهداری و کمخطر باشد.
پیشنهاد عملی: چند صفحهٔ نمونه را با XPathهای مختلف آزمایش کنید، selectors را در مرورگر با ابزار inspect تست کنید و سپس آنها را در اسکریپت با WebDriverWait جایگزین sleep کنید تا بیشترین پایداری را بدست آورید.





