Makzan

我管理世界職業技能競賽之網站技術項目、舉辦本地設計與開發賽事、開課分享技術心得。一個用網頁來表達自己的作家。

Python|使用 Pandas 的 read_html 讀取網頁上的表格內容

發布於
修訂於
Python 的一大常見用途是爬取網站內容,我們會因應不同網站的構造使用不同的爬蟲策略。而當我們需要揭取的是表格數據資料時,便可以使用 Pandas 內建的 read_html 來達成。

Python 的一大常見用途是爬取網站內容,我們會因應不同網站的構造使用不同的爬蟲策略。而當我們需要揭取的是表格數據資料時,便可以使用 Pandas 內建的 read_html 來達成。

爬蟲的不同策略

先宏觀地看看在網絡上取得數據有甚麼不同的策略。

從容易到麻煩排序:

  1. API 結構數據存取
  2. 數據表格 <table> 存取 ← 今期的內容
  3. BeautifulSoup 網站內容抽取
  4. 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 個視頻教學課程。

如果我的文章有價值,請左下角 👍🏻按讚支持,或訂閱贊助我持續創作及分享。

麥誠 Makzan


喜歡我的文章嗎?
別忘了給點支持與讚賞,讓我知道創作的路上有你陪伴。

CC BY-NC-ND 2.0 版權聲明

教學文系列

4

看不過癮?

一鍵登入,即可加入全球最優質中文創作社區