---
# System prepended metadata

title: 【Python 網路爬蟲筆記】Selenium Library、爬取 Hackmd 文章專題 - part 4
tags: [python, 程式設計, 網頁程式設計, Python, 爬蟲, web crawler, 程式語言, 網頁]

---

【Python 網路爬蟲筆記】Selenium Library、爬取 Hackmd 文章專題 - part 4
===

目錄（Table of Contents）：

[TOC]

---

感謝你點進本篇文章！！我是 LukeTseng，一個熱愛資訊的無名創作者，由於近期大學開設大數據分析程式設計這門課程，裡面談到了爬蟲概念，讓我激起一些興趣，因而製作本系列筆記。

聲明：本篇筆記僅供個人學習用途，斟酌參考。

本篇筆記使用 Jupyter Notebook，搭載 Anaconda 虛擬環境，如需下載者可至該網址：https://www.anaconda.com/download

## 安裝 Selenium 模組

透過以下指令：

```
pip install selenium
```

在 Jupyter Notebook（安裝完後記得 Restart Kernel 才會啟用）：

```
!pip install selenium
```

## 什麼是 Selenium？

Selenium 是一種開源的網頁瀏覽器自動化工具，可以透過程式碼來模擬 user 在瀏覽器上的各種操作（像人一樣），從而完成自動化測試或網頁爬蟲任務。

Selenium 就是動態爬蟲中應用到最重要的技術。

Selenium 用 WebDriver 來驅動與控制瀏覽器，每種瀏覽器都有專屬的 WebDriver，Chrome 就是用 ChromeDriver、Firefox 用 GeckoDriver、Safari 用 SafariDriver 等等。

總之 WebDriver 是在 Selenium 裡面中最常使用到的技術，因為它可模擬很多 user 的操作像點擊元素、填寫表單、捲動頁面等等。

本篇筆記使用 Chrome 瀏覽器進行操作與編寫程式，要下載 ChromeDriver 可至該網站：https://sites.google.com/chromium.org/driver/downloads

記得要下載目前正使用對應的 Chrome 版本，具體如何看自己的 Chrome 版本？如下圖所示。

![image](https://hackmd.io/_uploads/Byqc2ZRTlg.png)

![image](https://hackmd.io/_uploads/H1t-TZCplx.png)

不過也可以不用下載，因為有另一個模組就的指令是可以在程式執行時自動下載，會在下一節介紹。

## 自動安裝 WebDriver 的方法（懶人法）

首先先安裝 webdriver-manager：

```
pip install webdriver-manager
```

之後在程式中即可使用：

```python=
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

# 自動下載並安裝對應版本的 ChromeDriver
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
```

## 第一支 selenium 程式

以下是一個小範例，會透過 selenium 自動化程式開啟瀏覽器，並前往指定網站。

```python=
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

# 建立 WebDriver 物件（自動下載對應版本的 ChromeDriver）
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

# 開啟 Google 首頁
driver.get('https://www.google.com')

# 暫停 5 秒
import time
time.sleep(5)

# 關閉瀏覽器
driver.quit()
```

在當建立 WebDriver 物件的時候，瀏覽器就會開起來了。

當程式執行後，會出現 Chrome 目前受到自動測試軟體控制的字樣，這是正常的，表示程式執行成功。

由於有設定暫停 5 秒的緣故，所以 5 秒後瀏覽器就會關閉了。

## 元素定位操作

可以使用 `.find_element()` 方法定位網頁元素，內部的第一個參數主要語法是 `By.XXX`，而 XXX 則是要定位元素的方法，具體如下範例所示：

```python=
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get('https://www.google.com')

# 1. 透過 ID 定位（最快最準）
search_box = driver.find_element(By.ID, "APjFqb")

# 2. 透過 NAME 屬性定位
search_box = driver.find_element(By.NAME, "q")

# 3. 透過 CLASS_NAME 定位
element = driver.find_element(By.CLASS_NAME, "gLFyf")

# 4. 透過 CSS Selector 定位（靈活度高）
search_box = driver.find_element(By.CSS_SELECTOR, "textarea[name='q']")

# 5. 透過 XPath 定位（功能最強）
search_box = driver.find_element(By.XPATH, "//textarea[@name='q']")
```

XPATH 取得的方式很簡單，只要對某個元素按下右鍵 -> Copy -> Copy XPATH 即可取得。

![image](https://hackmd.io/_uploads/BJuMZf0Tgx.png)

日後要定位元素也建議使用 XPATH 的方式。

## 常用操作方法

在定位網頁元素後，接下來要做的就是類似像點擊、捲動畫面等操作了，如：

- `.send_keys()` 輸入文字
- `.submit()` 提交表單（模擬按下 Enter）
- `.click()` 按下滑鼠左鍵

詳細方法可至 Selenium 官方網站：https://www.selenium.dev/documentation/webdriver/elements/interactions/

然後可到該網站進行一些 Selenium 的測試與學習：https://example.oxxostudio.tw/python/selenium/demo.html

以下是一個小範例：

```python=
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get('https://example.oxxostudio.tw/python/selenium/demo.html')

time.sleep(5) # 等待 javascript 跑完

text_box = driver.find_element(By.XPATH, '//*[@id="show"]')
text_box.send_keys("Hello World!")

B = driver.find_element(By.XPATH, '/html/body/button[2]')
B.click()

add_number = driver.find_element(By.XPATH, '//*[@id="add"]')
add_number.click()

time.sleep(10)

driver.quit()
```

## 取得網頁元素內容

| 方法 | 說明 | 範例 |
| :-- | :-- | :-- |
| `.text` | 取得元素的純文字內容 | `element.text` |
| `.get_attribute('屬性名')` | 取得元素的指定 HTML 屬性值 | `element.get_attribute('href')` |
| `.id` | 取得元素的 id | `element.id` |
| `.tag_name` | 取得元素的標籤名稱 | `element.tag_name` |
| `.size` | 取得元素的尺寸（長寬） | `element.size` |
| `.is_displayed()` | 判斷元素是否顯示在頁面上 | `element.is_displayed()` |
| `.is_enabled()` | 判斷元素是否可用 | `element.is_enabled()` |
| `.is_selected()` | 判斷元素是否被選取（如 checkbox） | `element.is_selected()` |
| `.parent` | 取得元素的父元素 | `element.parent` |
| `.screenshot('檔名.png')` | 將元素截圖並儲存為圖片 | `element.screenshot('test.png')` |

From [Selenium 函式庫 - Python 網路爬蟲教學 | STEAM 教育學習網](https://steam.oxxostudio.tw/category/python/spider/selenium.html)

以下是對於測試網站所做的範例（改自 [Selenium 函式庫 - Python 網路爬蟲教學 | STEAM 教育學習網](https://steam.oxxostudio.tw/category/python/spider/selenium.html)）：

```python=
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

# 啟動瀏覽器
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get('https://example.oxxostudio.tw/python/selenium/demo.html')

# 取得 id 為 a 的元素
a = driver.find_element(By.ID, 'a')

# 取得 class 為 btn 的元素
b = driver.find_element(By.CLASS_NAME, 'btn')

# 取得 class 為 test 的元素
c = driver.find_element(By.CSS_SELECTOR, '.test')

# 取得 name 為 dog 的元素
d = driver.find_element(By.NAME, 'dog')

# 取得 tag 為 h1 的元素
h1 = driver.find_element(By.TAG_NAME, 'h1')

# 取得指定超連結文字的元素
link1 = driver.find_element(By.LINK_TEXT, '我是超連結，點擊會開啟 Google 網站')

# 取得超連結文字包含 Google 的元素
link2 = driver.find_element(By.PARTIAL_LINK_TEXT, 'Google')

# 取得元素內容
print("a.id:", a.id)  # 元素 id
print("b.text:", b.text)  # 元素文字
print("c.tag_name:", c.tag_name)  # 元素標籤名稱
print("d.size:", d.size)  # 元素尺寸
print("link1.href:", link1.get_attribute('href'))  # 元素屬性值
print("link2.target:", link2.get_attribute('target'))  # 元素屬性值
print("h1.is_displayed:", h1.is_displayed())  # 是否顯示
print("d.is_enabled:", d.is_enabled())  # 是否可用
print("a.is_selected:", a.is_selected())  # 是否被選取

# 將 body 元素截圖
body = driver.find_element(By.TAG_NAME, 'body')
body.screenshot('./test.png')

driver.quit()
```

輸出結果如下：

![image](https://hackmd.io/_uploads/rkseDfCalx.png)

在外面額外產生了 test.png 的截圖。

![image](https://hackmd.io/_uploads/ryJGDzCalx.png)

## 專題：爬取 Hackmd 作者頁面的所有文章資訊

以下透過我個人 Hackmd 頁面進行爬取，首先爬取第一頁的每篇文章標題。

```python=
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import time

# 開啟瀏覽器
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get('https://hackmd.io/@LukeTseng')

# 等待 javascript 跑完
time.sleep(3)

# find_elements() 加上 s 可回傳一個列表，一次將所有相同的元素傳入列表中
titles = driver.find_elements(By.CSS_SELECTOR, 'span.line-clamp-1.flex-1.text-lg.font-semibold')

for title in titles:
    print(title.text)

driver.quit()
```

輸出結果：

![image](https://hackmd.io/_uploads/SJX-IRJRgx.png)

如果想要爬取每一頁的內容資訊怎麼辦？可以觀察到 HackMD 沒有下一頁的功能，因此需要自己實作。

![image](https://hackmd.io/_uploads/rklUwAy0lg.png)

從下圖中打開【檢查】，可以分析一下每個頁面的結構長怎樣，這邊實測發現每個頁面按鈕都是一樣的元素所組成，另外我也特別觀察了一下每個元素的 XPATH，他最後面都有特定的規律，如：

- `//*[@id="hackmd-app"]/section/main/div/div[2]/div/div/div[2]/div/ul/li[1]/a`
- `//*[@id="hackmd-app"]/section/main/div/div[2]/div/div/div[2]/div/ul/li[2]/a`
- `//*[@id="hackmd-app"]/section/main/div/div[2]/div/div/div[2]/div/ul/li[3]/a`

上面這些都是頁面 1、2、3 的元素，其他部分都沒有更動，唯一更動的地方只有最後面的 `/li[1]`，他裡面的中括號的數字會動，所以可以考慮使用 string format 去變動。

![image](https://hackmd.io/_uploads/rJMPD0k0lx.png)

以下是程式碼：

```python=
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.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import time

# 開啟瀏覽器
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get('https://hackmd.io/@LukeTseng')

page_num = 1

# 爬取第 1 頁到第 14 頁
for page_num in range(1, 15):
    print(f"正在爬取第 {page_num} 頁...")
    
    # 等待標題元素載入
    WebDriverWait(driver, 10).until(
        EC.presence_of_all_elements_located((By.CSS_SELECTOR, 'span.line-clamp-1.flex-1.text-lg.font-semibold'))
    )
    
    # 爬取當前頁面的所有標題
    titles = driver.find_elements(By.CSS_SELECTOR, 'span.line-clamp-1.flex-1.text-lg.font-semibold')
    
    for title in titles:
        print(f"  - {title.text}")
    
    # 如果不是最後一頁，點擊下一頁
    if page_num < 14:
        try:
            # 找到下一頁的按鈕（頁碼 = 當前頁+1）
            next_page_button = WebDriverWait(driver, 10).until(
                EC.element_to_be_clickable((By.XPATH, f'//*[@id="hackmd-app"]/section/main/div/div[2]/div/div/div[2]/div/ul/li[{page_num + 1}]/a'))
            )
            next_page_button.click()
            time.sleep(2)  # 等待頁面載入
        except Exception as e:
            print(f"無法點擊第 {page_num + 1} 頁: {e}")
            break

print("所有頁面都爬完了！")
driver.quit()
```

輸出結果：

![image](https://hackmd.io/_uploads/Bkqo_AJRgl.png)

其中以下程式碼是 Selenium 的顯式等待（Explicit Wait）機制，能自動等待頁面 loading 完成，像是以下就是等待每個標題元素加載完成才繼續執行程式碼。

而其中 `WebDriverWait` 的第二個參數 10 就是最多等待 10 秒的意思。

`until` 為持續檢查某個條件，直到條件成立或超時。

`EC.presence_of_all_elements_located(...)` 是等待的條件，意思是等待「所有符合條件的元素都出現在網頁的 DOM 結構中」。

```python=
WebDriverWait(driver, 10).until(
        EC.presence_of_all_elements_located((By.CSS_SELECTOR, 'span.line-clamp-1.flex-1.text-lg.font-semibold'))
    )
```

爬取完標題後，可以來爬取每篇文章的日期、瀏覽量，甚至可以算出所有文章的總瀏覽量：

```python=
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.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import time

# 開啟瀏覽器
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get('https://hackmd.io/@LukeTseng')

page_num = 1

all_views = 0

# 爬取第 1 頁到第 14 頁
for page_num in range(1, 15):
    print(f"正在爬取第 {page_num} 頁...")
    
    # 等待標題元素載入
    WebDriverWait(driver, 10).until(
        EC.presence_of_all_elements_located((By.CSS_SELECTOR, 'span.line-clamp-1.flex-1.text-lg.font-semibold'))
    )

    # 等待日期元素載入
    WebDriverWait(driver, 10).until(
        EC.presence_of_all_elements_located(
            (By.CSS_SELECTOR, 'div.text-text-subtle > span:first-child')
        )
    )

    # 等待瀏覽量元素載入
    WebDriverWait(driver, 10).until(
        EC.presence_of_all_elements_located(
            (By.XPATH, "//a[i[contains(@class, 'ph-eye')]]")
            # //a 尋找所有 <a>
            # contains() 函數檢查 class 屬性是否包含指定的文字
            # @class 取得 class 屬性值
            # 'ph-eye' 要搜尋的子字串
            
            # i[contains(@class, 'ph-eye')]
            # 當中的 i 表示 <a> 標籤必須包含一個 <i> 子元素，且 <i> 的 class 屬性要有 'ph-eye' 這個字串
        )
    )
    
    # 爬取當前頁面的所有標題
    titles = driver.find_elements(By.CSS_SELECTOR, 'span.line-clamp-1.flex-1.text-lg.font-semibold')

    # 爬取當前頁面的所有日期
    dates = driver.find_elements(By.CSS_SELECTOR, 'div.text-text-subtle > span:first-child')

    # 爬取當前頁面的所有瀏覽量
    views = driver.find_elements(By.XPATH, "//a[i[contains(@class, 'ph-eye')]]")
    
    for title, date, view in zip(titles, dates, views):
        print(f"  - {title.text}\n    日期：{date.text}\n    瀏覽量：{view.text}\n")
        all_views += int(view.text.replace(',', '')) # 將 , 替換成空字串以來轉成整數
    
    # 如果不是最後一頁，點擊下一頁
    if page_num < 14:
        try:
            # 找到下一頁的按鈕（頁碼 = 當前頁+1）
            next_page_button = WebDriverWait(driver, 10).until(
                EC.element_to_be_clickable((By.XPATH, f'//*[@id="hackmd-app"]/section/main/div/div[2]/div/div/div[2]/div/ul/li[{page_num + 1}]/a'))
            )
            next_page_button.click()
            time.sleep(2)  # 等待頁面載入
        except Exception as e:
            print(f"無法點擊第 {page_num + 1} 頁: {e}")
            break
            
print(f"所有文章總瀏覽量：{all_views}")
print("所有頁面都爬完了！")
driver.quit()
```

輸出結果：

![image](https://hackmd.io/_uploads/Hk_kkJl0lx.png)

## 參考資料

[Selenium 函式庫 - Python 網路爬蟲教學 | STEAM 教育學習網](https://steam.oxxostudio.tw/category/python/spider/selenium.html)

[瀏覽器自動化工具Selenium介紹 – CH.Tseng](https://chtseng.wordpress.com/2023/07/14/%E7%80%8F%E8%A6%BD%E5%99%A8%E8%87%AA%E5%8B%95%E5%8C%96%E5%B7%A5%E5%85%B7selenium%E4%BB%8B%E7%B4%B9/)

[[DAY7]Selenium簡介 | iT 邦幫忙::一起幫忙解決難題，拯救 IT 人的一天](https://ithelp.ithome.com.tw/m/articles/10319363)

[認識 Selenium · GitBook](https://alincode.github.io/learngeb/intro/selenium.html)

[Day 15: Selenium － 基本概念和操作 - iT 邦幫忙::一起幫忙解決難題，拯救 IT 人的一天](https://ithelp.ithome.com.tw/articles/10320979)

[python一招完美搞定Chromedriver的自动更新 - NewJune - 博客园](https://www.cnblogs.com/new-june/p/16698204.html)

[Python Selenium 教學筆記 - HackMD](https://hackmd.io/@FortesHuang/S1V6jrvet)

[動態網頁爬蟲第一道鎖 - Selenium教學：如何使用Webdriver、send_keys(附Python 程式碼) - 臺灣行銷研究](https://tmrmds.co/uncategorized/16513/)