Py / Python網頁資料擷取與分析班-筆記 8/23 番外-幾種解析xml的方式

Py / Python網頁資料擷取與分析班-筆記 8/23使用 xml.etree.ElementTree 模組來解析xml

透過 requests模組取得資料內容,並將檔案儲存在本機

現在嘗試使用不用在本機存檔的方式來處理

拜估狗大神之後得到幾種方式

1.xmltodict
2.BeautifulSoup
3.xml.etree.ElementTree fromstring()
4.io StringIO()

以政府公開資訊網的勞工體格及健康檢查認可醫療機構來實作

由於資料來源網站會檢測SSL連線,所以在requests 必須增加一個參數  verify=0


1.xmltodict

可以將requests模組取得的資料內容轉換成字典形式,所以可以用字典的方法來取得資料

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import requests
html = requests.get("https://apiservice.mol.gov.tw/OdService/download/A17000000J-020057-rpR",verify=0)

import xmltodict
data = xmltodict.parse(html.text)
# print(data) #{'ass_hosp': {'認可醫療機構': [{'縣市別': '新北市', '醫療機構名稱': '國立臺灣大學醫學院附設醫院金山分院', '醫療機構地址': '新北市金山區五湖里玉爐路7號', '勞工健檢聯絡人': '蔡語恩', '連絡電話': '02-2498-9898#7500', '認可類別及有效期限': '一般健檢(1100101-1121231)', '備註': None},
# print(data["ass_hosp"]["認可醫療機構"]) #[{'縣市別': '新北市', '醫療機構名稱': '國立臺灣大學醫學院附設醫院金山分院', '醫療機構地址': '新北市金山區五湖里玉爐路7號', '勞工健檢聯絡人': '蔡語恩', '連絡電話': '02-2498-9898#7500', '認可類別及有效期限': '一般健檢(1100101-1121231)', '備註': None}, {'縣市別': '新北市', '醫療機構名稱': '中英醫療社團法人中英醫院', '醫療機構地址': '新北市板橋區文化路1段196號1-4樓', '勞工健檢聯絡人': '陳惠菁', '連絡電話': '02-22563584', '認可類別及有效期限': '一般健檢(1080101-1111231)、巡迴一般健檢(1080101-1111231)', '備註': '1. 依勞動部109年4月15日勞職授字第10902012992號函,為配合衛生福利部「109年度醫院評鑑及教學醫院評鑑作業順延1年」,辦理勞工巡迴體格及健康檢查類別之醫療機構原認可效期同時配合展延至110年12月31日。2. 依勞動部110年6月23日勞職授字第1100203157號函,因應嚴重特殊傳染性肺炎(COVID-19)疫情,認可效期展延1年。'},
# print(len(data["ass_hosp"]["認可醫療機構"])) #393
# print(data["ass_hosp"]["認可醫療機構"][392]["縣市別"]) #新北市
dataFrame=[]
cols=["縣市別","醫療機構名稱","醫療機構地址"]
index=[]
for i in range(0,len(data["ass_hosp"]["認可醫療機構"])):
    d1=data["ass_hosp"]["認可醫療機構"][i]["縣市別"]
    d2=data["ass_hosp"]["認可醫療機構"][i]["醫療機構名稱"]
    d3=data["ass_hosp"]["認可醫療機構"][i]["醫療機構地址"]
    # print(d1)
    # print(d2)
    # print(d3)
    dataFrame.append([d1,d2,d3])
    print(dataFrame)
    index.append(i+1)

import pandas as pd
df = pd.DataFrame(dataFrame,index,cols)
print(df)
df.to_excel("OdService.xlsx")

 

2.BeautifulSoup

BeautifulSoup除了可以解析Html之外,也可以解析xml

解析之後,可以用find_all() 或 find() 等方法來取得資料

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import requests
from bs4 import BeautifulSoup
html = requests.get("https://apiservice.mol.gov.tw/OdService/download/A17000000J-020057-rpR",verify=0)
sp = BeautifulSoup(html.text, "xml")
# print(sp.find_all("認可醫療機構")[1].text.split("\n"))

def not_empty(s):
    return s and s.strip()

data=[]
cols=["縣市別","醫療機構名稱","醫療機構地址"]
index=[]
for i in range(0,len(sp.find_all("認可醫療機構"))):
    #print(i)
    dOne = sp.find_all("認可醫療機構")[i].text.split("\n")
    #print(dOne)
    dOne = filter(not_empty,dOne)
    listOne=list(dOne)
    # print(listOne)
    # print(listOne[0])
    d1=listOne[0]
    d2=listOne[1]
    d3=listOne[2]
    data.append([d1,d2,d3])
    index.append(i+1)

import pandas as pd
df = pd.DataFrame(data,index,cols)
print(df)
df.to_excel("OdService2.xlsx")

 

不過,透過BeautifulSoup解析xml,資料前後都會有空格

新北市
中英醫療社團法人中英醫院
新北市板橋區文化路1段196號1-4樓
陳惠菁
02-22563584
一般健檢(1080101-1111231)、巡迴一般健檢(1080101-1111231)
1. 依勞動部109年4月15日勞職授字第10902012992號函,為配合衛生福利部「109年度醫院評鑑及教學醫院評鑑作業順延1年」,辦理勞工巡迴體格及健康檢查類別之醫療機構原認可效期同時配合展延至110年12月31日。2. 依勞動部110年6月23日勞職授字第1100203157號函,因應嚴重特殊傳染性肺炎(COVID-19)疫情,認可效期展延1年。

因此透過 split(“\n”)來分割資料,陣列內的前後都會有空白元素

所以參考網路上的方式,自訂一個過濾函式來處理

def not_empty(s):
    return s and s.strip()

filter(not_empty,dOne)

3.xml.etree.ElementTree fromstring()

使用 xml.etree.ElementTree的fromstring()方法來解析requests模組取得的資料內容

但是取得資料的方式跟parse()不一樣

主要是fromstring()沒有getroot(),要使用tag屬性來取得標籤名稱,而標籤內容則是使用text屬性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import requests
html = requests.get("https://apiservice.mol.gov.tw/OdService/download/A17000000J-020057-rpR",verify=0)

import xml.etree.ElementTree as ET
root = ET.fromstring(html.text)
print(root.tag)           #ass_hosp
print(len(root))          #393
print(root[0].tag)        #認可醫療機構
print(root[0][0].tag)     #縣市別
print(root[0][0].text)    #新北市

dataFrame=[]
cols=["縣市別","醫療機構名稱","醫療機構地址"]
index=[]
for i in range(0,len(root)):
    d1=root[i][0].text
    d2=root[i][1].text
    d3=root[i][2].text
    # print(d1)
    # print(d2)
    # print(d3)
    dataFrame.append([d1,d2,d3])
    # print(dataFrame)
    index.append(i+1)

import pandas as pd
df = pd.DataFrame(dataFrame,index,cols)
print(df)
df.to_excel("OdService_0823-24.xlsx")

 

4.io StringIO()

將requests模組取得的資料內容轉換成 text/io

也就是將資料寫入記憶體,但是可以跟文件一樣地操作

因此,也可以使用xml.etree.ElementTree的parse()來解析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import requests,io
html = requests.get("https://apiservice.mol.gov.tw/OdService/download/A17000000J-020057-rpR",verify=0)
# print(type(html.text))
f = io.StringIO(html.text)
# print(f.read())

import xml.etree.ElementTree as ET
obj = ET.parse(f)
print(type(obj))                   #ElementTree Objects
print(type(obj.getroot()))         #Element Objects
print(obj.getroot().tag)           #ass_hosp
print(obj.getroot()[0].tag)        #認可醫療機構
print(obj.getroot()[0][0].tag)     #縣市別
print(obj.getroot()[0][0].text)    #新北市

worker_root = obj.getroot()
# print(type(obj)) #ElementTree Objects

s="縣市別,醫療機構名稱,醫療機構地址\n"
for worker_row in worker_root:
    s+="{},{},{}\n".format(worker_row[0].text,worker_row[1].text,worker_row[2].text)
#print(s)
#轉存為csv
f2=open("worker_0823-25.csv","w",encoding="UTF-8")
f2.write(s)
f2.close()

cols=["縣市別","醫療機構名稱","醫療機構地址"]
data=[]
index=[]

# for youbike_row in youbike_root:   
#     data.append([youbike_row[0].text,youbike_row[1].text,youbike_row[2].text])
#     index.append(youbike_row[0].text)
#print(data)

for i in range(0,len(worker_root)):   
    data.append([worker_root[i][0].text,worker_root[i][1].text,worker_root[i][2].text])
    index.append(i+1)

import pandas as pd
df = pd.DataFrame(data,index,cols)
#print(df)
df.to_excel("worker_0823-25.xlsx")

 

參考資料

在 Python 中將 XML 轉換為字典 | D棧 – Delft Stack

xml.etree.ElementTree — ElementTree XML API — Python 3.10.6 說明文件

XML解析 — The Hitchhiker’s Guide to Python

io — Core tools for working with streams — Python 3.10.6 documentation