# 用爬蟲幫我蒐集餐廳評價! [TOC] > 這篇文章希望讓沒有網頁基礎的讀者也能看懂,所以會介紹網頁的基礎原理、基礎 HTML、爬蟲工具介紹。以下是文章架構: > 1. 爬蟲能做什麼及原理 > 2. 網頁怎麼形成 > 3. 怎麼找網頁上資源的網址 > 4. 看懂 HTML > 5. 介紹爬蟲工具 ## 爬蟲是什麼? 我們平常會透過瀏覽器連上網站來查找資訊,比如購物網站的商品、徵才網站上的職缺、即時股價…,當我們需要定時或是大量蒐集這些資訊時,每次要都手動操作是非常費時費力的,如果能交給程式來做是不是就輕鬆多了?這個能幫我們蒐集網站資料的程式,就叫做爬蟲。 ### 爬蟲能做什麼? 簡單來說爬蟲就是可以用程式去模擬真人瀏覽網站的行為,比如取得網站資料或送出資料給網站 | 真人 | 爬蟲 | | -------- | -------- | | 瀏覽 Dcard 上的貼文 | 取得 Dcard 上的貼文| | 登入 Facebook | 送出帳密給 Facebook > 想要透過爬蟲程式幫我們瀏覽網站,就要先了解我們實際瀏覽網站的流程 [影片連結](https://www.youtube.com/watch?v=BdRjutf8K0c&ab_channel=%E5%AD%B8%E4%BB%81%E5%A4%A7%E5%A4%A7) ## 爬蟲的原理 ### 瀏覽網頁的流程 * 人:打開瀏覽器 → 搜尋網站 → 瀏覽器向目標網站發出請求 (request) → 伺服器回應 (response) 網頁 (HTML) 給瀏覽器 → 瀏覽器將網頁顯示在畫面上 ![](https://hackmd.io/_uploads/rJuFw85ZT.png) [資料來源](https://medium.com/%E8%AA%A4%E9%97%96%E6%95%B8%E6%93%9A%E5%8F%A2%E6%9E%97%E7%9A%84%E5%95%86%E7%AE%A1%E4%BA%BAzino/%E5%88%9D%E5%AD%B8%E8%80%85%E5%BF%85%E7%9C%8B-%E4%B8%80%E5%80%8B%E8%A7%80%E5%BF%B5-%E9%96%8B%E5%95%9Fpython-%E7%B6%B2%E8%B7%AF%E7%88%AC%E8%9F%B2%E6%88%90%E9%95%B7%E4%B9%8B%E8%B7%AF-%E5%90%AB%E8%A7%A3%E8%AA%AA%E5%BD%B1%E7%89%87-fac0a17cd261) * 爬蟲:利用程式向目標網站發出請求 (request) → 伺服器回應 (response) 網頁 (HTML) 給程式,拿到網頁後我們就可以拿來運用 ![](https://hackmd.io/_uploads/SkRIIkneT.png) [資料來源](https://medium.com/%E8%AA%A4%E9%97%96%E6%95%B8%E6%93%9A%E5%8F%A2%E6%9E%97%E7%9A%84%E5%95%86%E7%AE%A1%E4%BA%BAzino/%E5%88%9D%E5%AD%B8%E8%80%85%E5%BF%85%E7%9C%8B-%E4%B8%80%E5%80%8B%E8%A7%80%E5%BF%B5-%E9%96%8B%E5%95%9Fpython-%E7%B6%B2%E8%B7%AF%E7%88%AC%E8%9F%B2%E6%88%90%E9%95%B7%E4%B9%8B%E8%B7%AF-%E5%90%AB%E8%A7%A3%E8%AA%AA%E5%BD%B1%E7%89%87-fac0a17cd261) * 簡單來說,我們瀏覽網頁時,瀏覽器會去跟網頁伺服器要求網頁的 HTML,拿到後再顯示在網頁上,就會變成我們看到的網頁 * 而爬蟲程式就是模擬這樣的步驟。程式在執行時會取得網頁的 HTML,取得後就可以拿來利用,比如分析網頁上的留言等 > 已經知道爬蟲會拿到 HTML,那 HTML 是什麼? ### HTML 是什麼? * 全名 : HyperText Markup Language - 超文本標記語言 * 就是我們看到的網頁 * HTML 就像是一個 Word 檔案,可以在裡面寫入文字、排版、放圖片等,再用瀏覽器打開就會變成我們看到的網頁 ![](https://hackmd.io/_uploads/SkrHtU5-p.png) * 也就是說,HTML 包含了網頁上的內容 * 所以要用爬蟲蒐集網頁上的資料,做法就是拿到網頁的 HTML,再從裡面找出資料 > 已經知道瀏覽網頁的流程了,接下來就要先了解怎麼取得網頁上的資料,進而用程式去取得資料 ### 網頁通常是由 HTML 和各種資源組成 * 網頁不一定只是一個 HTML,通常是由 HTML 加上如 CSS (網頁美化)、資料、圖片...等**資源**共同組成 * 上述的資源都有各自對應的網址。只要對這些網址發送請求,就能取得想要的資源 ![](https://hackmd.io/_uploads/SyvNXl2g6.png) [圖片連結](https://developers.google.com/community/gdsc/images/gdsc-social-share.png) * 瀏覽器連上網站後會向伺服器請求這些資源,取得回應的資源後再把這些資源放到 HTML 裡,最後顯示在畫面上 ![](https://hackmd.io/_uploads/ryj3_xngp.png) * 所以我們瀏覽網頁時所看到的 HTML,是已經將這些資源放進去後產生的完整 HTML ### 怎麼取得資源的網址? * 前面已經知道資源都有各自對應的網址,只要對這些網址發送請求,就能從回應中取得資源 * Q: 那要怎麼知道資源對應的網址? * A: 透過瀏覽器的 Network 介面找出我們要的資源 * 按`F12`或右鍵 -> 檢查 ![](https://hackmd.io/_uploads/rJ-g2GBWa.png) [資料來源](https://www.twse.com.tw/zh/trading/historical/stock-day-avg.html) * 開啟後右上方選 Network (之後要先重新整理畫面一次) * Network 會監測瀏覽器發出的請求 (request) 與收到的回應 (response) * 我們現在看到的畫面也是收到的回應資料之一 (是一個 HTML) ![](https://hackmd.io/_uploads/SyQ0iR2Tn.png) [資料來源](https://www.twse.com.tw/zh/trading/historical/stock-day-avg.html) * 可以根據資源類型篩選,會更容易找到 * ![](https://hackmd.io/_uploads/rkbgrzpa3.png) * All: 全部類型 * Fetch/XHR: 網頁發送請求取得的資源 (通常資料會在這裡) * Js: JavaScript,網頁邏輯程式碼 * CSS: 網頁美觀程式碼 * Img: 圖片檔案 * Doc: HTML檔案 * 以證交所網站為例,篩選資源後可以一個一個找,從 Preview 或 Response 中查看回應資料的內容,就能找到想要的資料 ![](https://hackmd.io/_uploads/SJyPLf6T2.png) * 找到想要的資料後,從 Headers 中的 Request URL 就能知道資源對應的網址 (URL) ![](https://hackmd.io/_uploads/Bk9oPfppn.png) * 所以我們只要對這個網址發送請求,就能取得想要的資源,也就是台股收盤價 ::: info Request Method > 發送請求的方法,也就是你想對這個資源做什麼樣的操作 * 常見的 Request Method: * GET: 取得資料。 * POST: 送出資料,比如登入時送出帳號資訊 * PUT: 修改資料 * DELETE: 刪除資料 * 如果在 Headers 看到 Request Method 是 GET,那在用爬蟲程式發送 Request (請求) 時,Request Method 就要設定為 GET * 以這張圖為例,我們取得這個資源的方法 (method) 是 GET,所以程式的 Request Method 就要設成 GET ![](https://hackmd.io/_uploads/Bk9oPfppn.png) [資料來源](https://ithelp.ithome.com.tw/articles/10299957) ::: ## 爬蟲時可能會發生的狀況 **Q1: 明明我看網站的 HTML 上有我想要的資料,但用爬蟲爬下來後卻沒看到** * A: 可能是因為這個網站是**動態**網站,而不是**靜態**網站 > 那什麼是靜態網站,什麼是動態網站? ### 靜態網站 vs 動態網站 * **靜態網站** ![](https://hackmd.io/_uploads/ByF4mwUW6.png) [資料來源](https://steam.oxxostudio.tw/category/python/spider/about-spider.html) * 特點: * 伺服器將資料寫在回應給瀏覽器的 HTML 上 * 只要對這個網站的網址發送請求,就可以得到想要的資料 * 但因為資料被放在 HTML,所以要自己從 HTML 整理出想要的資料 * 就像是一本講義,這本講義可以輕鬆買到,但要自己整理重點 * 例子:https://histock.tw/stock/rank.aspx * 打這個網頁時,股票資訊就已經寫在網頁裡了 * 對這個網頁發送請求,拿到 HTML 後再從 HTML 裡找出想要的資料 * **動態網站** ![](https://hackmd.io/_uploads/rJy0BPU-T.png) [資料來源](https://steam.oxxostudio.tw/category/python/spider/about-spider.html) * 特點: * 資料沒有寫在回應給瀏覽器的 HTML 上,而是瀏覽器另外向伺服器請求該資料 * 直接對這個網站的網址發送請求不一定能取得想要的資料 * 要對資料的網址發送請求 -> 在 Network 介面中尋找資料的網址並發送請求 * 因為是直接取得該資料,所以整理起來很方便 * 就像跟別人要整理好的筆記,需要找一下筆記放在哪裡,但拿到筆記後重點別人都已經整理好了 * 例子:https://www.twse.com.tw/zh/trading/historical/stock-day-avg.html * 在網頁中輸入想要查詢的股票和年、月並按查詢後,才會去向伺服器請求對應的資料,取得回應後再顯示在網頁上 ![](https://hackmd.io/_uploads/SyMJzmB-6.png) * 只要對股價資料的網址發送請求,就能拿到股價的資料 **Q2: 怎麼分辨是靜態網站還是動態網站** * A: 兩個方法 1. 在 Network 介面看回應的 HTML 裡有沒有想要的資料,若沒有 -> 可能是動態網站 2. 用爬蟲爬網站的 HTML 下來看有沒有像要的資料,若沒有 -> 可能是動態網站 **Q3: 爬蟲程式連不上網站** > 有些網站會擋爬蟲,可能可以透過偽裝成瀏覽器解決 * A: 修改 Request Header 的內容,就能將爬蟲程式偽裝成瀏覽器 * 網頁伺服器可以從 Request Header 中的 User-Agent 了解使用者使用的瀏覽器 ![](https://hackmd.io/_uploads/SyPzN0LW6.png) * 也就是說,爬蟲程式在發送請求時,我們只要在 Header 的 User-Agent 中放入瀏覽器的資訊,網頁伺服器就會認為這個請求是由瀏覽器發送,而不是爬蟲程式 ### 爬蟲注意事項 * 有禮貌 若是對同一個網頁持續爬蟲,盡量每發一次請求就停頓幾秒,可以減低被網站封鎖的機率。因為對網頁擁有者來說,過多的流量消耗會導致伺服器吃不消而掛掉,所以短時間大量發送請求會被認為是在攻擊網站,而被網站封鎖 * 爬蟲穩定度較低 * 如果是長期持續的爬蟲,可能會遇到一些例外的情況: 1. 發送請求時連線失敗 * 可以利用程式的 try catch 機制確保請求有發送成功 * 每次發送請求之後都停頓幾秒,讓伺服器有喘息時間 2. 網頁內容如果有更改,爬蟲程式會出現錯誤 * 可能要持續記錄資料蒐集的情況,才能及時去更改程式碼 * 或是有些網站有提供 API 的方式,直接向 API 的網址發送請求就可以拿到資料,而不用解析 HTML 的內容,就不會因網頁內容更改而出現錯誤 3. 持續向某一網站爬蟲時,伺服器可能因效能問題,會回傳錯誤的資料 * 可以利用程式的 try catch 機制確保回應的資料是正確的 * 每次發送請求之後都停頓幾秒,讓伺服器有喘息時間 ## HTML 介紹 > 爬到 HTML 之後,要看得懂 HTML 才有辦法整理出我們想要的資料 ### 標籤 & 元素 * 透過 **標籤<>** (tag) 來描述文字、超連結、圖片等 **元素** 的種類和顯示方式 * 簡單來說,標籤就是告訴 HTML 我要寫入哪種資料,而寫入的資料就是元素 * ex : `<h1>Hello</h1>` 在網頁上會是大標題格式的文字 > `<h1>` 是標籤,Hello 是元素 * 常見標籤: * `<h1>` 大標題文字 * `<h2>` 比大標題小的文字 * `<h3>...` 依此類推,到`<h6>` * `<p>` 段落文字 * `<a>` 超連結文字 * `<img>` 圖片 * `<button>` 按鈕 * `<form>` 表單,ex : 登入表單 * `<input>` 輸入欄位 * `<table>` 表格。通常會搭配`<th>`表頭、`<tr>`表格的每一列、`<td>`表格的每一欄 * `<div>` 區塊,可以包含多個標籤,通常是方便調整整個區塊內標籤的排版或美觀時使用 * HTML 範例: ```html=1 <!DOCTYPE html> <html> <head> <title>範例網頁</title> </head> <body> <h1>Hello</h1> <p>哈囉!</p> </body> </html> ``` ![](https://hackmd.io/_uploads/SkKEngBC2.png) * HTML 通常會搭配 **CSS** 和 **JavaScript** 一起使用 * **CSS** : 用於調整網頁排版、美觀 * **JavaScript** : 網頁上的程式碼。可以實現互動功能,比如設定按了某按鈕會跳出警告訊息 * Q: tag 跟爬蟲有什麼關係? * A: 爬蟲程式取得 HTML 後,可以透過 tag 找到想要的資料 ### CSS & JavaScript 以及 class & id > 可以用 class 和 id 來快速找到資料的位置 * CSS 和 JavaScript 通常會透過 **tag (標籤)**、**class (類別)**、**id (識別號)** 來控制元素 * **class** 類別 : 可以將多個元素設為同個 class,並對這個 class 的元素統一進行操作 * 就像多個學生可以是相同學系,多個元素也可以有相同的 class * **id** 識別號 : 可以為一個元素設定該元素獨有的 id,可以透過 id 直接找到這個元素 * 就像學生有自己唯一的學號,元素也可以有唯一的 id * 範例: ```html=1 <!DOCTYPE html> <html> <head> <title>範例網頁</title> <style> .class2 { color: red; } </style> </head> <body> <h1 id="text1" class="text2">Hello</h1> <p class="text2">哈囉!</p> </body> <script> document.getElementById('text1').innerHTML = 'Hello World!'; </script> </html> ``` * 取得 id 為 text1 的元素會找到 `<h1 id="text1" class="text2">Hello</h1>` 這個元素 * 取得 class 為 text2 的元素會找到 `<h1 id="text1" class="text2">Hello</h1>` 和 `<p class="text2">哈囉!</p>` 這兩個元素 * Q: class 和 id 跟爬蟲有什麼關係? * A: 爬蟲程式取得 HTML 後,可以透過 tag 的 class 和 id 從 HTML 中快速找到想要的資料 ## 爬蟲工具介紹 (以 Python 為範例) 以下是爬蟲經常會使用到的工具: * **Requests 請求工具** * 用程式發送請求,取得回應的資源 * **BeautifulSoup 解析工具** * 如果 Requests 得到的回應資源是 HTML,可以用 BeautifulSoup 來解析(整理) HTML,找出想要的資料 * **Selenium 動態爬蟲** * 用程式模擬真人使用瀏覽器(預設會開啟瀏覽器),在爬取動態網站較難找到資源的網址時適合使用 ### Requests > 程式可以透過 Requests 向伺服器發送請求並回傳回應 * 安裝指令 (在本機執行的話要安裝) `pip install requests` * 導入程式 `import requests` * 發送請求 * 發送 get 請求 * 程式在發送請求後會回傳回應結果,可以用變數來存放 (這裡的r) `r = requests.get('網址')` * 發送 post 請求 * 第一個位置是網址,第二個位置放要傳送的字典型態資料。ex : 傳送登入的帳號密碼 `r = requests.post('網址', {'key':'value'})` >key 是名稱,value 是數值。ex:'name' : 'user1' * 印出回應的文字內容 * 如果回應是 html : `print(r.text)` * 如果回應是 json : `print(r.json())` * 印出回應的編碼 `print(r.encoding)` * 印出 HTTP 狀態碼 `print(r.status_code)` :::info **HTTP 狀態碼** * 一個三位數,表示一個 HTTP 請求是否已完成 * 分為五種類: * 資訊回應 (Informational responses, 100–199) * 表示請求已被接收,伺服器正在等待進一步操作 * 成功回應 (Successful responses, 200–299) * 表示請求已成功接收 * 重定向 (Redirects, 300–399) * 表示客戶端需要採取進一步的操作才能完成請求 * 客戶端錯誤 (Client errors, 400–499) * 表示客戶端出現了錯誤或無法完成請求 * 伺服器端錯誤 (Server errors, 500–599) * 表示伺服器在處理請求時出現錯誤 * 常見狀態碼: * 200 OK : 請求成功,很棒 * 403 Forbidden : 客戶端沒有訪問權限 * 404 Not Found : 伺服器找不到對應的資源,比如使用者把網址打成不存在的網址 * 500 Internal Server Error : 伺服器發生錯誤 [資料來源](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Status) ::: * 如果今天爬的是一個 HTML,即使用 `print(r.text)` 印出網頁內容文字,還是會有 HTML 的 tag,並不是乾淨的資料 * 若要取出乾淨的資料,則要透過解析工具,也就是接下來要介紹的 **BeautifulSoup** ### BeautifulSoup > 我們透過 Requests 取得網頁 HTML 之後,通常會搭配 BeautifulSoup 來解析 HTML,找出想要的資料 * 安裝指令 (在本機執行的話要安裝) `pip install BeautifulSoup4` * 導入程式 `from bs4 import BeautifulSoup` * 使用解析器解析 HTML * 將前面 Requests 回傳的 HTML,利用 `html.parser` 這個解析器解析,並把結果存到變數 `soup` 裡 ```python=1 r = requests.get('網址') soup = BeautifulSoup(r.text, 'html.parser') ``` ::: info BeautifulSoup 所有解析器 * 除了前面提到的 `html.parser` 這個解析器之外,還有以下這些解析器: ![](https://hackmd.io/_uploads/B1S-kyI02.png) * 除了 `html.parser` 是內建之外,其他解析器要透過指令安裝 * `lxml` : `pip install lxml` * `html5lib` : `pip install html5lib` [資料來源](https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/) ::: * 印出排版過後的 HTML `print(soup.prettify())` * 解析 HTML 之後,就可以利用 find 和 select 兩種方法來找出想要的資料: * **find** * 以 **tag** 搜尋 * 找出第一個符合條件的標籤 ```python=1 first_a = soup.find('a') # 會回傳第一個 <a> 標籤 ``` * 找出所有符合條件的標籤 ```python=1 all_a = soup.find_all('a') # 會回傳包含所有 <a> 標籤的 list ``` * 同時搜尋多種標籤 ```python=1 all_a_p = soup.find_all(['a', 'p']) # 將條件的標籤放在 list 裡 # 只要是 <a> 或 <p> 標籤都會回傳 # 如果是 find 只會回傳符合條件的第一個 # find_all 則是回傳所有符合條件標籤的 list ``` * 限制 `find_all` 搜尋數量 * `find_all` 預設會搜尋所有符合條件的標籤。若 HTML 文件較大或符合條件的標籤數量較多,而想要的資料在比較前面的位置時,可以限制搜尋的數量,當搜尋到設定的數量就不會再繼續搜尋,可以減少執行的時間 ```python=1 first_2_a = soup.find_all(['a'], limit=2) # 會回傳前兩個 <a> 標籤的 list ``` * 以 **id** 搜尋 * 找出所有 `id` 為 `test` 的標籤 ```python=1 tags = soup.find_all(id='test') ``` > FIXME : id 不能重複為甚麼還有 find_all ? * 可以同時使用 tag 和 id 搜尋 ```python=1 tag = soup.find_all('h2', id='test') ``` * 以 **class** 搜尋 * 找出所有 `class` 為 `test` 的標籤 > 是 class_ 不是 class ```python=1 tags = soup.find_all(class_='test') ``` * 同時搜尋 tag、id、class * 找出所有 `tag` 為 `<p>`、`id` 為 `test_id`、`'class` 為 `test_class` ```python=1 soup.find('p', id='test_id', class_='test_class') ``` * 搜尋上層標籤 * 如果今天無法定位的想要的標籤,但可以該標籤內的子標籤,可以用 `find_parent` 和 `find_parents` 來搜尋子標籤的父標籤,就能定位到我們想要的標籤 * `find_parent`、`find_parents` 就像 `find`、`find_all`的概念,一個是找第一個,另一個是找全部 * 如果 HTML 長這樣,我們想要取得這個 `<p>` 標籤但無法定位,就可以搜尋 `<b>` 的父節點來找到 `<p>`: ```html=1 <p>這是一份<b class="boldtext">HTML文件</b>。</p> ``` ```python=1 tag_b = soup.find('b') parent_p = tag_a.find_parent('p') print(parent_p) ``` * 用前面的方法得到的會是完整的標籤,如果要取出文字,就是用 `getText()` ```python=1 all_p = soup.find_all('p') for p in all_p: print(p.getText()) ``` * **select** ### Selenium * 安裝 `selenium` 和相關套件 * colab * 安裝 selenium ```=1 !apt-get update !pip install selenium ``` * 安裝 webdriver (要讓 selenium 使用哪種瀏覽器就安裝哪種) ```=1 !apt install chromium-chromedriver // chromium !apt install firefox-geckodriver // firefox ``` * 本機 ```=1 pip install selenium ``` * [selenium 4.10.0 之後要用 Service](https://stackoverflow.com/questions/76428561/typeerror-webdriver-init-got-multiple-values-for-argument-options) * https://googlechromelabs.github.io/chrome-for-testing/#stable ## 爬蟲程式設計流程分享 ## 實際爬網站 ### 其他 * moodle 登入 * dacard 留言 * 大樂透 ## reference * [檢視網頁](https://ithelp.ithome.com.tw/m/articles/10294726)