مقدمه
در این مقاله عملی و فنی میخواهیم چگونگی استفاده از XPath همراه با Playwright را برای وب اسکریپینگ توضیح دهیم. هدف این است که پس از خواندنِ این راهنما، بتوانید عناصر صفحه را با XPath پیدا کنید، رفتارهای منتظرمانی (waiting) را درست مدیریت کنید، روی عناصر کلیک و اسکرینشات بگیرید، و در نهایت دادهها را از صفحات دینامیک استخراج کنید. مثالهای عملی با Playwright به زبان Python آورده شده تا مناسب توسعهدهندههای پایتون سطح متوسط باشد.
چرا XPath؟ چه زمانی از CSS استفاده کنیم
CSS سلکتورها ساده و خوانا هستند و برای اغلب موارد مناسباند؛ اما وقتی ساختار صفحه پیچیده است، عنصر شناساییشده id/class ندارد، یا نیاز به انتخاب بر اساس متن/نسبت اجداد/خواهران است، XPath تواناییهای بیشتری دارد. در انتخاب بین این دو به پایداری ساختار صفحه و خوانایی کد توجه کنید.
- مزایای XPath: انتخاب بر اساس متن، محاسبه نسبتهای والد/خواهر، و فیلترهای پیچیده.
- مزایای CSS: سادگی، سرعت و سازگاری با ابزارهای front-end.
مروری بر ساختار و سینتکس XPath
نمادهای پایهای:
- // : جستجو از هر نقطه در اسناد.
- / : مسیر از ریشه (Absolute XPath).
- [@attr='value'] : فیلتر بر اساس مقدار attribute.
- contains()، text()، و محاسبات موقعیتی مانند (//ul/li)[1].
مثال ساده: //h3[@class='country-name'] هر تگ h3 با کلاس country-name را در سراسر سند انتخاب میکند.
پایگاه: Playwright + XPath (نمونه سریع به صورت Python)
مثال زیر نشان میدهد چگونه با Playwright به یک صفحه برویم، عنصرِ منتخب را با XPath پیدا کنیم و متن آن را استخراج کنیم. ورودی: آدرس صفحه. خروجی: لیست متنهای استخراجشده.
from playwright.async_api import async_playwright
import asyncio
async def get_country_names():
async with async_playwright() as p:
browser = await p.chromium.launch()
context = await browser.new_context()
page = await context.new_page()
# ورودی: URL صفحه
await page.goto('https://www.scrapethissite.com/pages/simple/')
# Locator با XPath (نکته: Playwright XPath را مستقیم میپذیرد)
locator = page.locator('//h3[@class="country-name"]')
# خروجی: لیست متنها
names = await locator.all_text_contents()
print(names)
await browser.close()
asyncio.run(get_country_names())
توضیح خطبهخط:
- async_playwright() : شروع context آسنکرون Playwright.
- p.chromium.launch() : راهاندازی مرورگر (headless بهطور پیشفرض).
- page.goto(url) : ناوبری به آدرس؛ میتواند timeouts و waitUntil داشته باشد.
- page.locator(xpath) : ساختهشدن یک Locator؛ Locator تا زمان عملیات واقعی resolve نمیشود.
- locator.all_text_contents() : همه متنهای عناصر یافتهشده را برمیگرداند.
انتخاب عنصر: Absolute vs Relative XPath
Absolute XPath مسیر کامل از ریشه است (/html/body/...), که در صفحات پایدار مفید اما شکننده است. Relative XPath با // انعطافپذیرتر و قابل نگهداریتر است؛ مخصوصا برای سایتهایی که DOM آنها تغییر میکند، توصیه میشود از relative و فیلترهای کلاس/attribute یا متن استفاده کنید.
منتظر ماندن (Waiting) و پایداری
Playwright در بسیاری از عملیاتها انتظار داخلی دارد، اما در موارد زیر از explicit waits استفاده کنید:
- محتوای دینامیک که با AJAX بارگذاری میشود
- وقتی میخواهید مطمئن شوید که یک سلکتور خاص وجود دارد قبل از اقدام
# مثال: منتظر ماندن برای وجود XPath
await page.wait_for_load_state('networkidle')
await page.wait_for_selector('//*[@class="product_pod"]/h3')
توجه: Locatorها خودشان اغلب صبر و retry توکار دارند؛ یعنی وقتی از متدهای اقدام روی locator استفاده میکنید (مثل click() یا fill())، Playwright تلاش میکند تا شرایط لازم برقرار شود و سپس عمل را انجام دهد. اما برای extract یا evaluate ها بهتر است ابتدا با wait_for_selector یا wait_for_load_state اطمینان حاصل کنید.
اجرای عملیات: کلیک، پر کردن و اسکرینشات
مثال: کلیک روی آیتمی که متن مشخصی دارد.
# کلیک روی عنصر بر اساس متن با XPath
link_xpath = '//a[contains(text(), "Samsung galaxy s6")]'
await page.wait_for_selector(link_xpath)
await page.click(link_xpath)
برای گرفتن اسکرینشات:
await page.screenshot(path='screenshot.png', full_page=True)
نکته: page.click میتواند مستقیم XPath را بپذیرد؛ ولی برای عملیات پیچیدهتر بهتر است locator ساخته و از آن استفاده کنید تا readability و قابلیت reuse افزایش یابد.
اسکرول خودکار و صفحات بینهایت
برای سایتهایی که با اسکرول بارگذاری بیشتر میشوند، از حلقه اسکرول استفاده کنید تا تا پایان محتوا پیمایش شود.
# نمونه ساده اسکرول تا زمانی که ارتفاع تغییر نکند
previous_height = 0
while True:
await page.evaluate('window.scrollBy(0, window.innerHeight)')
await page.wait_for_timeout(1500)
current_height = await page.evaluate('document.documentElement.scrollHeight')
if current_height == previous_height:
break
previous_height = current_height
این روش کمک میکند جاوااسکریپتِ بارگذاریِ تنبل content را فراخوانی کند و سپس بتوانید عناصر جدید را با XPath انتخاب کنید.
مثال: لاگین اتوماتیک (با رعایت امنیت)
ورودی: نام کاربری/رمز عبور (ترجیحا از متغیر محیطی). خروجی: صفحه پس از ورود و اسکرینشات.
import os
from playwright.async_api import async_playwright
import asyncio
async def login_example():
username = os.getenv('SCRAPE_USERNAME')
password = os.getenv('SCRAPE_PASSWORD')
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.goto('https://demo.applitools.com/')
# استفاده از XPath برای فیلدها
await page.locator('//input[@id="username"]').fill(username)
await page.locator('//input[@id="password"]').fill(password)
await page.locator('//a[@id="log-in"]').click()
await page.wait_for_load_state('networkidle')
await page.screenshot(path='after_login.png')
await browser.close()
# توجه: اطلاعات حساس را در کد هاردکد نکنید؛ از محیط یا vault استفاده کنید.
نکات امنیتی و اخلاقی:
- هرگز credentials را هاردکد نکنید؛ از متغیر محیطی یا secrets manager استفاده کنید.
- قوانین سایت را رعایت کنید و برای سایتهایی که اجازه اسکریپ ندارند، اقدام نکنید.
استخراج ساختیافته دادهها (مثال: کتابها)
برای استخراج چند فیلد از هر آیتم، ترکیب XPath برای انتخاب container و سپس query داخلی برای هر فیلد مناسب است. مثال زیر نام، قیمت و امتیاز هر کتاب را از لیست میخواند.
async def extract_books(page):
# انتخاب کانتینرهای هر محصول (با CSS در این مثال برای خوانایی)
containers = await page.locator('li.col-xs-6.col-sm-4.col-md-3.col-lg-3').all()
results = []
for i in range(len(containers)):
c = page.locator(f'(//li[contains(@class, "col-xs-6")])[{i+1}]')
name = await c.locator('h3 a').inner_text()
price = await c.locator('p.price_color').inner_text()
rating_class = await c.locator('p.star-rating').get_attribute('class')
rating = rating_class.split()[1] if rating_class else ''
results.append({'name': name.strip(), 'price': price.strip(), 'rating': rating})
return results
نکته: استفاده از ترکیب CSS و XPath زمانی که یک روش سادهتر است بلامانع است—هدف خوانایی و پایداری است.
بهترینروشها و نکات عملکردی
- از XPathهای ساده و مشخص استفاده کنید: به جای مسیریابی طولانی، روی شناسهها، کلاسها یا متن تکیه کنید.
- اجتناب از Absolute XPath: مسیرهای کامل شکنندهاند؛ از relative و predicates استفاده کنید.
- حداکثر استفاده از Locatorها: Locatorها retry و wait توکار دارند؛ آنها را بازاستفاده کنید تا race condition کاهش یابد.
- مدیریت خطا و زمانسنجی: از try/except و timeout معقول استفاده کنید و برای عملیات طولانی از backoff/ retry بهره ببرید.
- حفظ احترام به سایت: نرخ درخواستها را کنترل کنید، از هدر مناسب استفاده کنید و قوانین robots.txt و سیاستهای سایت را رعایت کنید.
- حفاظت از دادههای حساس: credentials را در محیط امن نگهداری کنید و در گزارشها آنها را لو ندهید.
جمعبندی
XPath ابزار قدرتمندی برای انتخاب دقیقِ عناصر در اسکریپها و تستها است و وقتی با Playwright بهدرستی ترکیب شود، امکان ساخت اسکریپهای قابلاعتماد برای وب اسکریپینگ فراهم میشود. نکات عملی این مقاله: از relative XPath استفاده کنید، منتظر بودن (waiting) را مدیریت کنید، از Locatorها برای کاهش خطا استفاده کنید، و برای امنیت و پایداری کد بهترینروشها را رعایت کنید. با کمی تمرین روی مثالهای بالا، میتوانید ترکیبهای قدرتمندی برای استخراج دادههای ساختیافته بسازید.





