Playwright: ส่องเว็บแบบคนจริง แต่เร็วกว่าเยอะ
เบื่อมั้ยกับการต้องมานั่งคลิกๆ กรอกๆ ข้อมูลในเว็บซ้ำๆ ซากๆ? หรือจะเทสเว็บทีไรก็ต้องกดเองทุกรอบ? นี่แหละคือจุดที่เครื่องมือพวก Automation Testing อย่าง Playwright เข้ามาช่วยชีวิตเราได้โคตรดีเลย!
สมัยก่อนเราอาจจะคุ้นกับ Selenium มาบ้าง แต่บอกตรงๆ ว่า Playwright นี่มันมาแรงกว่าเยอะในหลายๆ มุมนะ ทั้งเร็วกว่า ใช้โค้ดน้อยกว่า แถมยังรองรับ Browser เกือบทุกเจ้า ไม่ว่าจะเป็น Chromium (Chrome), Firefox, หรือ WebKit (Safari) คือเขียนทีเดียวรันได้หมดอะ เจ๋งปะล่ะ?
เริ่มต้นง่ายๆ Install ยังไง?
เอาจริงๆ ไม่ได้ยากอะไรเลยนะ แค่มี Python ติดเครื่องก็พอละ (จะ 3.7+ ก็ได้) เปิด Terminal หรือ CMD ขึ้นมาแล้วก็รันคำสั่งนี้:
pip install playwright
playwright install
ไอ้ playwright install นี่มันจะไปโหลด Browser ตัวที่จำเป็นมาให้เราอัตโนมัติ ไม่ต้องไปหาโหลดเองให้วุ่นวาย สะดวกจริงไรจริง
โค้ดแรก: เปิดเว็บ ถ่ายรูป
มาลองโค้ดแรกแบบง่ายๆ กันเลยดีกว่า แค่เปิดเว็บ Google แล้วก็ถ่ายรูปเก็บไว้ดูซะหน่อย
import asyncio
from playwright.async_api import Page, expect, async_playwright
async def run():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=False) # ลองเป็น True ถ้าไม่อยาก ให้มันเปิดหน้าจอขึ้นมา
page = await browser.new_page()
print("กำลัง เปิด Google...")
await page.goto("https://www.google.com")
print("ถ่ายรูปแล้วนะ! ไปดูที่ screenshot.png")
await page.screenshot(path="screenshot.png")
print("ปิดบราวเซอร์ละนะ")
await browser.close()
if __name__ == "__main__":
asyncio.run(run())
โค้ดข้างบนเนี่ย สิ่งที่ต้องรู้คือ Playwright มันทำงานแบบ async/await นะ คือถ้าใครไม่คุ้นกับ Python asyncio อาจจะงงๆ นิดหน่อย แต่ก็คือมันทำงานแบบ non-blocking อ่ะ ทำให้มันเร็วกว่าการทำอะไรแบบ synchronous เยอะเลย
รันโค้ดปุ๊บ จะเห็น Chrome เปิดขึ้นมาแว้บหนึ่ง (ถ้า headless=False นะ) แล้วก็ปิดไป พร้อมกับมีไฟล์ screenshot.png อยู่ในโฟลเดอร์เดียวกัน ลองเปิดดูดิ!
ลองกรอกฟอร์ม ค้นหาข้อมูล
เอาละ มาลองทำอะไรที่ซับซ้อนขึ้นอีกนิด เช่น เปิด Google เหมือนเดิม แต่คราวนี้จะลองค้นหาคำว่า "Playwright Python" ดู
import asyncio
from playwright.async_api import Page, expect, async_playwright
async def search_on_google():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=False) # เปิดให้เห็น
page = await browser.new_page()
await page.goto("https://www.google.com")
# หาช่อง search bar โดยใช้ Selector (จะใช้ id, name, class หรืออื่นๆ ก็ได้)
# ใน Google ช่องค้นหาหลักๆ จะใช้ name="q"
search_input = page.locator('textarea[name="q"]')
print("เจอช่องค้นหาละ")
# พิมพ์ข้อความลงไป
await search_input.fill("Playwright Python")
print("พิมพ์ 'Playwright Python' ละ")
# กด Enter หรือคลิกปุ่มค้นหา
# await search_input.press("Enter") # กด Enter ก็ได้
# หรือจะคลิกปุ่ม "Google Search"
search_button = page.locator('input[name="btnK"]').first # บางทีมันมีปุ่มซ่อนอยู่ ต้องเอาอันแรก
await search_button.click()
print("กดค้นหาแล้ว")
# รอมันโหลดหน้าผลลัพธ์
await page.wait_for_url("https://www.google.com/search?q=Playwright+Python*")
print("โหลดหน้าผลลัพธ์เสร็จละ")
# ถ่ายรูปผลลัพธ์
await page.screenshot(path="search_results.png")
await browser.close()
if __name__ == "__main__":
asyncio.run(search_on_google())
ตรง page.locator('textarea[name="q"]') เนี่ย คือการบอก Playwright ว่าให้ไปหา Element ที่เป็น textarea แล้วมี name เป็น q นะ อันนี้ต้องดู inspect elementของเว็บที่เราอยากจะ automate เอานะครับ บางทีมันไม่ใช่ name อาจจะเป็น id หรือ class ก็ได้
แล้วก็ await search_button.click() บางทีปุ่มมันอาจจะโหลดช้า หรือมีปุ่มหลายอันที่ Selector เหมือนกัน ต้องระวังหน่อย ผมเลยใส่ .first เข้าไปเพื่อบอกว่าเอาอันแรกที่เจอเลย บางทีถ้าไม่ระวังมันจะเจอ Error เช่น PlaywrightTimeoutError: Timeout 30000ms exceeded. Expected at least one element to be visible, enabled or editable. อันนี้คือมันหาไม่เจอหรือไม่พร้อมใช้งานนั่นแหละ
ปัญหาที่เจอบ่อยๆ และวิธีแก้แบบบ้านๆ
หนึ่งในปัญหาคลาสสิกของพวก Automation คือ "Element ไม่เจอ" หรือ "Element ยังไม่พร้อมให้ interact" นี่แหละครับ
- Element ไม่เจอ (Selector ผิด): อันนี้เจอโคตรบ่อย เพราะเว็บมันชอบเปลี่ยนโครงสร้าง HTML หรือเราเขียน Selector ผิดเอง วิธีแก้คือเปิด Developer Tools (กด F12 ใน Chrome/Firefox) แล้ว
Inspectหา Element ที่ต้องการให้ชัวร์ๆ บางที Selector ที่ดูง่ายๆ อาจจะไม่ Unique ก็ได้ ลองใช้page.locator('text="ข้อความบนปุ่ม"')หรือpage.get_by_role('button', name='ส่งข้อมูล')แทนก็ได้ มันฉลาดขึ้นเยอะนะ. - Element โหลดไม่ทัน: บางทีเว็บมันโหลดข้อมูลมาแบบ Async ทำให้ Element ที่เราอยากจะคลิกยังไม่มีอยู่บนหน้าจอ หรือมีแล้วแต่ยังกดไม่ได้ Playwright มันฉลาดพอที่จะรอระดับนึงอยู่แล้ว แต่ถ้าไม่พอ ก็ใช้
page.wait_for_selector('div.some-class-that-appears-later')หรือexpect(element).to_be_visible()เข้าช่วยได้
อย่างเช่น ถ้าปุ่มมันขึ้นมาหลังจากโหลดข้อมูลเสร็จแล้ว เราอาจจะต้องรอแบบนี้:
# ... โค้ดส่วนบน ...
await page.goto("https://some-dynamic-website.com")
# รอให้ div ที่มี id เป็น 'content-loaded' ปรากฏขึ้นมา
await page.wait_for_selector('div#content-loaded')
# พอชัวร์ว่า content โหลดแล้ว ค่อยไปหาปุ่มข้างใน
some_button = page.locator('div#content-loaded button.submit')
await some_button.click()
# ... โค้ดส่วนล่าง ...
หรือบางทีเราอยากจะเช็คว่า Text นี้ปรากฏในหน้าเว็บแล้วจริงๆ ค่อยไปต่อ ก็ใช้ expect ช่วยได้:
await page.goto("https://some-website.com/success")
# รอให้ข้อความ "ทำรายการสำเร็จ!" โผล่มาใน element ที่เป็น div
await expect(page.locator('div:has-text("ทำรายการสำเร็จ!")')).to_be_visible()
print("เห็นข้อความสำเร็จแล้ว")
คิดเห็นส่วนตัวนะ
ผมว่า Playwright นี่มันโคตรดีเลยสำหรับงานที่ต้อง Automated Web Browser ไม่ว่าจะเอาไปทำ Test, Scraping (แต่ต้องระวังเรื่อง Terms of Service ของเว็บนะ), หรือแม้แต่ทำ Bot เล็กๆ น้อยๆ ใช้เองให้ชีวิตง่ายขึ้น เพราะมันเร็ว เสถียร แล้วก็ API มันใช้ง่ายกว่า Selenium เยอะ ส่วนตัวนะชอบมาก เพราะเขียน Python แล้วมันดูเป็นธรรมชาติกว่าเยอะเลยคือมันจบในตัวดีอะ ไม่ต้องไปหา Driver อะไรมาลงเพิ่มให้วุ่นวายเหมือนเมื่อก่อน
ข้อเสียก็อาจจะมีเรื่องที่ว่ามันใหม่กว่าหน่อย Community อาจจะยังไม่ใหญ่เท่า Selenium (แต่ก็โตเร็วมากนะ) แล้วก็บางทีการ Debug โค้ด asyncio อาจจะใช้เวลาทำความเข้าใจนิดนึงสำหรับคนที่ไม่เคยใช้ แต่เชื่อเหอะ คุ้ม!
ถ้าใครอยากลองเอาไปใช้กับงานจริงจัง ผมแนะนำให้ลองศึกษาเรื่อง Test Runner อย่าง pytest-playwright ดูนะ มันจะช่วยจัดระเบียบ Test Case เราได้ดีขึ้นเยอะเลย