如何用 python 解析三層結構 XML?

python


lmxl/beautifulsoup 以及其它各種xml解析庫/包


4/11更新:突然發現可以上傳圖片了,傳幾張圖片,然後把代碼的語言說明清楚了,好看一點。

最近正在寫這個方面的東西,算是自己造輪子玩,隨便說幾句,說的不對請輕噴。

Python開箱既用的特性為我們提供了豐富的庫來解析XML文檔,不需要第三方庫,標準庫就有很多,我們這裡用xml庫進行解析。

通過如下的語句調用xml庫:

import xml.etree.ElementTree as ET
import json
#通過json庫將解析之後的字典dump成為JSON

在這裡我將題主的問題複雜化一點,要求解析一個XML文檔,並按照一定的格式返回相應的JSON數據(相信做過微信公眾號開發的同學一定知道為什麼會有這樣的需求)。

我有如下的思路:

  • 首先,解析過後的XML文檔可以看作一個樹,不過是多叉的,而且層數不一定。
  • 於是,我這裡採用了深度優先的方式來對解析過的XML樹進行遍歷。
  • 遍歷有兩種方式,遞歸和循環,在Python中,因為性能的原因,選擇循環可能在解析比較大的文檔時有一定優勢。

解析樹的過程可以這樣來想:

  • 進行一個循環,從根節點向樹的葉子節點進發,當發現了下一層節點均是葉子節點時,我遍歷該節點的子節點列表,對他們的數據進行獲取,合併,並刪除這些節點。之後,返回上一層節點,將已經合併過的這個節點刪除,繼續循環。

那進行這個循環的條件是什麼呢?當樹的枝幹不斷地被砍去之後,最終會在一個循環,你的當前節點會回到根節點,而最後一次合併完成之後,根節點的子節點也全部消失,所以循環條件可以為:根節點有子節點。

那麼如何記錄上一層節點呢?可以用一個列表,來保存當前的子節點的路徑,每次的父節點便是該列表的最後一個。

於是文字流程圖可以這樣設計:

while循環條件 : 根節點有子節點
當前結點 -&> 獲取當前結點子節點列表 -&>
遍歷當前結點的每一個子節點,查詢遍歷節點是否有子節點 -&>
有 : 記錄當前節點下標,遍歷節點作為當前節點,退出循環;
無 : 則該節點的所有子節點均為葉子節點 -&> 合併當前結點的所有子節點到當前節點 -&>
父節點取出一個作為當前節點
繼續循環

我畫幾張圖說一下這個過程。

這個是最初的那個樹(儘管實際情況比個複雜,但是對演算法本身的適用性沒有影響)

  1. 首先從根結點A出發,假如從左手的第一個子節點開始,也就是B。
  2. 我們對B的子節點遍歷(也就是D,E)於是發現D,E並不都沒有子節點,也就是說D,E並不都是葉子節點
  3. 於是我們將B存到以前的記錄里,並將當前節點設置為E
  4. 對E進行(2)的步驟,我們發現E的兩個子節點(也就是H,I)都沒有子節點,也就是都是葉子節點了
  5. 於是我們進行第一次合併,合併H,I的數據,並刪除H,I兩個子節點

之後我們從歷史中取出父節點節點,並設置為當前節點(也就是B)

然後進行了第二次合併:

於是當前節點設置為了A,之後進行第三次合併:

進行了最後一次合併之後,只剩下了一個孤零零的A:

於是根節點沒有了子節點,循環結束,返回結果。

之後就直接上代碼:

我這裡採用一個類的方式來寫。

class ParserTree(object):
"""
用來解析XML文件,並將其以字典形式返回
@Author : guiqiqi187@gmail.com
@Date : 2017-4-9
"""
def __init__(self, XMLfileName, key = "Key", value = "Value", attr = "Attr"):
"""
類構造函數

tree : 當前的XML樹,
rootNode : 根節點,
self.currentNode : 當前節點 (初始為根節點),
self.parentNode : 當前結點的父節點列表,
self.childNode : 當前結點的子節點,
keyName, valueName, attrName : 合併節點時需要使用的信息,
result : 需要返回的結果集
"""
tree = ET.parse(XMLfileName)
rootNode = self.rootNode = tree.getroot()
self.currentNode = rootNode
self.rootNodeTag = rootNode.tag
self.key = key
self.value = value
self.attr = attr
self.childNode = list()
self.parentNode = list()
self.result = dict()

def go(self):
while self.rootNode.getchildren():
self.childNode = self.currentNode.getchildren()
childNodePointer = 0
haveChild = False
for node in self.childNode:
if len(node.getchildren()) != 0:
haveChild = True
break
childNodePointer += 1
if haveChild:
self.parentNode.append(self.currentNode)
self.currentNode = self.childNode[childNodePointer]
else:
lastNode = self.currentNode
self.mergeNode()
self.currentNode = self.parentNode.pop()
self.currentNode.remove(lastNode)
continue
returnDict = {self.rootNodeTag : self.result}
return returnDict
def mergeNode(self):
pass

mergeNode是一個用來合併節點的函數,大家可以根據需要自己寫一個。

構造函數中key,value,attr,分別指合併時候獲取到的標籤名,值名,屬性名稱。

下面是一個例子,大家不想看的就可以直接跳過了:

&
&
&
&
&TEXT&
&

&
&FLOAT&
&

&

&
&
&TEXT&
&

&
&INT&
&TEXT&
&INT&
&INT&
&

&

&
&
&TEXT&
&

&
&TEXT&
&

&

&
&
&INT&
&

&
&FLOAT&
&TEXT&
&

&

&
&
&TEXT&
&

&
&TEXT&
&INT&
&TEXT&
&TEXT&
&INT&
&TEXT&
&TEXT&
&

&

&
&
&INT&
&TEXT&
&

&
&INT&
&TEXT&
&

&

&

這是我描述一個小資料庫的XML文檔,沒有什麼意義,用以上的代碼解析完成之後是一個字典,我們將其JSON化:

#toDict是傳入的字典
def jsonCreater(toDict):
return json.dumps(toDict)

我們將其格式化之後:

{
"DATABASE": {
"SALEINFO": {
"INDEX": [
{
"Key": "DATE",
"Attr": {
"NULL": "FALSE"
},
"Value": "INT"
}
],
"OTHERS": [
{
"Key": "TURNOVER",
"Attr": {
"NULL": "FALSE"
},
"Value": "FLOAT"
},
{
"Key": "MOSTGOOD",
"Attr": {
"NULL": "FALSE"
},
"Value": "TEXT"
}
]
},
"GOODS": {
"INDEX": [
{
"Key": "GOODID",
"Attr": {
"NULL": "FALSE"
},
"Value": "TEXT"
}
],
"OTHERS": [
{
"Key": "PRICE",
"Attr": {
"NULL": "FALSE"
},
"Value": "FLOAT"
},
{
"Key": "TIME",
"Attr": {
"NULL": "FALSE"
},
"Value": "INT"
},
{
"Key": "THUMBS",
"Attr": {
"NULL": "FALSE"
},
"Value": "TEXT"
},
{
"Key": "SALES",
"Attr": {
"NULL": "FALSE"
},
"Value": "INT"
}
]
},
"USER": {
"INDEX": [
{
"Key": "USERID",
"Attr": {
"NULL": "FALSE"
},
"Value": "TEXT"
}
],
"OTHERS": [
{
"Key": "PHONE",
"Attr": {
"NULL": "FALSE",
"UNIQUE": "TRUE"
},
"Value": "TEXT"
},
{
"Key": "SEX",
"Attr": {
"NULL": "TRUE"
},
"Value": "INT"
},
{
"Key": "AVATAR",
"Attr": {
"NULL": "TRUE"
},
"Value": "TEXT"
},
{
"Key": "NICKNAME",
"Attr": {
"NULL": "FALSE"
},
"Value": "TEXT"
},
{
"Key": "REGTIME",
"Attr": {
"NULL": "FALSE"
},
"Value": "INT"
},
{
"Key": "LOCATE",
"Attr": {
"NULL": "TRUE"
},
"Value": "TEXT"
},
{
"Key": "PASSWORD",
"Attr": {
"NULL": "TRUE"
},
"Value": "TEXT"
}
]
},
"WXREPLY": {
"INDEX": [
{
"Key": "MSGTYPE",
"Attr": {
"NULL": "FALSE"
},
"Value": "INT"
},
{
"Key": "MSG",
"Attr": {
"NULL": "FALSE"
},
"Value": "TEXT"
}
],
"OTHERS": [
{
"Key": "RTMSGTYPE",
"Attr": {
"NULL": "FALSE"
},
"Value": "INT"
},
{
"Key": "RTMSG",
"Attr": {
"NULL": "FALSE"
},
"Value": "TEXT"
}
]
},
"HISTORY": {
"INDEX": [
{
"Key": "USERID",
"Attr": {
"NULL": "FALSE"
},
"Value": "TEXT"
}
],
"OTHERS": [
{
"Key": "BUYTIME",
"Attr": {
"NULL": "FALSE"
},
"Value": "INT"
},
{
"Key": "ORDERNUM",
"Attr": {
"NULL": "FALSE"
},
"Value": "TEXT"
},
{
"Key": "PAYMENT",
"Attr": {
"NULL": "FALSE"
},
"Value": "INT"
},
{
"Key": "SUCCESS",
"Attr": {
"NULL": "FALSE"
},
"Value": "INT"
}
]
},
"ADMIN": {
"INDEX": [
{
"Key": "ACCOUNT",
"Attr": {
"NULL": "FALSE"
},
"Value": "TEXT"
}
],
"OTHERS": [
{
"Key": "PASSWORD",
"Attr": {
"NULL": "FALSE"
},
"Value": "TEXT"
}
]
}
}
}

這樣的結果還是不錯吧。

感覺又要被噴。


beautifulSoup lxml

正則表達式簡單粗暴

chrome和firefox直接複製路徑啊,firefox有個插件firebug更強大了


直接上re


推薦一個簡單粗暴的組合:xml2dict+flatten 把xml轉成orderdict,然後拍平成單層dict,管你三層還是四層,一次遍歷無需遞歸~


這個時候你需要支持XPath或選擇器的庫,比如: 支持XPath的xml.etree.ElementTree,支持選擇器的BeautifulSoup等等。


推薦閱讀:

使用Python建站對於日流量20w的pv你會採用什麼樣的架構?
windows8 64位python 安裝theano報錯?

TAG:Python | 數據分析 | 樹數據結構 |