水庫資料網頁爬蟲
===
###### tags: 計畫 網頁爬蟲
**2019年03月**計畫內容:用網頁爬蟲技術將水庫資料從網頁中調出,並整理成圖表顯示過去幾年來水庫水位變化,更甚者可以搭配天氣資料,做成水庫水位的關聯,以及其它相關資訊的總整理。
環境:jupyter notebook
語言:python3.7
套件:**requests**、**BeautifulSoup4** (CMD 用 pipinstall 方法安裝)
其它軟體搭配:[Postman](https://www.getpostman.com/downloads/)
資料網站:[水庫統計表] http://fhy.wra.gov.tw/ReservoirPage_2011/StorageCapacity.aspx
:::info
這個是我沒辦法爬出資料的相似網站:
**http://fhy.wra.gov.tw/ReservoirPage_2011/Statistics.aspx**
(雖然我也不知道 Statistics 和 StorageCapacity 到底有什麼差...)
:::
## 爬蟲筆記 Crawling Note
參考網址:[大數學堂](https://www.largitdata.com/course_list/1)
- 檢視網頁原始碼:
```
- Chrome 更多工具 → 開發人員工具
- 右鍵 → 檢視網頁原始碼
```
- jupyter notebook 其它用法:
```python=3.7
%pylab inline #在jupyternotebook繪圖
%ls | grep test #(linux 指令)當下檔案中尋找有test的檔案名稱
%run test.py #執行test.py檔
from IPython.display import display, Math, Latex
display(Math(r'c = \sqrt{a^2+b^2}')) #出現有c=a^2+b^2的數學效果
```
<hr/>
### 利用 GET 取得網頁內容
網頁右鍵 → 檢查 → Network → 重新載入 → 最上面檔案(Header)找url
```python=
import requests
res = requests.get("url") #利用requests得到網頁內容
print(res.text) #輸出內容
```
### 用 POST 取得網頁內容
有的網頁需要一些輸入才能得到查詢頁的結果(比如水庫資料要輸入日期時間),這時候就需要用post來進行網頁爬蟲。
```python=
import requests
#在search result當中的值定義
pyload = {
'title_1' : "value_1",
'title_2' : "value_2",
'title_3' : "value_3",
}
#利用requests.post把想查詢的條件資料送出給網頁
res = requests.post("url", data = pyload)
print(res.text) #輸出內容
```
但這個方法我目前發現不可行,因為政府的水庫網站在給設定參數(圖中黑粗體字`__VIEWSTATE`)時,會遇到一個很棘手的亂數問題,但這個亂數似乎又有關於驗證碼的問題,試了很多方法都沒有用...所以我就找另外一種方法試試了QAQ

==2019/04/05 更新:我發現應該不是 POST 方法的問題,應該是網站的問題==
<hr/>
### 透過 __VIEWSTATE 驗證爬取資料
感謝 [這篇文章](https://www.ptt.cc/bbs/R_Language/M.1496796579.A.2FB.html) 的指點以及終於換個網址 parse 資料,才能有今天成功的日子啊~~
- __VIEWSTATE 是什麼?
`__VIEWSTATE` 大概算是一種網頁的驗證碼,在送出 POST 資料時需要這項參數,如果錯誤的話,將會無法得到網頁正確的資料,而跑出 `0|500|Error` 的訊息。[[ 更多說明 ]](https://blog.51cto.com/slliang/1783837)
- 怎麼找到 __VIEWSTATE ?
每次載入網頁的時候,網頁都會生成一組 `__VIEWSTATE` 值,於是我們只要先用 GET 方式把當中的 `__VIEWSTATE` 值找出來並當作 POST 的參數即可。
```python=
# 此函式會找特定的value,如「__VIEWSTATE」等
def find_value(name, web):
reg = 'name="' + name + '".+value="(.*)" />'
pattern = re.compile(reg)
result = pattern.findall(web.text)
try:
return result[0]
except:
return ""
```
[[ 程式碼參考來源 ]](https://www.finlab.tw/%E7%94%A8python%E7%8D%B2%E5%8F%96%E6%8C%81%E8%82%A1%E6%90%8D%E7%9B%8A%E8%A1%A8/)
- 除了 __VIEWSTATE 以外,還需要什麼?
在查詢方式上,可以看到有四個能夠選擇的值(水庫性質、年、月、日),而使用者可以透過在 HTML 原始碼輸入元素的 id 去賦值,比如年份的 id 是 `ctl00$cphMain$ucDate$cboYear` ,則可以指定要哪一年的資料。
送進去的參數如下:
```python=
date_list = [year, month, date]
load_list = [find_value("__EVENTTARGET", content), find_value("__VIEWSTATE")]
payload = {
#"ctl00$ctl02": find_value("ctl00$ctl02", d),
#"ctl00_ctl02_HiddenField": find_value("ctl00_ctl02_HiddenFiel", d),
"__EVENTTARGET": load_list[0],
#"__EVENTARGUMENT": "",
"__VIEWSTATE": load_list[1],
#"__VIEWSTATEGENERATOR": find_value('__VIEWSTATEGENERATOR', d),
#"__EVENTVALIDATION": find_value('__EVENTVALIDATION', d),
'ctl00$cphMain$cboSearch': "所有水庫",
'ctl00$cphMain$ucDate$cboYear': date_list[0],
'ctl00$cphMain$ucDate$cboMonth': date_list[1],
'ctl00$cphMain$ucDate$cboDay': date_list[2],
#'ctl00$cphMain$ucDate$cboHour': para[3],
#'ctl00$cphMain$ucDate$cboMinute': para[4],
#'__ASYNCPOST': 'true'
}
requests.post("url", data = payload) #請求資料
soup = BeautifulSoup(res.text, "lxml").find_all("tr")[2:]
```
透過 BeautifulSoup 解析網頁以後,加上資料的提取(字串處理,取**蓄水量的百分比**)就能蒐集到各水庫的資料了。
每次讀取資料都要 parse 網站資料太費時,我把讀出來的資料寫進 csv 檔,等之後的資料視覺化時再取 csv 檔讀資料即可。[[ 資料集下載處 ]](https://github.com/hsiaoping-zhang/Reservoir_DataVisiualization/tree/master/data)
<hr>
### (番外) Selenium 介紹
Selenium 是個能提供 python 進行自動化測試的套件,使用者可以利用程式碼進行頁面上的操作,進行大量資料搜索的時候就不用手動操作。
但為了因應網頁瀏覽器幫你做事情,還需要一個 web driver 下載。[[ chrome driver 載點 ]](https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-win64.zip)
(記得要把 exe 檔放在和 python 同個資料夾底下才能正常運行)
- 首先要去 command line 把套件下載
```
pip install Selenium
```