Python(00):解析xml文件(sax、dom、ElementTree)和lxml


一、Python對XML的解析

常見的XML編程接口有DOM和SAX,這兩種接口處理XML文件的方式不同,使用場合也不同。

python有三種方法解析XML:SAX,DOM和ElementTree

1、DOM(Document Object Model)

DOM的解析器在解析一個XML文檔時,一次性讀取整個文檔,把文檔中所有元素保存在內存中的一個樹結構里,之后利用DOM提供的不同函數來讀取該文檔的內容和結構,也可以把修改過的內容寫入XML文件。

由於DOM是將XML讀取到內存,然后解析成一個樹,如果要處理的XML文本比較大的話,就會很耗內存,所以DOM一般偏向於處理一些小的XML(如配置文件)比較快。

python中用xml.dom.minidom來解析xml文件。

from xml.dom.minidom import parse

DOMTree = parse(r'book.xml')  # minidom解析器打開xml文檔並將其解析為內存中的一棵樹
booklist = DOMTree.documentElement  # 獲取xml文檔對象,就是拿到樹的根

if booklist.hasAttribute('type'):
    print('Root element is ', booklist.getAttribute('type'))  # 判斷根節點booklist是否有type屬性,有則獲取並打印屬性值
books = booklist.getElementsByTagName('book')  # 獲取booklist對象中所有的book節點的list集合
print('book節點的個數為:', len(books))
print('book節點的個數為:', books.length)

for book in books:
    print("*******************book*******************")
    if book.hasAttribute('category'):
        print('category is ', book.getAttribute('category'))
    # 根據節點名title/author/pageNumber得到這些節點的集合list
    title = book.getElementsByTagName('title')[0]
    author = book.getElementsByTagName('author')[0]
    pageNumber = book.getElementsByTagName('pageNumber')[0]

    print('title is ', title.childNodes[0].data)
    print('author is ', author.childNodes[0].data)
    print('pageNumber is ', pageNumber.childNodes[0].data)

2、SAX(simple API for XML)

Python標准庫中包含SAX解析器,SAX是用的是事件驅動模型,通過在解析XML過程中觸發一個個的事件並調用用戶定義的回調函數來處理XML文件。
解析的基本過程:
讀到一個XML開始標簽,就會開始一個事件,然后事件就會調用一系列的函數去處理一些事情,當讀到一個結束標簽時,就會觸發另一個事件。所以,我們寫XML文檔如果有格式錯誤的話,解析就會出錯。
這是一種流式處理,一邊讀一邊解析,占用內存少。適用場景如下:
1、對大型文件進行處理;
2、只需要文件的部分內容,或者只需從文件中得到特定信息。
3、想建立自己的對象模型的時候。

from xml.sax import *

class DengHandler(ContentHandler):
    def startDocument(self):
        print("----開始解析xml文檔----")
    def endDocument(self):
        print("----xml文檔解析完畢----")
    def startElement(self,name,attrs):
        if name == "author":
            print("名字:",attrs['name']," 日期:",attrs["birth"])

parse("deng.xml",DengHandler())

3、ElementTree(元素樹)

ElementTree就像一個輕量級的DOM,具有方便友好的API。代碼可用性好,速度快,消耗內存少。

因DOM需要將XML數據映射到內存中的樹,一是比較慢,二是比較耗內存;而SAX流式讀取XML文件,比較快,占用內存少,但需要用戶實現回調函數(handler),所以一般選用ElementTree(元素樹)。

二、xml.etree.ElementTree解析XML

<?xml version="1.0"?>
<data>
    <country name="Liechtenstein">
        <rank updated="yes">2</rank>
        <year>2008</year>
        <gdppc>141100</gdppc>
        <neighbor name="Austria" direction="E"/>
        <neighbor name="Switzerland" direction="W"/>
    </country>
    <country name="Singapore">
        <rank updated="yes">5</rank>
        <year>2011</year>
        <gdppc>59900</gdppc>
        <neighbor name="Malaysia" direction="N"/>
    </country>
    <country name="Panama">
        <rank updated="yes">69</rank>
        <year>2011</year>
        <gdppc>13600</gdppc>
        <neighbor name="Costa Rica" direction="W"/>
        <neighbor name="Colombia" direction="E"/>
    </country>
</data>

1、遍歷和查找xml

xml協議在各個語言里的都是支持的,在python中可以用以下模塊操作xml:

# print(root.iter('year')) #全文搜索
# print(root.find('country')) #在root的子節點找,只找一個
# print(root.findall('country')) #在root的子節點找,找所有

import xml.etree.ElementTree as ET

tree = ET.parse("xmltest.xml")
root = tree.getroot()
print(root.tag)

# 遍歷xml文檔
for child in root:
    print('========>', child.tag, child.attrib, child.attrib['name'])
    for i in child:
        print(i.tag, i.attrib, i.text)

# 只遍歷year 節點
for node in root.iter('year'):
    print(node.tag, node.text)

2、修改和刪除節點

import xml.etree.ElementTree as ET

tree = ET.parse("xmltest.xml")
root = tree.getroot()

# 修改
for node in root.iter('year'):
    new_year = int(node.text) + 1
    node.text = str(new_year)
    node.set('updated', 'yes')
    node.set('version', '1.0')
tree.write('test.xml')

# 刪除node
for country in root.findall('country'):
    rank = int(country.find('rank').text)
    if rank > 50:
        root.remove(country)

tree.write('output.xml')

3、添加節點

# 在country內添加(append)節點year2
import xml.etree.ElementTree as ET

tree = ET.parse("a.xml")
root = tree.getroot()
for country in root.findall('country'):
    for year in country.findall('year'):
        if int(year.text) > 2000:
            year2 = ET.Element('year2')
            year2.text = '新年'
            year2.attrib = {'update': 'yes'}
            country.append(year2)  # 往country節點下添加子節點

tree.write('a.xml.swap')

4、自己創建xml文檔

import xml.etree.ElementTree as ET

new_xml = ET.Element("namelist")
name = ET.SubElement(new_xml, "name", attrib={"enrolled": "yes"})
age = ET.SubElement(name, "age", attrib={"checked": "no"})
sex = ET.SubElement(name, "sex")
sex.text = '33'
name2 = ET.SubElement(new_xml, "name", attrib={"enrolled": "no"})
age = ET.SubElement(name2, "age")
age.text = '19'

et = ET.ElementTree(new_xml)  # 生成文檔對象
et.write("test.xml", encoding="utf-8", xml_declaration=True)

ET.dump(new_xml)  # 打印生成的格式

三、lxml.etree解析XML(推薦)

lxml 是一種使用 Python 編寫的庫,可以迅速、靈活地處理 XML,支持 XPath

lxml.etree和xml.etree.ElementTree兩個的操作方式看起來差不多,但lxml要更好一些,使用更簡潔。解析xml的時候,自動處理各種編碼問題。而且它天生支持 XPath 1.0、XSLT 1.0、定制元素類。

不過,lxml不是Python自帶的標准庫。需要自己安裝,如下方式安裝:

$ pip install lxml

from lxml import etree
 
 
with open('./books.xml') as f:
    # print(f.read())
    text = f.read()
    html = etree.HTML(text.encode())
    # print(html)
    print(html.tag)
 
    print(html.xpath('//title'))  # 從根節點向下找任意層中title的節點
    print(html.xpath('//book//title'))
    print(html.xpath('//book[@id="bk102"]'))
    print(html.xpath('//book[@id]'))
    print(html.xpath('//@id'))  # 取回的是屬性
    print(html.xpath('//*[@id]'))
    print(html.xpath('//bookstore/book[1]'))
    print(html.xpath('//bookstore/book[1]/@id'))  # ['bk101']
    print(html.xpath('//bookstore/book[last()]/@id'))  # last()為最后一個節點
    print(html.xpath('//*[contains(local-name(), "store")]'))  # [<Element bookstore at 0x2ce5648>]
    # local-name()為當前標簽名字
    print(html.xpath('//bookstore/*'))  # 匹配根節點bookstore下的所有子節點,不遞歸;
    print(html.xpath('//*[@*]'))  # 匹配所有有屬性的節點
    print(html.xpath('//@*'))  # 匹配所有屬性
    print(html.xpath('//book/title|//book/price'))  # 匹配book節點下title標簽或prices標簽
    print(html.xpath('//book[position()=2]/@id'))  # ['bk102']
    print(html.xpath('//book[price > 40]/@id'))
    print(html.xpath('//book[1]/text()'))  # 匹配第一個book節點下所有文本子節點
    print(html.xpath('//book[1]//text()'))  # 匹配第一個book節點下所有文本節點
    print(html.xpath('//*[contains(@class,"even")]'))  # 匹配屬性class中包含even字符串的節點

可以使用 lxml 的 etree 庫來進行爬取網站信息。

從豆瓣電影中提取“本周口碑榜”:

import requests
from lxml import etree  # lxml 是c語言的庫,效率非常高


url = 'http://movie.douban.com'
headers = {'User-agent': "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) \
            Chrome/55.0.2883.75 Safari/537.36"}
response = requests.get(url, headers=headers)

with response:
    if response.status_code == 200:
        text = response.text
        html = etree.HTML(text)
        print(html.tag)

        titles = html.xpath('//div[@class="billboard-bd"]//a/text()')
        for title in titles:
            print(title)

        print("*********************")


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM