Python|使用 Pandas 讀取澳門公職人員開考列表及進行文字處理與篩選

Makzan
·
·
IPFS
·
此篇,我們借助澳門公職人員開考列表,示範了從網絡中取得內容後,如何通過 split 字串處理抽出內容、通過 apply 批量套用函數、使用 groupby 計算基統計數值、及使用 == 篩選數據。

大家好,來繼續 Python Pandas 數據處理系列介紹。

我在 使用 Pandas 的 read_html 讀取網頁上的表格內容 中介紹了不同的表格讀取例子,但讀取後未進一步探討如何處理。在這篇中,我們以澳門公職人員開考列表之網頁作為例子,從讀取至分柝文字及搜尋篩選開考資訊。

在這個例子中,我們會學到 Python 及 Pandas 的以下幾方面技巧:

  • 當 read_html 不成功時,使用 requests 配搭取得網頁內的 <table> 元素內容
  • 使用 Split 將字串切開
  • 使用 apply 加 split 將一行內容拆成多欄,方便搜尋篩選
  • Python 列表 Slicing 取得頭 N 項數據

目標網站分析

目標網站:https://concurso-uni.safp.gov.mo/

https://concurso-uni.safp.gov.mo/

在上述網站中,中間有三個開考表格,我示範的目標是第三個:「專業或職務能力評估開考(2021/7/1後開展的)」。

中間三個是 <table> 表格,即我們期望使用 read_html 取得網站後,最少有三個 DataFrame 表格,可能會因為網站其他地方也使用了 <table> 而有更多,但最少應為 3 個。

使用 requests 配搭取得網頁內的 <table> 元素內容

在使用 Pandas 的 read_html 讀取時,有些情況會出現表格就在那𥚃,但使用 pandas read_html 時會報錯,說找不到 <table> 元素,此時大約有以下可能,可能性由高至低排列:

  1. 網站內容目測是表格,但背後的 HTML 代碼其實不是表格。
  2. 網站的 <table> 數據是 JavaScript 動態加載的,來源是 JSON。
  3. 網站的 <table> 數據是 JavaScript 動態載入的,而來源亦是 <table> HTML 格式。
  4. 網站在 pandas 存取時會屏蔽,不返回數據。

這個網站就是原因 4,當我們嘗試用以下代碼時,read_html 會報錯說找不到 <table> 元素。因為 Pandas 的存取會內置使用 urllib3,所以網站會有可能針對這個字眼來的客戶端進行屏蔽。

import pandas as pd
url = "https://concurso-uni.safp.gov.mo/"

tables = pd.read_html(url) # 錯誤

解決方案是使用其他存取方法取得此版網頁的 HTML,再把 HTML 交給 read_html 分析成 DataFrame 數據。

而其他方法,包括使用 BeautifulSoup 或 Selenium。前者是下載網站的 HTML 內容並分析生成元素結構樹,然後我們可以通過 CSS 選取器按條件取得當中的內容,是通用的網站爬蟲方式。而後者,Selenium 是控制瀏覽器的自動化測試工具,可以用編程控制瀏覽器載入網頁,包括用 JavaScript 動態生成的網頁,甚至自動填表或進行介面操作等。

由於 BeautifulSoup 是下載內容的機制而 Selenium 是啟動瀏覽器自動化機制,兩者運行速度相差甚遠,所以我們會優先使用 BeautifulSoup 下載 HTML 內容,若失敗時,或必須自動化操作始能存取目標數據時,才會使用 Selenium。

以下代碼,我們嘗試使用 requests 讀取網站,再將讀取得的 HTML 源代碼直接交予 read_html,印出找到的表格數量,得到 4 個表格。

import pandas as pd
import requests

url = "https://concurso-uni.safp.gov.mo/"
res = requests.get(url)
tables = pd.read_html(res.text)
print(len(tables))

取得的 4 個數據表格中,當中第三個,就是我們需要的。所以 df = tables[3] 然後列印出來望望,得出以下數據。

設定欄名稱

Pandas 的操作,多以欄作為思考。我們會檢視數據的欄位,只保留需要的,反為這些原始數據的欄位重新命名,以適合我們一會使用。

由於這個表格只有兩欄,我們可以直接用 df.columns 設定。例如:

df.columns = ["日期", "內容"]

設定後,我們可以通過 df["內容"] 來存取整欄的內容。每欄數據的類型為 Pandas 的 Series (系列)類型。

例如以下為 df["內容"] 的結果,可以列出每行的內容。

分柝文字內容,得出部門、範疇、開考職階

從圖中可以看出,這些內容有待清理。這些內容有一定模式,就是一開始是部門名稱,伴隨一個空格,緊接是開考範野,再伴隨另一個空格,緊接是開考職階。我們可以利用 split,對每一行的內容按空格切開,得出列表。

例如,假若有這句字串「交通事務局 機電工程範疇 第一職階二等高級技術員」

example = "交通事務局 機電工程範疇 第一職階二等高級技術員"
example.split(" ")

而 Python 中,我們可以同時將 N 個變量名稱對應 N 個列表項目,會一一儲存對應的項目數據,我們稱這個動作為 unpack。例如,以下 split 範例,我們可以分別將三個值放到三個變量中去。

example = "交通事務局 機電工程範疇 第一職階二等高級技術員"
部門, 範疇, 職階 = example.split(" ")

這樣就可以分別將列表的三個值分配到三個獨立的變量中。要注意的是使用這招式,必須前後數量匹配才可。如果要處理的文字可能有額外空格,我們可以加上 [:3] 來確保只取頭三個值。

example.split(" ")[:3] 

關於 [:3],這是列表切片,詳情可以參考:Python 使用列表切片 List Slicing 取得列表的範圍數值

整欄處理的 apply 函數

上述就是 split 的基本用法。但我們不會這樣逐列逐列處理。於 Pandas 中,我們是以欄位思考的,可以用 apply 來為整欄的數據套用上述的 split 函數。

df["內容"].apply(lambda x: x.split(" ")[:3])

關於 lambda 的使用,可以參考 @YCJHUO在 Pandas 中,如何使用 lambda 以及客制化 boolean 值

以下為運行結果,可以看到每行內容已成功抽取為三個獨立的值。

但我們需要的是生成三欄獨立的欄,所以基於上述代碼,需要再套用 apply(pd.Series),其原理是當 Pandas 生成欄(Series)時,會自動將多重列表、字典(Dict)等拆分成欄。所以更新後的代碼如下:

df["內容"].apply(lambda x: x.split(" ")[:3]).apply(pd.Series)

從結果可見,今次我們成功從每行的一串文字抽取出三項資訊,並分別得出三欄。

將此三欄加到原 DataFrame 數據表上,數據清理階段便暫告完成。

df[["部門", "範疇", "職階"]] = df["內容"].apply(lambda x: x.split(" ")[:3]).apply(pd.Series)
數據清理完成

數據使用

於這份數據中,我們可以做一些基本查詢,例如看一看數據中共涉及哪些部門及哪些範疇。

set(df["部門"])
列出涉及部門
set(df["範疇"])
列出涉及範疇

我們亦可以通過 group by,按範疇分類,數數有多少筆記錄,或倒轉按部門分類,數數每個部門有多少筆記錄。

按範疇分類:

df.groupby(by="範疇")["內容"].agg(len)

按部門分類:

df.groupby(by="部門")["內容"].agg(len)

groupby 是將某一欄的數據變成索引,而關連此欄每一項數據的,就按我們提供的函數整合。常見的整合有 .agg(np.sum) 加總、.agg(np.mean) 取平均值、及上述代碼使用的 .agg(len) 數數目。

使用 groupby 後,可以按所屬數值的數據進行加總、數數、取平均值等運算

篩算特定數據

我們可以將特定值作為篩選條件,通過某一欄位與值的比較,我們會得出一個 True/False 的 Boolean 布林值序列(Series),再將此序列放回 DataFrame,就以只顯示 True 的那列,達至篩選效果。

mask = df["範疇"]=="電機工程範疇"
df[mask]
df[mask] 的原理

亦可以隨時將覺得有用的 DataFrame 數據輸出成 Excel:

df[mask].to_excel("最近電機工程範疇開考列表.xlsx")

總結

此篇,我們借助澳門公職人員開考列表,示範了從網絡中取得內容後,如何通過 split 字串處理抽出內容、通過 apply 批量套用函數、使用 groupby 計算基統計數值、及使用 == 篩選數據。

今日的例子以文字處理為主,之後我們再以其他例子探討 Pandas 的其他應用,如數值處理、移動平均線運算、文字的正規表達成抽出等等。

Pandas 系列文章

  1. 使用 Pandas 的 read_html 讀取網頁上的表格內容
  2. 本文:使用 Pandas 讀取澳門公職人員開考列表及進行文字處理與篩選


— 麥誠 Makzan,2022-01-14。


我是麥誠軒(Makzan),除了正職外,平常我要麼辦本地賽與辦世界賽,要麼任教編程與網站開發的在職培訓。現正轉型將面授培訓內容寫成電子書、網上教材等,至今撰寫了 7 本書, 2 個視頻教學課程。

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

麥誠 Makzan


CC BY-NC-ND 2.0

Like my work? Don't forget to support and clap, let me know that you are with me on the road of creation. Keep this enthusiasm together!

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

期望與放下

甚麼是世界職業技能競賽

【最後兩天】買《所謂「我不投資」,就是 all in 在法定貨幣》親身體驗擁有 NFT