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 วินาที)

สาเหตุหลักๆ ที่ผมเจอประจำเลยนะ:

  1. Selector ผิด! อันนี้เบสิกสุด แต่พลาดกันบ่อย พิมพ์ผิดบ้าง หรือเว็บมันเปลี่ยน Class Name/ID/โครงสร้าง HTML ไปแล้ว. วิธีแก้คือต้องกลับไป Inspect Element ในเบราว์เซอร์ดูใหม่ว่า Selector ที่เราใช้มันยังถูกอยู่ไหม. ใช้ Chrome DevTools เนี่ยแหละ ช่วยชีวิต.
  2. 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 จะแก้ยังไง :)

Read more

ไอลีน กู: ตำนานนักสกีฟรีสไตล์ผู้พลิกโฉมวงการและความหมายของชัยชนะ

ไอลีน กู: ตำนานนักสกีฟรีสไตล์ผู้พลิกโฉมวงการและความหมายของชัยชนะ

เจาะลึกเรื่องราวของ Eileen Gu นักสกีฟรีสไตล์ผู้สร้างประวัติศาสตร์ในโอลิมปิก 2026 สถิติที่ไม่เคยมีมาก่อน ประเด็นถกเถียง และความแข็งแกร่งส่วนตัวที่ทำให้เธอก้าวสู่ระดับโลก

By ทีมงาน devdog
วันพระ: คู่มือฉบับสมบูรณ์สำหรับพุทธศาสนิกชนและผู้สนใจยุคใหม่

วันพระ: คู่มือฉบับสมบูรณ์สำหรับพุทธศาสนิกชนและผู้สนใจยุคใหม่

เจาะลึกวันพระและความสำคัญของวันมาฆบูชา 2569 ทั้งวันหยุดราชการ ธนาคาร กิจกรรมเวียนเทียนต้นไม้ และผลกระทบต่อบริการขนส่ง เตรียมตัววางแผนทำบุญและพักผ่อน

By ทีมงาน devdog
ถอดรหัสรักแท้: "บังมัดคลองตันต้นข้าว" เรื่องราวที่สะท้อนการให้อภัยและการเริ่มต้นใหม่

ถอดรหัสรักแท้: "บังมัดคลองตันต้นข้าว" เรื่องราวที่สะท้อนการให้อภัยและการเริ่มต้นใหม่

เจาะลึกงานวิวาห์ "บังมัดคลองตัน" กับ "ต้นข้าว มิสแกรนด์" พร้อมเหตุผลจากใจเจ้าสาวที่เลือกความรักเหนือกาลเวลาและคำวิจารณ์ สู่การเริ่มต้นชีวิตคู่ที่สะท้อนการให้อภัย

By ทีมงาน devdog
ไฮไลท์บอลไทยลีก 2: มหาสารคาม เอสบีที เอฟซี กับฟอร์มร้อนแรงสู่เส้นทางเพลย์ออฟ

ไฮไลท์บอลไทยลีก 2: มหาสารคาม เอสบีที เอฟซี กับฟอร์มร้อนแรงสู่เส้นทางเพลย์ออฟ

เจาะลึกไฮไลท์บอลไทยลีก 2 ของมหาสารคาม เอสบีที เอฟซี กับฟอร์มร้อนแรง ชัยชนะสำคัญจาก ชิตชนก และบทบาทโค้ชดุสิต สู่เส้นทางเพลย์ออฟที่น่าจับตา!

By ทีมงาน devdog