# 動態網頁爬蟲-使用Selenium
###### tags: `python` `selenium` `crawler`
## 介紹
#### 動態網頁
動態網頁顧名思義,指的就是網頁內容是動態的,每次瀏覽同一個網頁的內容可能都不同,例如: 書本的搜尋,匯率,新聞等等,根據使用者的選擇或輸入不同的互動,動態的產生網頁內容。
#### 客戶端動態網頁
產生有兩種方式:
- 從網頁伺服器下載JavaScript程式碼,在客戶端執行後產生內容。
- 透過客戶端的需求,在伺服器端產生內容後,以AJAX(Asynchronous JavaScript And XML)技術下載到客戶端。
#### 伺服器端動態網頁
在網頁伺服器內以伺服器程式語言(如: PHP,ASP.NET或JSP等等)執行後來動態產生網頁後再回應給客戶端。
#### Selenium
對於爬蟲來說,如果爬取動態網頁內容,只會得到一堆JavaScript程式碼,因為網頁內容需要透過執行JavaScript程式碼後產生,所以對於動態網頁,爬蟲會看不到執行後的結果,所以無法使用Beautiful Soup或lxml來爬取資料。
Selenim為一個跨平台的自動瀏覽器(Automates Browsers),其原本是用來做為Web應用程式的軟體測試框架,本身為開放原始碼,用在爬蟲上,可以做為模擬使用者輸入內容,點擊連結來與網頁作互動。
#### Selenium套件
Selenium為數個元件組合而成的一個套件,其元件有:
- Selenium IDE: 一套建立Selenium測試的整合開發環境,附屬在Chrome或Firefox瀏覽器內,可以錄製,編輯和除錯建立的Selenium測試。
- Selenium Client API: 可以直接使用程式語言來建立Selenium測試,支援: Java,Ruby,JavaScript,C#和Pythonx來跟Selenium WebDriver通訊。
- Selenium WebDriver: 接收來自Selenium API的命令來控制瀏覽器,支援Chrome,Firefox,Safari,Microsoft Edge ,IE等瀏覽器。
## 安裝Selenium
對Python來說,需要安裝有個,一個是Selenium Client API(也就是Python模組),另一個就是WebDriver,。
#### 安裝Selenium Client API
```
pip3 install selenium
```
#### ~~安裝WebDriver~~
##### 步驟一: 下載
https://www.selenium.dev/documentation/webdriver/getting_started/install_drivers/
> **備註:**
>
> 1. Selenium 4.6版(包含)以後不需要再下載driver了。
> 2. Selenium 4.5版以前請下載對應瀏覽器的WebDriver
> **注意:**
> - WebDriver並非下載最新版本就好,而是下載的版本必須和你的瀏覽器版本一樣,例如你的Chrome瀏覽器是106版,那WebDriver就要下載106版的。
> Windows作業系統請下載chromedriver_win32.zip即可。
##### 解壓縮
將下載的zip檔案解壓縮到你的Python程式目錄下。
## Selenium基本操作
引入selenium的webdriver物件
```
from selenium import webdriver
```
初始化WebDriver
```
driver = webdriver.Chrome('chromedriver')
```
> **注意:**
>
> 如果是在MacOS下,如果和Python放在同一層目錄,需要額外加上代表目前路徑的`./`符號,如:
>
> ```
> driver = webdriver.Chrome('./chromedriver')
> ```
設定等待網頁載入的時間,如果提早載入完成,會提早結束等待,如果時間不夠,讀到的網頁內容會有殘缺:
```
driver.implicitly_wait(10)
```
連線到網站:
```
driver.get('https://rate.bot.com.tw/xrt?Lang=zh-TW')
```
印出網頁標題:
```
print(driver.title)
```
取得網頁內容並印出:
```
html = driver.page_source
print(html)
```
也可以利用BeautifulSoup來分析網頁內容:
```
soup = BeautifulSoup(driver.page_source, 'lxml')
print(soup.prettify())
```
將取得的網頁內容存到本地端:
```
with open('index.html', 'w', encoding='utf-8',) as file:
file.write(soup.prettify())
```
關閉WebDriver, 瀏覽器也會跟著關閉:
```
driver.quit()
```
#### 完整程式碼
```python=
from selenium import webdriver
from bs4 import BeautifulSoup
import requests
driver = webdriver.Chrome('chromedriver')
driver.implicitly_wait(10)
driver.get('https://rate.bot.com.tw/xrt?Lang=zh-TW')
print(driver.title)
html = driver.page_source
print(html)
soup = BeautifulSoup(driver.page_source, 'lxml')
print(soup.prettify())
with open('index.html', 'w', encoding='utf-8',) as file:
file.write(soup.prettify())
driver.quit()
```
## Selenium資料定位
Selenium有兩個系列的資料定位函式:
- `find_element()`: 用來取得網頁中的第一個定位到的HTML元素。
- `find_elements()`: (名稱中多了一個s),用來取得所有網頁中定位到的元素。
這兩個函式都可以使用下面方式來定位元素:
| 名稱 | 說明 |
| --------------------- | ---------------------------- |
| `"id"` | 使用id屬性值來定位元素 |
| `"name"` | 使用name屬性值來定位元素 |
| `"xpath"` | 使用XPath表達式來定位元素 |
| `"link text"` | 使用超連結文字來定位元素 |
| `"partial link text"` | 使用部分超連結文字來定位元素 |
| `"tag name"` | 使用標籤名稱來定位元素 |
| `"class name"` | 使用class屬性值來定位元素 |
| `"css selector"` | 使用CSS選擇器來定位元素 |
```
ID = "id"
XPATH = "xpath"
LINK_TEXT = "link text"
PARTIAL_LINK_TEXT = "partial link text"
NAME = "name"
TAG_NAME = "tag name"
CLASS_NAME = "class name"
CSS_SELECTOR = "css selector"
```
#### 定位台灣銀行匯率下載CSV按鈕,並模擬點擊來下載檔案
```
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException
from bs4 import BeautifulSoup
import requests
driver = webdriver.Chrome('chromedriver')
driver.implicitly_wait(10)
driver.get('https://rate.bot.com.tw/xrt?Lang=zh-TW')
print(driver.title)
html = driver.page_source
try:
download_csv = driver.find_element(By.LINK_TEXT, '下載 Excel (CSV) 檔')
print(download_csv.tag_name)
print(download_csv.get_attribute('href'))
download_csv.click()
except NoSuchElementException:
print('無法定位')
driver.quit()
```
> **備註:**
> 下載的檔案名稱會跟直接從網站下載的檔名不同
#### Selenium動作函式:
| 名稱 | 說明 |
| ------------------- | ---------------------- |
| `click()` | 點擊元素 |
| `click_and_hold()` | 在元素上按住滑鼠左鍵 |
| `context_click()` | 在元素上按住滑鼠右鍵 |
| `double_click()` | 點擊兩次元素 |
| `move_to_element()` | 移動滑鼠游標到元素中間 |
| `key_up()` | 放開鍵盤的按鍵 |
| `key_down()` | 按下鍵盤的按鍵 |
| `perform()` | 執行所有儲存的動作 |
| `send_keys()` | 送出按鍵到元素 |
| `release()` | 在元素上鬆開滑鼠按鍵 |
#### 執行JavaScript程式碼
##### 使用 Selenium 滾動網頁捲軸(scrollBar)
```
driver.execute_script('window.scrollBy(0,1000)')
```
**說明:**
scrollBy(x,y)中,x為必須參數,表示向右滾動的像素值;y也為必須參數,表示向下滾動的像素值
```
driver.execute_script('window.scrollTo(0,1000)')
```
**說明:**
scrollTo(x,y) 中,x為必要參數,表示要在視窗顯示區左上角顯示的x坐標;y也為必要參數,表示要在視窗顯示區左上角顯示的y坐標
##### 捲軸拉到底
```
driver.execute_script('window.scrollTo(0,document.body.scrollHeight)')
```
或
```
driver.execute_script('window.scrollTo(0,document.documentElement.scrollHeight)')
```
#### send_key()常用的按鍵代碼
`ENTER` `SHIFT` `LEFT_SHIFT` `CONTROL` `LEFT_CONTROL` `ALT` `LEFT_ALT` `SPACE`
#### Selenium例外物件
| 名稱 | 說明 |
| ----------------------------- | ------------------------ |
| ElementNotSelectableException | 選取的該元素不允許被選取 |
| ElementNotVisibleException | 元素存在,但是不可見 |
| ErrorInResponseException | 伺服器端回應錯誤 |
| NoSuchElementException | 選取的元素不存在 |
| TimeoutException | 超過時間限制 |