爬蟲概論
===
###### tags: `爬蟲` `python`
Tips for crawlers:
1. 有沒有現成的 code
2. 該網站是否已經提供 API 接口
* 不要造成別人伺服器的負擔!
News APIs: https://newsapi.org/s/bbc-news-api
## 基本分類
* 靜態爬蟲: 透過 get, post 獲取頁面資訊,依照不同網站架構,可能需要多次的 request
* Text
* Image
* file
* 網站爬蟲: 將整個網站架構下的所有頁面進行巢狀爬取 (可以多利用框架如 Scrapy 來執行) [blog](http://woodenrobot.me/2017/04/09/Scrapy%E7%88%AC%E8%99%AB%E6%A1%86%E6%9E%B6%E6%95%99%E7%A8%8B%EF%BC%88%E5%9B%9B%EF%BC%89-%E6%8A%93%E5%8F%96AJAX%E5%BC%82%E6%AD%A5%E5%8A%A0%E8%BD%BD%E7%BD%91%E9%A1%B5/)
* 動態爬蟲: 是透過 javascript 即時生成資料,無法直接從網頁原始碼中獲得,需要透過模擬瀏覽器或自己找出網址、程式碼生成邏輯來處理。[Quick Javascript switcher](https://chrome.google.com/webstore/detail/quick-javascript-switcher/geddoclleiomckbhadiaipdggiiccfje)
* 可解析 => 獲取資訊重複爬取
* 不可解析 => 模擬瀏覽器 (但很多視覺化呈現之動態圖表,其資料也多由API傳輸,所以可以直接尋找該API)
## 了解網頁架構

* HTML (HyperText Markup Language)

還是沒什麼感覺?
實際來看看[網站](https://www.ncdr.nat.gov.tw/)吧!
### HTTP 標籤

例子:
`<p class="Hi!我是attributes"> Hi!我是contents </p>`
|標籤名稱|用途|
| -------- | -------- |
|`<h1> -<h6>`|標題|
|`<p>`|段落|
|`<a>`|超連結|
|`<table>`|表格|
|`<tr>`|表格內的row|
|`<td>`|表格內的cell|
|`<br/>`|換行(無結束標籤)|
|屬性名稱|意義|
| -------- | -------- |
|**class**|標籤的類別(可重複)|
|**id**|標籤的id(不可重複)|
|title|標籤的顯示資訊|
|style|標籤的樣式|
|**href**|超連結|
|data-*|自行定義新的屬性|
### HTTP 協定
**請求(request)& 回應(response)**
HTTP是一個用戶端終端(用戶)和伺服器端(網站)請求和應答的標準(TCP)。通過使用網頁瀏覽器、網路爬蟲或者其它的工具,用戶端發起一個HTTP請求到伺服器上指定埠(預設埠為80)。其中我們稱這個用戶端為用戶代理程式(user agent)。HTTP/2 為目前版本,於2015年5月作為網際網路標準正式發布。 --- from wiki
補充: [HTTP & HTTPs 間的差異](https://www.webdesigns.com.tw/HTTPorHTTPS.asp)

* 請求方法 (request)
| 方法(動作)| 解釋 (modified from wiki) |
| -------- | -------- |
| **GET** | 向指定的資源發出「顯示」請求,只用在讀取資料 |
|HEAD|與GET方法一樣,但伺服器將不傳回資源的本文部份。|
|**POST**|向指定資源提交資料,請求伺服器進行處理。|
|PUT|向指定資源位置上傳其最新內容。|
|DELETE|請求伺服器刪除Request-URI所標識的資源。|
|TRACE|回顯伺服器收到的請求,主要用於測試或診斷。|
|OPTIONS|使伺服器傳回該資源所支援的所有HTTP請求方法。|
|CONNECT|HTTP/1.1協定中預留給能夠將連線改為管道方式的代理伺服器。|
* 狀態碼
- [1xx訊息](https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81#1xx%E6%B6%88%E6%81%AF "HTTP狀態碼")——請求已被伺服器接收,繼續處理
- [2xx成功](https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81#2xx%E6%88%90%E5%8A%9F "HTTP狀態碼")——請求已成功被伺服器接收、理解、並接受 EX: 200 OK
- [3xx重新導向](https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81#3xx%E9%87%8D%E5%AE%9A%E5%90%91 "HTTP狀態碼")——需要後續操作才能完成這一請求
- [4xx請求錯誤](https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81#4xx%E8%AF%B7%E6%B1%82%E9%94%99%E8%AF%AF "HTTP狀態碼")——請求含有詞法錯誤或者無法被執行 EX: 404 Not Found
- [5xx伺服器錯誤](https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81#5xx%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%94%99%E8%AF%AF "HTTP狀態碼")——伺服器在處理某個正確請求時發生錯誤
* Header
- 通用頭欄位(英語:General Header Fields)
- 請求頭欄位(英語:Request Header Fields)
- 回應頭欄位(英語:Response Header Fields)
- 實體頭欄位(英語:Entity Header Fields)
定義了一個HTTP協定事務中的操作參數,可分為四類。 [wiki](https://zh.wikipedia.org/wiki/HTTP%E5%A4%B4%E5%AD%97%E6%AE%B5)
讓我們把網頁[原始碼變漂亮](https://www.cleancss.com/html-beautify/)吧!
### 所以到底什麼是 "請求(request)" & "回應(response)" 呢?
下載 [**Postman**](https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=zh-TW) Google Chrome plugin 來實際體驗看看吧!
in Python EX: [高鐵](https://www.thsrc.com.tw/tw/TimeTable/SearchResult)
**Form Data** 是用 post 時,需要傳給 server 的資料!
```
form_data = {
"StartStation":"2f940836-cedc-41ef-8e28-c2336ac8fe68",
"EndStation":"e6e26e66-7dc1-458f-b2f3-71ce65fdc95f",
"SearchDate":"2017/08/13",
"SearchTime":"20:30",
"SearchWay":"DepartureInMandarin"}
response_post = requests.post("https://www.thsrc.com.tw/tw/TimeTable/SearchResult", data = form_data)
```
## Python 爬蟲 GO!
教學重點:
1. request
2. 清理、篩選: BeautifulSoup, regular expression, XPath etc.
3. 重組、輸出: Pandas
### BeautifulSoup
*參考文獻:[https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/](https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/)*
How?
```
from bs4 import BeautifulSoup
soup = BeautifulSoup(page,'lxml')
```

BeautifulSoup 中分為四大類:
- **Tag**
- name
- attrs
```
print('印出第一個a Tag\n',soup.a)
print('印出該 Tag 下的 attrs\n',soup.a.attrs)
soup.a.get('id')
```
印出第一個a Tag
`<a href="/" id="logo">批踢踢實業坊</a>`
印出該 Tag 下的 attrs
`{'id': 'logo', 'href': '/'}`
印出 Tag 下特定屬性
`logo`
- **NavigableString**
- .string
- .text
```
print('取出標籤中的文字:\n',soup.a.string)
print('取出標籤中的文字:\n',soup.a.text)
```
取出標籤中的文字:
`批踢踢實業坊`
- **BeautifulSoup**
BeautifulSoup對象表示的是一個文檔的全部內容,大部分時候可以把它當作Tag對象。
- **Comment**
Comment對象是一個特殊類型的NavigableString對象。
### 常用 BeautifulSoup methods
#### **find_all**( tag_name , attrs , recursive , text , **kwargs )
* tag_name
* 標籤名稱: soup.find_all('b')
* 正規表示式: soup.find_all(re.compile("^b"))
* 列表回傳: soup.find_all(["a", "p"])
* 規則回傳: soup.find_all(has_class_but_no_id)
```
def has_class_but_no_id(tag):
return (tag.has_attr('class') and not tag.has_attr('id') )
```
* tag_name + attrs
`soup.find_all("a", class_="ptt_title")`
`soup.find_all(attrs={"data-foo": "value"}) `
* 直接搜索標籤中文字
`soup.find_all(text=["Tillie", "Elsie", "Lacie"])`
* 限制回傳數量
`soup.find_all("div", limit=2) `
### Python regular expression: **re.findall()**
[Official doc](https://docs.python.org/3/library/re.html)
[中文筆記](https://hackmd.io/CwE2GNgMwThhaSAOApvYAjATAVnhjGAdnxA3AGYsQQKYsig=?view)
```
import re
re.findall(pattern, string)
```
# 實戰演練
## 1. 水庫水位爬蟲: 靜態與可解析動態爬蟲
見 **爬蟲系列-2-3 水庫即時資訊**
## 2. [高鐵時刻表](https://www.thsrc.com.tw/tw/TimeTable/SearchResult):
cookie 與 header
```
import requests
import pandas as pd
from bs4 import BeautifulSoup
res = requests.get('https://www.thsrc.com.tw/tw/TimeTable/SearchResult')
soup = BeautifulSoup(res.text, "lxml")
name = [tag.text for tag in soup.find_all('option')[1:13]]
code = [value.attrs['value'] for value in soup.find_all('option')[1:13]]
station = pd.DataFrame({'站名':name,'代號':code},columns = ['站名','代號'])
station
```

```
import requests
import pandas as pd
# Input
url = "https://www.thsrc.com.tw/tw/TimeTable/Search"
form_data = {
'SearchType': 'S',
'StartStation': 'e6e26e66-7dc1-458f-b2f3-71ce65fdc95f',
'EndStation': 'e8fc2123-2aaf-46ff-ad79-51d4002a1ef3',
'DepartueSearchDate': '2019/02/28',
'DepartueSearchTime': '12:00',
}
headers = {'accept': 'application/json, text/javascript, */*;q=0.01',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh,zh-TW;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6',
'content-length': '340',
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'cookie':,
'origin': 'https://www.thsrc.com.tw',
'referer': 'https://www.thsrc.com.tw/tw/TimeTable/SearchResult',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36',
'x-requested-with': 'XMLHttpRequest'}
# Requests
response = requests.request("POST", url, data=form_data, headers=headers)
r = response.json()
print(response.text)
r1 = r["data"]["DepartureTable"]["TrainItem"]
df = pd.DataFrame(r1)
```
