# Python Crawler 爬蟲 整合 一句解:自動瀏覽全球資訊網的網路機器人 ## 爬蟲是什麼🤔 網路爬蟲 ( spider 或 web crawler ),是一種可以「自動」瀏覽全球資訊網的網路機器人,許多的搜尋入口網站 ( 例如 Google ),都會透過網路爬蟲收集網路上的各種資訊,進一步分析後成為使用者搜尋的資料,許多開發者也會自行開發不同的爬蟲程式,進行大數據收集與分析的動作。 當你使用瀏覽器打開一個網頁時,其實是向其伺服器發送 請求(request),並且伺服器 回傳(response) 資料再交給瀏覽器解析與渲染,才出現日常熟悉的網站。而網路爬蟲(web crawler)便是擷取伺服器回傳中我們要的特定資料,並且將過程自動化。 ### <font style="color: #FFCC99">還是不懂?</font>換個方式解釋 一個透過程式「自動抓取」網站資料的過程。 在這資訊爆炸的時代中,如果透過人工的方式來收集網站資料,效率低也耗時 因此資料的收集與整理,可以透過網路爬蟲來協助,先制定好規則,網路爬蟲就可以自動依照規則收集和擷取資料並整理出我們所需的格式,像是 Excel 試算表、CSV 檔案或是資料庫等 <br/> ### 優缺點 >提一兩個重要的,剩下的因個人的使用而異,可以自己體會或深思 - 透過程式「自動抓取」網站資料,省時省人工力 EX:取得天氣資訊 / 取得股票價格、匯率 / 比價網 > 其實 ++搜尋引擎++ (Google、Yahoo..) 應該是最成功的爬蟲應用 > 透過爬蟲收集網站與內容,當搜尋關鍵字時,才能查出最相關資料 <br/> ## 爬蟲----站位角度不同,理解不同 ### 人類眼中的網頁 - Over Browser + 透過瀏覽器 Chrome, Firefox 之類的,我們看見鮮動活潑的網頁 + 透過一些按鈕、表單,來跟網頁進行互動 + 根據網頁的設計不同,我們可能需要動用到不同的工具 <br/> ### 爬蟲眼中的世界 - HTML ```html= <div class="title"> <a href="/bbs/Stock/M.1547651681.A.458.html">[標的] 2027 大成鋼 空</a> </div> ``` - `div`, `a` 是所謂的標記 (tag) - `class=`, `href=` 是所謂的屬性 (attribute) - `[標的] 2027 大成鋼 空` 則是文本 (text) + 爬蟲的基本流程大致上就是靠這三個東西來設計 1. 根據 tag 來尋找目標 2. 因為相同 tag 的東西太多了,所以用 attribute 來過濾 3. 基本上 text 是我們最終需要的資料 <br/> ### 爬蟲任務最重要的三個基本程序 ----從上方的解釋中可以看出以下三個發展階段---- 1. 瞭解你要爬取的目標 (請求網頁內容) 2. 規劃爬取流程 (抓取網頁資料) 3. 處理分析取得的資料 (儲存資料) <br/> <br/> <br/> ## 爬蟲的運作原理 ### 連線到特定網址 >> 抓取資料 >> 解析資料 <kbd>![](https://i.imgur.com/uQakGao.png)</kbd> ### 框架 (如下) <kbd>![](https://i.imgur.com/3ug90Hu.png)</kbd> <br/> <br/> <br/> ## 網路爬蟲?網頁抓取?! - 網路爬蟲包含兩個分類:<font style="color: #FF9933">網路爬蟲</font> 和 <font style="color: #FF9933">網頁抓取</font> - 「網路爬蟲」為統稱 - 網路爬蟲發現新資料;網頁抓取將內容解析並存儲數據 ### 網路爬蟲 web crawler 有時也稱為網路蜘蛛(spider),是一種自動網路機器人,其主要的目的是為建立索引並蒐索內容,像是 Google、Bing 和 Yahoo 等搜索引擎透過網路爬蟲將抓到的資料儲存下來,以便事後生成索引供用戶搜尋 ### 網頁抓取 web scraper 是一種從特定網頁上抓取頁面內容,然後將這些資料存儲至 Excel 試算表、CSV 檔案或是資料庫等,以供其它用途的過程,一般我們提到的網路爬蟲,事實上指的是這網頁抓取(web scraper)這一類 <br/> <br/> <br/> ## 複習下要用的工具! ### Python + 瞭解 Python 的基本語法如 print, 四則運算, 字串操作, if, for 迴圈等 + 在爬蟲任務裡面,可以瞭解下 Python 的 list 和 dict 兩種物件 + list 就如同字面意義,是一連串的資料表 + dict 為 dictionary 的簡稱,像是有字典的列表 + 這兩種物件常用到,釐清這兩者觀念與操作 <br/> ### Python 函式庫 `requests` [參考](https://requests.readthedocs.io/zh_CN/latest/api.html) 使用python requests 函式庫向伺服器發送請求 ![image](https://hackmd.io/_uploads/H1XSYF686.png) ### 安裝 因為requests函數不屬於Python的預設模組,使用requests函數來抓取資料前,必須先利用pip指令安裝 ``` pip install requests ``` 安裝完成後,在程式碼中import requests。 ```python= import requests ``` ### 語法 [更多方法及語法\ (英)](https://www.w3schools.com/python/module_requests.asp) #### GET ```python= requests.get(url[,headers,cookies,params,...]) ``` #### POST ```python= requests.post(url[,headers,cookies,data,...]) ``` `[ ]`:選用 省力點也可以這樣 ```python= from requests import request request("get",url[,headers,cookies,params,...]) ``` <br /> <br /> <br /> ## 請求Request `HTTP Method` HTTP的Request方法有[九種](https://developer.mozilla.org/zh-TW/docs/Web/HTTP/Methods) 最常用的是 **`GET`** 和 **`POST`** ## `GET` Method 向指定的資源要求資料,類似於查詢操作 以google搜尋為例 先開啟google搜尋頁面 (其實這裡已經做一次請求了) https://www.google.com/search 按下`F12`可以看到我們送出的`GET`請求(要重新整理) `GET`的參數會放在URL後面 `https://網址?參數=參數值`-- [headers](https://zh.wikipedia.org/wiki/HTTP%E5%A4%B4%E5%AD%97%E6%AE%B5), [cookies](https://zh.wikipedia.org/wiki/Cookie), [params (英)](https://en.wikipedia.org/wiki/Query_string) 帶 `params` 的 `get` https://www.google.com/search?q=klaire_kriel ###### 至於為什麼要加 `search`, 其實是因為 Google 伺服器有個專門為搜索提供 `get` 的頁面被命名為 `search`,但當 `get` 請求沒有 `params` 的時候,它會自動跳向另一個被命名為 `webhp` 的頁面。 <br /> ## `POST` Method 將要處理的資料提交上去,類似於更新操作。 而當需要更新的資料是較敏感的,就會用`POST`方法把params包起來。 `POST` params-- headers, [cookies](https://zh.wikipedia.org/wiki/Cookie), [data (英)](https://en.wikipedia.org/wiki/POST_(HTTP)#Use_for_submitting_web_forms) <br /> ## 如何開發網路爬蟲程式? Python 之所以是最適合寫爬蟲的程式語言,除了寫法更簡潔之外,還可以直接使用別人開發好的套件 - <font style="color: #FF9933">BeautifulSoup</font> - 用於解析網頁的 Python 套件,能夠幫助解析 HTML 網頁並擷取出所需的資料 - <font style="color: #FF9933">Selenium</font> - 能夠幫助我們抓取由 Javascript 產生的動態網頁的內容 只需要有些 Python 程式語言的基礎,透過實作範例練習,要能夠開發出網路爬蟲工具,比起以前是相當容易的 <br /> <br /> # 開發示例 ### `GET` 打開colab,抓取[ptt熱門看版網頁](https://www.ptt.cc/bbs/) ```python= import requests response = requests.request("GET", url='https://www.ptt.cc/bbs/')#可直接用request函式並在參數內選擇方法(get,post) print(response.text) #印出資料,也就是文字版的網頁 print(type(response)) #看它的資料型態 print(vars(response)) #看它的屬性 ``` ### 帶 `params` 的 `get` [HTTP狀態碼大全](https://zh.wikipedia.org/zh-tw/HTTP%E7%8A%B6%E6%80%81%E7%A0%81) ```python= import requests url = 'https://www.google.com/search' payload = { 'q':'klaire_kriel' } #dict response = requests.request("GET",url=url, params=payload) #關鍵字引數 print(response.text) # 確認從伺服器傳回的狀態碼 print(response.status_code) #200 ok # 判斷伺服器狀態後再抓取 # Python requests 函式庫 定義給 Response 的 status 被命名為 status_code if response.status_code == requests.codes.ok: print(response.text) ``` <br /> <br /> <br /> ## 使用 Beautiful Soup 抓取與解析網頁資料 Beautiful Soup 是一個 Python 的函式庫模組,可以快速解析網頁 HTML 碼,從中翠取出我們有興趣的資料、去蕪存菁。 一樣先安裝,但在colab裡都幫你載好了 ``` pip install bs4 ``` #### Beautiful Soup 基本用法 我們先以簡單的html檔來看bs4的功能 ```python= # 引入 Beautiful Soup 模組 from bs4 import BeautifulSoup as bs # 原始 HTML 程式碼 假設我們已經有檔案了 html_doc = """ <html><head><title>前進吧!高捷少女</title></head> <body><h2>K.R.T. GIRLS</h2> <p>小穹</p> <p>艾米莉亞</p> <p>婕兒</p> <p>耐耐</p> <a id="link1" href="https://zh.wikipedia.org/wiki/%E9%AB%98%E6%8D%B7%E5%B0%91%E5%A5%B3#%E5%B0%8F%E7%A9%B9">Link 1</a> <a id="link2" href="https://zh.wikipedia.org/wiki/%E9%AB%98%E6%8D%B7%E5%B0%91%E5%A5%B3#%E8%89%BE%E7%B1%B3%E8%8E%89%E4%BA%9E">Link 2</a> <a id="link3" href="https://zh.wikipedia.org/wiki/%E9%AB%98%E6%8D%B7%E5%B0%91%E5%A5%B3#%E5%A9%95%E5%85%92 3</a> <a id="link4" href="https://zh.wikipedia.org/wiki/%E9%AB%98%E6%8D%B7%E5%B0%91%E5%A5%B3#%E8%80%90%E8%80%90">Link 4</a> </body></html> """ # 以 Beautiful Soup 解析 HTML 程式碼 soup = bs(html_doc, 'html.parser') #前項"html_doc"為html的文字資料,後項"'html.parser'"為指定以何種解析器來分析html文字 print(soup) ``` ### 找尋網頁中的元素 `Ctrl + Shift + I` ![](https://i.imgur.com/haX51a0.png) <br /> <br /> <br /> ## 取得節點文字內容 ### ----獲取標籤內容---- `name`參數方法,可查找所有名為`name`的tag。 ```python= web_title = soup.title #取得網頁標題 print(web_title) print(web_title.string) #使用字串存取 ``` ### ----找查元素---- [find_all ( )](https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/#find-all) 、[find](https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/#find) ```python= # 選取全部符合條件(標籤節點)的元素 my_girls = soup.find_all('p') #<p></p>標籤 print(my_girls) # 選取第一個符合條件(標籤節點)的元素 my_girls = soup.find('p') # 返回具有keyword的元素 my_link = soup.find(id='link1') ``` <br /> <br /> <br /> <br /> ## 注意事項 - 不造成網站伺服器的負擔 > 每次爬取資料時,設定適當的等待延遲,避免短時間內送出大量的請求而造成伺服器的負擔 ( DDoS 攻擊,根據刑法第 360 條可能會觸法 )。 - 確認網站是否有提供 API > 如果網站有提供 API 供第三方直接取得資料,可以直接透過 API 抓取資料,節省讀取與分析網站 HTML 的時間。 - 注意 robots.txt > robots.txt 會規範一個網站允許什麼樣的 User-Agent 訪問,也會規範 Crawl-delay 訪問間隔時間,如果 Crawl-delay 設定 1,表示這個網站期望每次訪問的時間間隔一秒鐘。 - 盡可能讓程式模仿一個普通使用者的操作,因為許多網站不希望人家用程式去抓取他們的資料 - 必須包含 Headers - 遵守 robots.txt 的規範 > 確保爬蟲遵守 robox.txt 文件中定義的規範,此文件通常會在網站的根目錄中(robots.txt),它會說明爬蟲應該或是不應該爬的網站內容,例如在 Yahoo 奇摩電影的 robots.txt 中提到不限任何 User-agent,以及沒有限制哪些頁面不能爬取 <br /> <br /> <br /> <br /> # <font style="color: #FF9933">Test 1</font> (Newest) ## 建立請求 載入requests模組以建立HTTP請求 ```python= import requests ``` 建立GET請求 ```python= url = '' //填入目標網址以取得網頁內容 resp = requests.get(url) //建立get請求 ``` ## 解析請求 ### 目標為JSON 使用json()方法解析內容,返回值(data)為字典 ```python= data = resp.json() ``` <br /> ### 目標為HTML網頁內容 需使用BeautifulSoup模組裡的方法,使用以下指令載入該模組 ```python= from bs4 import BeautifulSoup ``` 使用BeautifulSoup(htmlMarkup,‘html5lib’)方法解析網頁內容,返回值(soup)為<class ‘bs4.BeautifulSoup’> ```python= soup = BeautifulSoup(resp.text,'html5lib') ``` <br /> ## 目標為JSON 以台灣證券交易所-201901月個股日成交資訊為例,[網址連結](https://www.twse.com.tw/exchangeReport/STOCK_DAY?response=json&date=20190101&stockNo=2881) 該請求返回的內容如下圖 ![image](https://hackmd.io/_uploads/SJ_7SJELp.png) 需使用Pandas模組裡的方法,使用以下指令載入該模組 ```python= import pandas as pd ``` 我們需要取得data裡的0~20所有資訊做為內容,將其放入pandas的dataframe裡,並使用fields作為其欄位的標籤 ```python= # data[]為先前取得的資料,型態為字典 df = pd.DataFrame(data['data'],columns = data['fields']) ``` 若要使用foreach迴圈讀取或印出dataframe的內容時,需使用df.values作為範圍,若只使用df作為範圍會得到錯誤的結果 ```python= for row in df: print(row) ``` ```txt= >日期 成交股數 成交金額 開盤價 最高價 最低價 收盤價 漲跌價差 成交筆數 ``` ```python= for row in df.values: print(row) ``` ```txt= >['108/10/01' '8,895,183' '399,156,974' '44.85' '45.00' '44.75' '45.00' '+0.45' '3,310'] ['108/10/02' '4,399,985' '196,735,002' '44.55' '44.85' '44.55' '44.75' '-0.25' '1,745'] ... ['108/10/31' '17,527,040' '787,119,996' '45.40' '45.40' '44.55' '44.55' '-0.90' '5,017'] ``` <br /> ## 目標為HTML網頁內容 類別為’bs4.BeautifulSoup’的物件可使用以下方法 ```python= find() //查詢第一筆符合的資料 find(htmlTag) //查詢標籤類型為htmlTag的第一筆元素 find(id=‘elementID’) //查詢id為elementID的元素 find(htmlTag,class_=‘className’) //查詢標籤為htmlTag且class為className的第一筆元素 find(htmlTag,className) //查詢標籤為htmlTag且class為className的第一筆元素 find_all() //查詢所有符合的元素 find_all(htmlTag) //查詢所有符合標籤類型為htmlTag的元素 find_all(id=‘elementID’) //查詢id為elementID的元素 find_all(htmlTag,class_=‘className’) //查詢標籤為htmlTag且class為className的所有元素 find_all(htmlTag,className) //查詢標籤為htmlTag且class為className的所有元素 ``` 若使用find_all()查詢,可使用foreach迴圈歷遍所有查找到的元素 ```python= for content in soup.find_all('li'): print(content.string) ``` 使用.string方法取得元素的內容 ```python= soup = BeautifulSoup('<h1 id="thisish1">content</h1>', 'html5lib') print(soup.h1) // 得 <h1 id="thisish1">content</h1> ``` ```python= soup = BeautifulSoup('<h1 id="thisish1">content</h1>', 'html5lib') print(soup.h1.string) // 得 content ``` 使用get(‘href’)方法取得元素的href屬性 ```python= soup = BeautifulSoup('<h1 href="katsuobushi.com">content</h1>', 'html5lib') print(soup.h1.get('href')) // 得 katsuobushi.com ``` ## 輸出檔案-- csv 需載入os及csv模組以使用open及writer方法 ```python= import os import csv ``` 使用w模式開啟檔案,w模式為打開一個文件只用於寫入,若文件已存在則打开文件,並從頭開始編輯,原有内容會被刪除。若文件不存在,則創建新文件。 ```python= file = open(filepath,'w',newline='',encoding='utf-8-sig') csvWriter = csv.writer(file) // 使用csv.writer()創建一個writer csvWriter.writerow(row) // writerow()方法可將一列資料寫入到檔案中 file.close() // 寫入完畢後須關閉檔案,釋放資源 ``` # <font style="color: #FF9933">Test 2</font> ```python= import urllib.request as req #建立一個 Request 物件,附加 Request Headers 的資訊 url = "網址" request = req.Request(url, headers = { "User-Agent":"需要的資訊" #到網頁 → F12 → Network → 通常是最上面的那個 → Headers → Request Headers → user-agent }) with req.urlopen(request) as response: data = response.read().decode("utf-8") ``` ### 解析資料 - JSON 格式 >使用內建的 JSON 模組來解析 - HTML 格式 > 使用第三方套件 BeautifulSoup 來解析 - 安裝 BeautifulSoup > 使用 pip 套件管理工具 ( 安裝python 時,就一起裝了 ) ```python= $ pip install beautifulsoup4 ``` ```python= import bs4 root = bs4.BeautifulSoup(data, "html.parser") #解析html print(root.title.string) #抓 title 標籤底下的 string titles = root.find("div", class_ = "title") #尋找 class = "title" 的 div 標籤 print(titles.a.string) titles = root.find_all("div", class_ = "title") #尋找所有 class = "title" 的 div 標籤 for title in titles: if title.a != None: print(title.a.string) ``` # <font style="color: #FF9933">Test 3</font> ### <font style="color: #FFCC33">抓ptt電影版</font> (用urllib內的request) [ptt電影版](https://www.ptt.cc/bbs/movie/index.html) #### 抓資料 ```python= import urllib.request as req #連線 url = "https://www.ptt.cc/bbs/movie/index.html" with req.urlopen(url) as response: data=response.read().decode('utf-8') print(data) ``` >連線會被拒絕 開啟網頁network,找到連線細節,點選index.html 找到 user-agent ```python= import urllib.request as req #連線 url = "https://www.ptt.cc/bbs/movie/index.html" #建立一個Request物件,附加Request headers的資訊 request = req.Request(url, headers={ "User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36" }) with req.urlopen(request) as response: data=response.read().decode('utf-8') print(data) ``` #### 解析資料(原始碼) 使用第三方套件:beautifulsoup > bs4.BeautifulSoup(要處理的原始碼,"格式") >> 格式可以放:html.parser >> [格式參考](https://blog.csdn.net/Winterto1990/article/details/47806175) | 解析器 | 說明 | | ----------- | ---- | | html parser | Python 3.2後開始內建,主要解析為HTML | | lxml | 以C語言編寫,解析速度快,支援性廣。主要解析為HTML | | lxml-xml | 以C語言編寫,解析速度快,支援性廣。主要解析為XML | | html5lib | 使用瀏覽器相同的方式進行解析,所以相容性相對的好,缺點是速度慢。 | 解析器安裝方式 pip install lxml pip install lxml-xml pip install html5lib >find("標籤",class_="class名") 找一個符合條件的 >find_all("標籤",class_="class名") 找到所有符合條件的 >.string 取文字 ```python= import bs4 #美化原始碼格式 root = bs4.BeautifulSoup(data,"html.parser") title=root.title #抓title標籤 stitle = root.title.string #抓標籤內的文字 t = root.find_all("div", class_="tiltle") #尋找class="title" 的div標籤 for title in t : if title.a != None: #如果標題包含a標籤(沒有被刪除),印出來 print(title.a.string) ``` ### 爬104 用requests ```python= import requests from bs4 import BeautifulSoup import lxml url = ( 'https://www.104.com.tw/jobs/search/?ro=0&keyword=Python&jobcatExpansionType=0&area=6001001005&order=15&asc=0&page=1&mode=s&jobsource=2018indexpoc') dom = requests.get(url).text soup = BeautifulSoup(dom, 'lxml') jobs = soup.find_all('article', class_="b-block--top-bord job-list-item b-clearfix js-job-item") # print(jobs[0].find('a',class_="js-job-link").text) for job in jobs: print(job.find('a',class_="js-job-link").text) print(job.get('data-cust-name')) print(job.find('p').text) print(job.find('span', class_="b-tag--default").text) print("----------------------------------------") ``` <br /> <br /> <br /> <br /> # 進階內容 ## 靜態? 動態! > 先有個簡單的認知即可 ### <font style="color: #FF9933">靜態爬蟲</font> 靜態網站是指網站完成一個請求 ( request ) 與回應 ( response ) 後,用戶端即不再與伺服器有任何的交流,所有的互動都只與瀏覽器的網頁互動,資訊不會傳遞到後端伺服器。 通常靜態網站爬蟲比較容易實作,只要爬蟲已經閱讀完整份網頁,就可以取得這個網頁所有的資訊進行分析 ( 如同去餐廳吃飯點了餐,餐送上來之後就可以慢慢品嚐 )。 ![image](https://hackmd.io/_uploads/HkThutpS6.png) <br/> ### <font style="color: #FF9933">動態爬蟲</font> 動態網站是指網站會依照使用者的行為不斷的與伺服器進行交流,例如傳送了 apple 資訊給伺服器,資訊經過伺服器處理後,才會回應 apple 是甜的、紅的、脆的...等相關資訊,不少動態網站甚至需要進行「登入」的動作,像是 Facebook、Instagram...等。 通常動態網站爬蟲實作比較複雜,爬蟲必須要知道網站需要什麼「資訊」,提供了正確的資訊,才能取得所需要的資料 ( 如同開啟保險箱一般,輸入了正確的密碼,才能開啟保險箱的內容 )。 ![image](https://hackmd.io/_uploads/rk4kYtTra.png) <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/> link === [2022/08/05 【Python學堂】新手入門第十二篇-如何利用Python爬取靜態網頁資料](https://www.pcschool.com.tw/blog/it/web-crawler-tutorial?fromto=99001126) [weatherAPI Url1](https://www.weatherapi.com/docs/) [weatherAPI Url2](https://www.weatherapi.com/weather/q/taipei-2478871)