美麗的湯 🥣
接續著前面的講題,現在我們要來進行一些進階的爬蟲操作,包括 BeautifulSoup4 的應用,並且製作一些簡單的爬蟲。
請遵照你之前在自己電腦安裝 pygame 的方法,執行指令安裝幾個需要的套件:
pip install bs4 html5lib
前面如果你沒有裝 requests
,也請一起裝上。
為了加強大家對 HTML 的語感(?),我們先來進行一個小小的練習:
我們來利用前面投影片提過的 html tag 寫個簡單的網頁,自我介紹一下。
我們也來補充一點前面沒提的網頁觀念:
延伸資訊:CRUD (Create, Read, Update, Delete), Restful API (POST, GET, PUT, DELETE)
在接續關於爬蟲的學習之前,我們先來補上一些 requests 練習。
來爬一下 Sprout OJ 吧。
有沒有發現 Sprout OJ 上面的頁面一打開都是空白的,都要載入一下才會顯示?為什麼呢?
再來我們會解釋怎麼利用瀏覽器的功能來找出網頁是怎麼抓取資料的:
請跟著講師來檢視一下 Status 的網頁:
利用檢查元素的網路工具(Network欄)來看一下瀏覽器傳輸的 HTTP requests,選擇只顯示「XHR」。
Note: XHR = XMLHttpRequest (JavaScript)
可以發現瀏覽器在抓資料的時候,會發出一個 POST 的請求,網址是 https://neoj.sprout.tw/api/challenge/list
。
只要加上對應的參數資料(稱為「request payload」),就可以從 OJ 的網頁伺服器取得「最新 100 筆 submission 的資料」。
再來從瀏覽器的「Preview」可以看到,伺服器回傳的資料是一個 key-value(索引對應到值)的格式,類似於 dictionary。
藉由一些方式我們可以知道它其實就是 JSON,我們可以直接透過 python 讀取成 dictionary 跟 list 的組合:
試試把以下的資料從這個 status 的 dictionary 中抓出來,分別用 list 儲存
再來試試查看使用者的 profile(個人檔案),這個應該稍微輕鬆一點。
點開 Sprout OJ 右上角自己的名字,再來用前面的方法檢視一下瀏覽器傳輸的 HTTP requests。
把使用者的資料抓下來
跟前面的 status 一樣,可以順利把 profile 跟 stats 存入 python 的變數中。
試著用 python 印出跟網頁上一樣的資料吧:
Note: 可以參考 Requests Documentation
讓我們先問什麼是 BeautifulSoup?我們可以找到一些網路上的答案:
Beautiful Soup 是一個 Python 的函式庫模組,可以讓開發者僅須撰寫非常少量的程式碼,就可以快速解析網頁 HTML 碼,從中翠取出使用者有興趣的資料、去蕪存菁,降低網路爬蟲程式的開發門檻、加快程式撰寫速度。
from <a href="https://blog.gtwang.org/programming/python-beautiful-soup-module-scrape-web-pages-tutorial/">G.T.Wang's blog</a>
簡單來說就是解析網頁的一個 Python 程式嘛。
那我們為什麼要用 BeautifulSoup?這是一個絕妙的問題,我們先想想到底要怎麼解析一個網頁吧:
首先,透過剛剛教的 requests,我們已經可以用 python 把網站上的資料抓下來了(抓成字串)。
再來,我們上週學過正規表達式(Regular Expression),正規表達式的語法可以用來在很大的字串中篩選(匹配、搜尋、比對)一些我們要找的資料。
先來試試看:搭配前面的 requests
,讓我們試試抓一下維基百科上的內容,從下面的例子開始:
https://zh.wikipedia.org/wiki/Python
這部分不用照著做
在這裡先來使用 regex 來尋找 <h1>
標籤:
看起來效果不錯,但如果要是比較複雜的情況呢:
Regular Expression 無法區分各層標籤的樹狀結構,所以只要複雜一點的 HTML 它就沒辦法處理了。假設只是要找文章內文,我們得到這樣的資料就不是太有用。
這就是為什麼我們不太適合用 Regular Expression 尋找 HTML 這種結構化語言裡面的資料。因為這樣反而會使「爬到」很多沒有用的資料。
有時候你原本有 99 個問題,用了正規表達式之後會變成 100 個問題
我們來開始用 BeautifulSoup4 來「萃取」網頁中的 HTML 元素吧:
上面的 response.text
可以改成任何 HTML 字串,或是讀進來的 HTML 檔案。
來看看這個連結:http://140.114.67.95/test.html(可以用「檢視原始碼」!)
現在,讓我們用 BeautifulSoup 來抓抓看這個網頁:
再來執行:
soup.title
soup.h1
soup.p
soup.find('p')
(跟 soup.p
一樣)soup.h1.text
soup.p.text
find
and find_all
soup.find('p')
soup.find_all('p')
soup.find(id="good")
先用 requests 抓一下維基的頁面:https://zh.wikipedia.org/wiki/Python
重複上面的各個 soup
操作:
soup.title
soup.h1
soup.p
soup.h1.text
soup.p.text
看起來不錯,用簡單的程式碼就能讓它順利幫我們找出文章第一個段落的所有內容了,比起 Regular Expression 來說有效率很多。
接下來試試看用 BeautifulSoup 來處理複雜一點的功能。
這樣就可以詳盡地找到網頁裡各個內文的文字了。
再來我們試著把維基百科頁面的目錄抓下來:
soup.find(id="toc")
利用一些組合,就能把完整的目錄用 list 儲存起來:
在 HTML 之中,元素層層包著其他元素,除了最外面和最裡面一層之外,每個元素都會有「父元素」和「子元素」。我們稱為 parent 跟 child。
讓我們再回到 test.html
:
soup.find('ul')
soup.find('ul').parent
list(soup.find('ul').children)
(包含文字及 tag 的全部子元素)soup.find('ul').find_all()
(只找出子元素裡的 tag)soup.find('ul').find_next_sibling()
soup.find('ul').find_previous_sibling()
soup.find('ul').find_next_siblings()
soup.find('ul').find_previous_siblings()
Note: 使用 find 開頭的函式呼叫就一定是找完整的 html 元素(tag),但如果使用 .children
或 .next_sibling
可能會顯示出元素和元素之間的換行字元(在 html 裡面可忽略),不能確保它一定會找到上一個 tag:
BeautifulSoup 也可以直接用 CSS Selector 篩選,我們就可以直接從網頁上複製 css selector,再讓 soup 根據 selector 去抓取對應的元素。
soup.select('#good')
soup.find(id='good')
一樣)soup.select('body > ul > li:nth-child(3))')[0]
延伸閱讀:
BeautifulSoup4 Documentation
Note: 請參考北區簡報
經過了前面的這些練習,你已經知道怎麼使用 BeautifulSoup 的爬蟲進行很多進階的 HTML 操作了。
是時候來看看作業說明囉!
再來我們可以使用每題的筆數來排序:
也可以寫出統計送出者的版本:
試試往下捲會怎樣?修改上面的篩選器會怎樣?
offset: 1xxxxx
欄位的 POST 請求,修改篩選器則是會更改 filter: {}
欄位連結:IMDB top 100
name_list
genre_list