Scrape เว็บที่ว่ายาก ด้วย Playwright (มี Error ให้ดู!)
หลายคนคงเคยเจอปัญหาเวลาจะ Scrape เว็บที่มันโหลดข้อมูลแบบ Dynamic อ่ะ แบบว่าเปิดหน้าเว็บมาทีแรกนะ ไม่มีข้อมูลที่เราต้องการหรอก ต้องรอ JavaScript ทำงาน, fetch API แล้วค่อย render ออกมา โคตรเซ็ง! สมัยก่อนถ้าใช้แค่ requests กับ BeautifulSoup คือจบเห่เลยครับ ทำไม่ได้แน่นอน ต้องไปง้อพวก Selenium ที่มันเปิดเบราว์เซอร์จริงๆ แต่ก็ช้าโคตรๆ แถมกินรีซอร์สเครื่องเยอะอีก.
พอมาเจอ Playwright นี่ชีวิตดีขึ้นเยอะเลย มันเป็น Library ที่ใช้ Automate Browser ได้เหมือน Selenium นั่นแหละ แต่เร็วกว่าเยอะ (รู้สึกไปเองมั้ยไม่รู้ แต่มันเร็วขึ้นจริง!) แถมใช้ได้หลายภาษาด้วยนะ ทั้ง Python, Node.js, Java, .NET ที่สำคัญคือมี Headless mode ด้วย ไม่ต้องเปิดหน้าต่างเบราว์เซอร์ให้รกจอเวลาเรา Scrape อยู่หลังบ้าน.
มาลองดูกันดีกว่า ว่าจะเริ่มใช้ยังไง:
ติดตั้ง Playwright
อันดับแรกก็ติดตั้ง Library Python ของ Playwright ก่อนเลย
pip install playwright
เสร็จแล้ว โคตรสำคัญเลยนะ ต้องรันคำสั่งนี้ด้วย เพื่อให้ Playwright มันดาวน์โหลด Browser binaries (Chromium, Firefox, WebKit) มาให้เรา ไม่งั้นเวลาจะรัน มันจะหาเบราว์เซอร์ไม่เจอ แล้วก็พัง!
playwright install
ถ้าลืมขั้นตอนนี้ โอกาสเจอ error "Browser not found" คือ 99% เลยครับ เชื่อผม!
ตัวอย่างง่ายๆ ลอง Scrape เว็บ Dynamic
สมมติว่าเราอยากจะ Scrape หัวข้อจากเว็บที่เป็น Single Page Application (SPA) ที่โหลดข้อมูลด้วย JS (อันนี้ผมขอใช้เป็นเว็บสมมติ หรือเว็บเดโมของ React/Vue เพื่อหลีกเลี่ยงการละเมิด Terms of Service ของเว็บจริงๆ นะครับ)
from playwright.sync_api import sync_playwright
def scrape_dynamic_page(url):
with sync_playwright() as p:
# p.chromium.launch() สามารถเปลี่ยนเป็น p.firefox.launch() หรือ p.webkit.launch() ได้นะ
# headless=True คือไม่เปิดหน้าต่างเบราว์เซอร์ให้เราเห็น
# ถ้าอยากดูว่ามันทำงานยังไง เปลี่ยนเป็น headless=False ได้เลย
browser = p.chromium.launch(headless=True)
page = browser.new_page()
print(f"กำลังเข้าสู่: {url}")
page.goto(url)
# สิ่งที่สำคัญมากๆ สำหรับเว็บ Dynamic คือ "การรอ"!
# บางทีแค่ page.wait_for_load_state('networkidle') ก็พอ
# แต่ถ้าเว็บมันซับซ้อน หรือข้อมูลมาช้า อาจจะต้องรอ Element ที่เราต้องการจริงๆ โผล่มาเลย
try:
# สมมติว่าหัวข้อที่เราอยากได้ อยู่ใน h1 ที่มี class เป็น 'main-title'
print("รอ Element 'h1.main-title'...")
page.wait_for_selector('h1.main-title', timeout=15000) # รอสูงสุด 15 วินาที
title_element = page.locator('h1.main-title').first
if title_element:
print(f"หัวข้อที่เจอ: {title_element.inner_text()}")
else:
print("ไม่เจอหัวข้อที่ต้องการว่ะ.")
except Exception as e:
print(f"เกิดข้อผิดพลาดในการรอ Element: {e}")
print("ลองแคปหน้าจอไว้ดูหน่อยว่าเกิดอะไรขึ้น...")
page.screenshot(path="error_page_screenshot.png")
browser.close()
# ตัวอย่างการเรียกใช้ (อันนี้ URL สมมติ หรือใช้เว็บเดโมของ Framework ต่างๆ ก็ได้)
# scrape_dynamic_page("https://react-redux.realworld.io/#/")
# (เว็บนี้อาจจะมีการเปลี่ยนแปลงตลอด ลองเช็คโครงสร้าง HTML ก่อนใช้นะ)
เจอ Error บ่อยๆ? นี่เลยวิธีแก้ปวดหัว!
สิ่งที่เจอโคตรบ่อยเวลา Scrape เว็บ Dynamic คือ TimeoutError: Waiting for selector 'some_selector' failed: Timeout 30000ms exceeded.
ไอ้ Error นี้อ่ะ คือโคตรปวดหัว! แปลว่า Playwright มันพยายามหา Element ที่เราบอกไป (เช่น h1.main-title) แล้วหาไม่เจอภายในเวลาที่กำหนด (ค่า default คือ 30 วินาที)
สาเหตุหลักๆ ที่ผมเจอประจำเลยนะ:
- Selector ผิด! อันนี้เบสิกสุด แต่พลาดกันบ่อย พิมพ์ผิดบ้าง หรือเว็บมันเปลี่ยน Class Name/ID/โครงสร้าง HTML ไปแล้ว. วิธีแก้คือต้องกลับไป Inspect Element ในเบราว์เซอร์ดูใหม่ว่า Selector ที่เราใช้มันยังถูกอยู่ไหม. ใช้ Chrome DevTools เนี่ยแหละ ช่วยชีวิต.
- JS ยังโหลดไม่เสร็จ หรือข้อมูลมาไม่ถึง: บางทีเว็บมันโหลดข้อมูลช้ากว่าที่เราคิด หรือข้อมูลที่เราอยากได้มันไม่ได้อยู่ใน DOM ตั้งแต่แรก. Playwright รอ Element โผล่มาได้ก็จริง แต่ถ้ามันยังไม่โผล่มาเลยภายในเวลา ก็ Timeout. วิธีแก้คือ:
- เพิ่ม
timeout: ในwait_for_selectorให้มันเยอะขึ้นหน่อย (เช่นtimeout=60000สำหรับ 1 นาที) - รอ
networkidle: ใส่page.wait_for_load_state('networkidle')ไปก่อนหน้าwait_for_selectorอีกที บางทีก็ช่วยได้. - Simulate การกระทำ: ถ้า Element นั้นจะโผล่มาเมื่อเราคลิกปุ่มอะไรบางอย่าง (เช่น "Load More", "Show Details") เราก็ต้องสั่งให้ Playwright คลิกปุ่มนั้นก่อน แล้วค่อยรอ Element ที่จะโผล่มา
- เพิ่ม
# ตัวอย่างแก้ TimeoutError ด้วยการคลิกปุ่ม
from playwright.sync_api import sync_playwright
def scrape_with_button_click(url):
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(url)
try:
# สมมติมีปุ่ม "Load More" ที่ต้องกดถึงจะเห็นข้อมูลเพิ่มเติม
print("กำลังมองหาปุ่ม 'Load More' เพื่อคลิก...")
# ใช้ try-except เพื่อจัดการกรณีไม่เจอ Element
load_more_button = page.locator('button.load-more')
# ตรวจสอบว่าปุ่มมีอยู่จริงไหมก่อนคลิก
if load_more_button.count() > 0:
load_more_button.click()
print("คลิกปุ่ม 'Load More' แล้ว! รอข้อมูลมา...")
# รอมันโหลดเสร็จ หรือรอ Element ใหม่โผล่มา
page.wait_for_load_state('networkidle')
page.wait_for_selector('.new-data-item', timeout=20000) # รอ item ใหม่ๆ โผล่มา
else:
print("ไม่เจอไอ้ปุ่ม 'Load More' แฮะ อาจจะไม่ต้องกด หรือ Selector ผิด.")
# ทีนี้ค่อยหาข้อมูลจริงๆ ที่เราต้องการ
all_items = page.locator('.data-list .item').all_text_contents()
print("ข้อมูลที่เจอ:")
for item in all_items:
print(f"- {item}")
except Exception as e:
print(f"เกิดข้อผิดพลาดร้ายแรง: {e}")
# ถ้าอยาก Debug ให้เห็นภาพ ลอง capture screenshot ตอน Error
print("แคปหน้าจอตอน Error ไว้ที่ error_on_click.png ละนะ.")
page.screenshot(path="error_on_click.png")
finally:
browser.close()
# scrape_with_button_click("https://some-other-dynamic-site.com")
ส่วนตัวนะ Playwright นี่มันโคตรดีตรงที่มันจัดการเรื่อง Asynchronous ได้ดีกว่า Selenium เยอะเลย (ในมุมมองของ API) ไม่ต้องมานั่ง time.sleep() มั่วซั่วบ่อยๆ แบบที่เคยเจอใน Selenium แล้วมานั่งลุ้นว่าข้อมูลจะมาทันไหม มันมี wait_for... มาให้เล่นเยอะมาก ซึ่งทำให้โค้ดเราเขียนง่ายขึ้น แล้วก็เชื่อถือได้มากขึ้น ไม่ต้องเดาสุ่มเวลา.
แต่ก็ไม่ได้หมายความว่า Playwright จะเป็น Magic Bullet ที่แก้ได้ทุกอย่างนะ! เว็บไซต์มันเปลี่ยนตลอดเวลา วันนี้ Scrape ได้สบายๆ พรุ่งนี้อาจจะโดนบล็อก IP หรือโครงสร้างเว็บเปลี่ยนจนโค้ดเราพังก็ได้ ชีวิตคนเขียน Scraper ก็งี้แหละครับ! ต้องทำใจแล้วก็เตรียมพร้อมที่จะแก้โค้ดอยู่เรื่อยๆ 555
หวังว่าบทความนี้จะเป็นประโยชน์กับคนที่กำลังมองหาเครื่องมือ Scrape เว็บแบบปวดหัวน้อยลงนะ หรืออย่างน้อยก็พอจะรู้ว่าถ้าเจอ Error แบบ TimeoutError จะแก้ยังไง :)