Python|使用 Pandas 的 read_html 讀取網頁上的表格內容
Python 的一大常見用途是爬取網站內容,我們會因應不同網站的構造使用不同的爬蟲策略。而當我們需要揭取的是表格數據資料時,便可以使用 Pandas 內建的 read_html
來達成。
爬蟲的不同策略
先宏觀地看看在網絡上取得數據有甚麼不同的策略。
從容易到麻煩排序:
- API 結構數據存取
- 數據表格
<table>
存取 ← 今期的內容 - BeautifulSoup 網站內容抽取
- Selenium 自動化操控瀏覽器存取
今天的例子
我們今天會用到各種數據表格例子,包括:
- 澳門特區政府公眾假期列表
- 讀取澳門中原地產
- 澳門中銀兌港元匯率
- 台灣各城市天氣預報
- 台灣銀行匯率
運行例子
數據處理的程式例子,我傾向使用 Jupyter Notebook,因為可以從取得數據開始,再逐步逐步實驗手上的數據及得出結果。最後真的要自動化定期實行時,才將代碼匯聚成單個 .py
檔案,甚至使用 PyInstaller
或 auto-py-to-exe
生成 .exe
執行檔。
所以,以下例子截圖及源碼使用 Google 的 Colab 工具,是一個於 Google Drive 上運行的 Google 版的 Jupyter Notebook。
可以通過以下網址運行:
https://colab.research.google.com/
或曾經建立過後,可以直接於 Google Drive 中建立 Colab Jupyter Notebook 檔案。
read_html 基本步
read_html 的基本用法是準備好網址 url
後,使用 pd.read_html(url)
讀取,如果從讓網頁中有找到表格數據,會得出一個列表,列表中分別是讓網頁的所有數據表格,而如果網頁中找不到任何表格,則會出現 ValueError
,我們可以通過 try
except
來防止錯誤導致程式碼中斷運行。
import pandas as pd url = "https://example.com" try: tables = pd.read_html(url) except ValueError: print("No tables found.")
當找到表格得出列表後,我們便可以進一步取得表格的 DataFrame 格式。DataFrame 是 Pandas 的欄列數據,十分適合進行數據處理之用。
如果目標網站只有一個表格,我們可以使用 tables[0] 來取得。如果目標網頁上有多個表格,則需要數一數你想取得的是哪一個表格了。
例子:讀取澳門今年的公眾假期列表
澳門政府網站有列出本年的公眾假期列表: https://www.gov.mo/zh-hant/public-holidays/year-2022/
這頁網頁有三個表格,分別是公眾假期、公務人員准豁免上班日期、公務人員補假。作為第一個例子,我們先讀取第一個表格,公眾假前列表。
import pandas as pd url = "https://www.gov.mo/zh-hant/public-holidays/year-2022/" tables = pd.read_html(url) len(tables) df = tables[0] df
注:上述最後一行,於 Jupyter Notebook 中會將最後一行的值打印出來,故得出以下截圖結果。而若於其他環境運行,例如 .py
或 .exe
等,則需要使用 print(df)
來打印。而在互動筆記本環境下直接打 df 來觀察 DataFrame 的值,會有以下的美觀表格輸出,便利我們查看數據。
源代碼:https://colab.research.google.com/drive/1nYn2av_n3d50k6RnZx2qDm36U1p1WwCD?usp=sharing
例子二:讀取澳門中原地產
澳門中原地產有成交數據記錄,網址為:
https://mo.centanet.com/Transaction
套用上述的代碼,而只需要替換網址,我們便可以得出以下數據。
import pandas as pd url = "https://mo.centanet.com/Transaction" tables = pd.read_html(url) tables[0]
源代碼:https://colab.research.google.com/drive/1yxjvfuKSyFu_gmpakZvaIeaBjNrA2B2z?usp=sharing
有沒有感受到,撰寫爬蟲程式,掌握了基本套路後,每次只需要替換網址便萬變不離其宗,相類似的網站,用相類似的技術及代碼而取得。都是 <table>
的,只要替換了網址,結果便已經千變萬化。所以,找到網址及找對網址也是非常重要喔!
例子三:澳門中銀兌港元匯率
再來看看一個澳門中銀的例子,從澳門中銀網站右側,可以連結至外幣兌港元牌價網頁,又或者可以通過以下網址跳轉。
https://www.bankofchina.com/mo/fimarkets/fm1/200912/t20091219_933601.html
但當我們嘗試取得當中的數據表格時,會發現 Pandas 出錯,報找不到任何表格。但表格明明就在那𥚃呢。
這時有數個可能性,包括網站對 Pandas 來的請求屏蔽了、又或者目測是表格,但其實代碼不是 <table>
來的。又或者這個表格是動態使用 JavaScript 載入的等。而這𥚃的原因,是因為這個表格是包在一個 <iframe>
網頁框中的,所以出面那層網頁沒有這個表格的 <table>
HTML 源代碼。
我們可以檢查是不是有一個網頁框,可以在 Firefox 瀏覽器中,右鍵查看目標內容是否一個 iframe 框,若有「本頁框」則代表這段內容在<iframe>
網頁框中 。結果果然是,我們可以選擇只顯示本頁框來得出真正的網址。
而真正的網址位於:https://www.bankofchina.com/mo/ftpdata/2009p1.htm
現在當我們使用 read_html
時就會成功了:
import pandas as pd url = "https://www.bankofchina.com/mo/ftpdata/2009p1.htm" tables = pd.read_html(url) tables[0]
源代碼:https://colab.research.google.com/drive/1_uNTSJjoyEKrFimQsCwvNTWKczVeV5wt?usp=sharing
從此例子可見,想辦法找對網址也是重要的一步。
想辦法找對網址也是重要的一步。
例子四:台灣各城市天氣預報
在嘗試取得台灣各城市天氣預報時,我們會預到另一個明明見到但找不到 <table>
表格的情況,其原因是因為這個表格是使用網頁上的動態代碼 JavaScript 動態取得的,而當我們使用 read_html 下載原網頁時,由於未有如瀏覽器般執行 JavaScript 及動態載入此數據,所以 read_html 找不到數據表格。
我們若在表格右鍵,又看不到「本頁框」,即是這個表格不是被 <iframe>
包著。那麼,這時可以按 F12 打開開發者工具,選擇「網絡」,重新載入網頁後,可以看到這個網頁的所有檔案載入,包括圖片檔案等。
我們想查看的是動態載入的數據。我們可以只篩選 "XHR" 類型的數據,XHR 是 JavaScript 動態載入的數據,而這個列表中,要數 ALL_Week.html 最像樣,按下去,再選擇 "Response" 返回值,可以看到果然就是我們想拿取的數據了。
此時,我們可以在檔頭 Headers 中找到這個 ALL_Week.html 的網址位置:
沒錯,就是以下這條網址:
https://www.cwb.gov.tw/V8/C/W/County/MOD/wf7dayNC_NCSEI/ALL_Week.html?v=
但讀取後,會得到亂碼呢。
這時,可以在 read_html
時加入 encoding
參數,設定為 utf8
,便可以成功獲得數據並解碼正確。
源代碼:https://colab.research.google.com/drive/1SyYIVMAwO00TlB0G3QabQfgie2tjrEYP?usp=sharing
小結:有網址才有下一步
上述花了這麼大的篇幅講解不同情況下獲取數據表格的方法,因為沒有網址就連門也找不到,更莫說進一步下載及處理數據了。而以下例子,則是獲得數據後,開展我們的數據處理簡化之旅。
數據整理例子:讀取臺灣銀行匯率表
想盡辦法取得網址及成功使用 read_html
取得數據表格後,才是正式的開始,剛剛的只能稱為序章。接下來是數據預處理,包括清理及簡化。然後就要看如何使用數據及最後輸出。
這𥚃使用臺灣銀行匯率表作為例子。其網址為: https://rate.bot.com.tw/xrt?Lang=zh-TW
這個網站取得數據相對直觀,直接 read_html
即可,但這個數據個表格最難的地方是兩層的欄標題及重覆的數據內容。我們可以通過一系列的 DataFrame 操作來簡化我們的數據,例如在以下例子中,假設我只想取得「銀行賣出價」,該如何一步步達成:
首先和所有 read_html 的代碼類同,按網址取得數據表格列表,我們取第一個表格。
import pandas as pd url = "https://rate.bot.com.tw/xrt?Lang=zh-TW" tables = pd.read_html(url) df = tables[0] df
清理欄位,只保留需要的
可以看到上方欄標題頗凌亂,且有兩行,屬於多重 Index,從 df.columns
可以了解每一欄的標題。
再仔細看,這個表明明就幾筆數據,為甚麼在 Pandas 中讀出來有那麼多欄?原因是這個表格的 HTML 源代碼其實是分為桌面版及手機版兩組數據的,而這兩組分別於桌面或手機時顯示或隱藏,所以在網站上是看不多,但實際上表格內的確有很多欄。再者,read_html
的這些欄目重覆之餘,也對不上數據,欄與數據有錯誤位移了一格的情況出現。
要清理這些數據,假設我們只需要「本行賣出」這一欄,即 DataFrame 內的第三欄。我們可以以數數的方法,只保留第一欄及第三欄。
通過 df.columns[0]
可以取得第一欄,df.columns[2]
取得第三欄。而放到一起成為列表,再使用 df
存取,即可以只取這兩欄出來:
df[[df.columns[0], df.columns[2]]]
再覆蓋原本的 df,就可以完成清理,並只剩下我們要的資訊欄目。
清理幣別數值
可以見到幣別中的內容重覆了,我們可以通過 apply 來批量處理,例如假設我們只需要保留幣別的中文,那麼我們可以從開括號處用 split
切開,取最頭的值,並使用 strip
來刪除前後倘有的空白:
df["幣別"] = df["幣別"].apply(lambda x: x.split("(")[0].strip())
lambda
是甚麼?是沒有名稱的一行函數。通常用於一次性轉換過程。使用方法是 lambda x: x
冒號前的 x
是參數,冒號後是一行自動返回值的代碼,上述 lambda 可以理解為:
def 沒有名字(x): return x.split("(")[0].strip()
將幣別設定為 Index
幣別將會是我們經常用作查詢的,所以可以設定為 Index,方便使用。
df.set_index("幣別", inplace=True)
設定後,我們若想取得哪一個幣別的匯價,便可以使用 df.loc["港幣"]
等來取得。
源碼及未處理的部份
讀取臺灣銀行匯率表源代碼:https://colab.research.google.com/drive/19mESLtm8GUCL4DNEUnz220ZukJwKuAE6?usp=sharing
至此,我們便成功讀取臺灣銀行匯率表,但我們還有數據類型未處理,由於有一劃線出現,導致整欄數字被辨識為字串。若果只需要取值而不用計算,其實還好,稍後再寫寫 Pandas 如何做運算。
方法不只一個
在網站上爬取資料,方法往往不只一個。例如上述的臺灣銀行牌價匯率表,其最底有提供 CSV 下載,右鍵可认複製連結,得到:https://rate.bot.com.tw/xrt/flcsv/0/day
由於這是 CSV 檔案,所以可以直接使用 pandas 的 read_csv
將這個網上 CSV 格式下載成 DataFrame。
import pandas as pd url = "https://rate.bot.com.tw/xrt/flcsv/0/day" df = pd.read_csv(url) df
— 麥誠 Makzan,2022-01-12。
我是麥誠軒(Makzan),除了正職外,平常我要麼辦本地賽與辦世界賽,要麼任教編程與網站開發的在職培訓。現正轉型將面授培訓內容寫成電子書、網上教材等,至今撰寫了 7 本書, 2 個視頻教學課程。
如果我的文章有價值,請左下角 👍🏻按讚支持,或訂閱贊助我持續創作及分享。
喜欢我的作品吗?别忘了给予支持与赞赏,让我知道在创作的路上有你陪伴,一起延续这份热忱!
- 来自作者
- 相关推荐