WorkBlog

Python Selenium 動態爬蟲教學:從入門到自動化實戰

趙柏翰
Python Selenium 動態爬蟲教學:從入門到自動化實戰

寫爬蟲寫到一半,發現目標網站的資料怎麼都抓不到?打開開發者工具一看,原來資料是透過 JavaScript 動態載入的。這時候 requests + BeautifulSoup 的組合就不夠用了,你需要的是 Selenium。

老實說,我第一次接觸 Selenium 是因為要爬一個電商網站的即時價格。那個網站用了大量的 AJAX 請求來載入商品資訊,用傳統的 HTTP 請求根本拿不到任何資料。後來用 Selenium 模擬瀏覽器操作,才順利拿到需要的數據。從那之後,Selenium 就成了我處理動態網頁的首選工具。

為什麼需要 Selenium

在討論 Selenium 之前,我們先釐清一個問題:什麼時候需要用 Selenium,什麼時候用 requests 就夠了?

如果目標網站的資料在 HTML 原始碼中就已經存在(也就是伺服器端渲染的靜態頁面),那用 requests 搭配 BeautifulSoup 就足夠了。但如果資料是透過 JavaScript 在瀏覽器端動態生成的,例如無限捲動的社群動態、點擊按鈕後才載入的內容、或是需要登入才能看到的頁面,這些情況就需要一個能執行 JavaScript 的工具——Selenium 就是這個工具。

Selenium 本質上是一個瀏覽器自動化框架。它可以啟動一個真實的瀏覽器(Chrome、Firefox 等),然後透過程式碼控制這個瀏覽器去做任何使用者能做的事:點擊按鈕、填寫表單、捲動頁面、等待元素載入。對於想要把重複性的瀏覽器操作自動化的人來說,Selenium 不只能爬資料,還能做到很多自動化的任務,就像Python 自動化寄信教學中用 SMTP 自動寄信一樣,都是讓程式替你完成繁瑣的工作。

環境安裝與 Selenium 4.x 自動管理 Driver

以前用 Selenium 最麻煩的就是 WebDriver 的管理。你需要手動下載對應瀏覽器版本的 driver,還要確保 driver 的路徑設定正確。瀏覽器一更新,driver 就可能失效,然後又要重新下載。

好消息是,從 Selenium 4.6 開始,這些問題都解決了。Selenium 現在內建了 Selenium Manager,它會自動偵測你的瀏覽器版本並下載對應的 driver。安裝方式非常簡單:

pip install selenium

就這樣,不需要額外安裝 webdriver-manager 或手動下載 chromedriver。Selenium Manager 會在你第一次執行的時候自動處理所有事情。

如果你用的是 Python 虛擬環境(強烈建議),完整的安裝流程是這樣的:

python -m venv venv
source venv/bin/activate  # Windows 用 venv\Scripts\activate
pip install selenium

WebDriver 初始化與基本設定

安裝完成後,來寫第一段 Selenium 程式。最基本的用法是啟動一個 Chrome 瀏覽器並開啟一個網頁:

from selenium import webdriver

driver = webdriver.Chrome()
driver.get("https://example.com")
print(driver.title)
driver.quit()

這段程式碼會啟動一個 Chrome 視窗,導航到 example.com,印出頁面標題,然後關閉瀏覽器。很直覺對吧?

在實際使用中,你通常會想要加一些選項來自訂瀏覽器的行為:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--window-size=1920,1080")
options.add_argument("--disable-notifications")
options.add_argument("--lang=zh-TW")

driver = webdriver.Chrome(options=options)
driver.get("https://example.com")

這些選項可以設定視窗大小、關閉通知彈窗、設定語言等。根據你的需求,可以加入更多的選項來模擬不同的瀏覽器環境。

元素定位方式完整介紹

Selenium 最核心的能力就是找到頁面上的元素並與之互動。它提供了多種定位方式,每種都有適合的使用場景:

from selenium.webdriver.common.by import By

# 透過 ID 定位(最可靠的方式)
element = driver.find_element(By.ID, "search-input")

# 透過 Class Name 定位
elements = driver.find_elements(By.CLASS_NAME, "product-card")

# 透過 CSS Selector 定位(最靈活)
element = driver.find_element(By.CSS_SELECTOR, "div.container > ul > li:first-child")

# 透過 XPath 定位(最強大)
element = driver.find_element(By.XPATH, "//div[@class=price]/span")

# 透過連結文字定位
element = driver.find_element(By.LINK_TEXT, "下一頁")

# 透過 Name 屬性定位
element = driver.find_element(By.NAME, "username")

我個人最常用的是 CSS Selector 和 XPath。CSS Selector 語法簡潔,適合大部分情況。XPath 更為強大,可以做到一些 CSS Selector 做不到的事情,例如透過文字內容定位元素,或是往上層尋找父元素。

一個實用的技巧:在 Chrome 開發者工具中,對著任何元素按右鍵,選擇「Copy」然後選「Copy selector」或「Copy XPath」,就能直接取得該元素的定位路徑。不過自動生成的路徑通常很長,建議手動精簡一下。

等待機制:Implicit Wait 與 Explicit Wait

動態網頁最大的挑戰就是時機問題——你的程式執行速度比瀏覽器載入速度快得多。如果元素還沒載入就嘗試操作,程式就會報錯。Selenium 提供了兩種等待機制來解決這個問題。

Implicit Wait(隱式等待):設定一次,全域生效。當 Selenium 找不到元素時,會等待指定的秒數再嘗試。

driver.implicitly_wait(10)  # 最多等 10 秒

Explicit Wait(顯式等待):針對特定條件等待,更加精確。搭配 WebDriverWait 和 expected_conditions 使用:

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 等待元素可被點擊
button = WebDriverWait(driver, 10).until(
    EC.element_to_be_clickable((By.CSS_SELECTOR, ".submit-btn"))
)

# 等待元素出現在 DOM 中
element = WebDriverWait(driver, 15).until(
    EC.presence_of_element_located((By.ID, "result"))
)

# 等待元素可見
element = WebDriverWait(driver, 10).until(
    EC.visibility_of_element_located((By.CLASS_NAME, "data-table"))
)

我的建議是:用 Implicit Wait 設定一個基本的等待時間,然後在關鍵的互動步驟使用 Explicit Wait。不要把 Implicit Wait 設得太長,否則當元素真的不存在時,程式會等很久才報錯。

常用操作:點擊、輸入與捲動

找到元素後,最常見的操作就是點擊、輸入文字和捲動頁面:

# 點擊按鈕
driver.find_element(By.CSS_SELECTOR, ".load-more").click()

# 清除輸入框並輸入文字
search_box = driver.find_element(By.ID, "search")
search_box.clear()
search_box.send_keys("Python 教學")

# 按下 Enter 鍵
from selenium.webdriver.common.keys import Keys
search_box.send_keys(Keys.ENTER)

# 捲動到頁面底部
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

# 捲動到特定元素
element = driver.find_element(By.ID, "footer")
driver.execute_script("arguments[0].scrollIntoView();", element)

捲動操作特別重要,因為很多網站使用「無限捲動」或「懶加載」的方式載入內容。你必須先捲動到對應的位置,內容才會被載入到 DOM 中。

Headless 模式:無頭瀏覽器

在開發階段,看到瀏覽器畫面有助於除錯。但在正式執行或部署到伺服器時,你通常不需要(也無法)顯示瀏覽器視窗。這時候就要用 Headless 模式:

options = Options()
options.add_argument("--headless=new")
options.add_argument("--window-size=1920,1080")
options.add_argument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")

driver = webdriver.Chrome(options=options)

注意幾個細節:用 --headless=new 而不是舊的 --headless,新版的 headless 模式行為更接近正常瀏覽器。即使是 headless 模式也要設定 window-size,否則預設的視窗大小可能會影響頁面的渲染結果。設定 user-agent 可以讓你的請求看起來更像正常的瀏覽器訪問。

實戰範例:爬取動態載入的電商價格

來看一個完整的實戰範例。假設我們要爬取一個電商網站的商品價格,這些商品資訊是透過 AJAX 動態載入的:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
import time
import csv

def scrape_products(url, max_pages=5):
    options = Options()
    options.add_argument("--headless=new")
    options.add_argument("--window-size=1920,1080")
    
    driver = webdriver.Chrome(options=options)
    products = []
    
    try:
        driver.get(url)
        
        for page in range(max_pages):
            # 等待商品卡片載入
            WebDriverWait(driver, 10).until(
                EC.presence_of_all_elements_located(
                    (By.CSS_SELECTOR, ".product-card")
                )
            )
            
            # 抓取所有商品資訊
            cards = driver.find_elements(By.CSS_SELECTOR, ".product-card")
            for card in cards:
                name = card.find_element(By.CSS_SELECTOR, ".product-name").text
                price = card.find_element(By.CSS_SELECTOR, ".product-price").text
                products.append({"name": name, "price": price})
            
            # 嘗試點擊下一頁
            try:
                next_btn = driver.find_element(By.CSS_SELECTOR, ".next-page")
                if "disabled" in next_btn.get_attribute("class"):
                    break
                next_btn.click()
                time.sleep(2)
            except:
                break
    finally:
        driver.quit()
    
    return products

這個範例展示了幾個重要的概念:使用 try/finally 確保瀏覽器一定會被關閉、用 WebDriverWait 等待動態內容載入、翻頁邏輯的處理、以及錯誤處理。抓到的資料之後可以用Python Matplotlib 視覺化教學中的方法來做圖表分析。

搭配 BeautifulSoup 分析 HTML

Selenium 的元素定位功能雖然強大,但在處理大量 HTML 解析時,BeautifulSoup 的速度更快、語法更簡潔。最佳做法是用 Selenium 負責載入頁面和執行動態操作,然後將頁面原始碼交給 BeautifulSoup 解析:

from bs4 import BeautifulSoup

# Selenium 載入頁面後
page_source = driver.page_source
soup = BeautifulSoup(page_source, "html.parser")

# 用 BeautifulSoup 解析
titles = soup.select(".product-title")
for title in titles:
    print(title.get_text(strip=True))

這種組合的好處是:Selenium 處理動態載入和互動,BeautifulSoup 處理 HTML 解析。兩者各司其職,效率最高。

排程自動化執行

爬蟲寫好了,接下來就是讓它定期自動執行。在 macOS 或 Linux 上,最簡單的方式是用 crontab:

# 每天早上 9 點執行
0 9 * * * /path/to/venv/bin/python /path/to/scraper.py

在 Windows 上則可以用工作排程器(Task Scheduler)。如果你想要更進階的排程管理,可以考慮用 Python 的 APScheduler 套件或 Celery。

另外,如果你的爬蟲需要跟其他自動化流程串接,例如爬完數據後自動寄送報告,可以參考Python 自動化寄信教學來實現完整的自動化工作流程。進階的自動化場景甚至可以結合LangChain Agent 開發入門中的技術,讓 AI 來判斷爬取的數據並做出決策。

常見問題與除錯技巧

在實際開發中,你一定會遇到各種問題。以下是我整理的常見問題和解決方式:

元素找不到(NoSuchElementException):最常見的原因是元素還沒載入。加上適當的等待機制通常能解決。也可能是元素在 iframe 中,需要先用 driver.switch_to.frame() 切換到對應的 iframe。

元素無法點擊(ElementClickInterceptedException):通常是因為有其他元素(例如彈窗、cookie 同意條)擋在前面。用 JavaScript 直接點擊可以繞過這個問題:driver.execute_script("arguments[0].click();", element)

被網站偵測到是機器人:可以嘗試設定 user-agent、加入隨機的等待時間、模擬人類的滑鼠移動和捲動行為。但要注意,爬蟲行為應該遵守網站的 robots.txt 規範和使用條款。

記憶體不斷增長:長時間運行的爬蟲可能會因為瀏覽器的記憶體洩漏而越來越慢。定期重啟 driver 是一個有效的解決方式——每爬完一定數量的頁面就 quit() 再重新建立。

結語

Selenium 是處理動態網頁爬蟲的強大工具,但它不是萬能的。對於簡單的靜態頁面,用 requests 更輕量高效。對於需要高效能的大規模爬取,可能需要考慮 Playwright 或 Scrapy。選擇工具的關鍵是根據實際需求,而不是一味追求功能最強大的方案。

不管你是用 Selenium 來爬資料、做自動化測試、還是建立 RPA 流程,掌握元素定位、等待機制和錯誤處理這三個核心概念,就足以應對大部分的使用場景了。動手做幾個專案,你很快就能上手。

趙柏翰

全端工程師,八年開發經驗。熱愛 TypeScript 生態系。

ReactTypeScriptPythonNode.js

繼續閱讀

Python 排程自動化教學:用 APScheduler 打造定時任務系統

Python 排程自動化教學:用 APScheduler 打造定時任務系統

相關文章

你可能也喜歡

探索其他領域的精選好文