Python(00):解析xml文件(sax、dom、ElementTree)和lxml
一、Python对XML的解析
常见的XML编程接口有DOM和SAX,这两种接口处理XML文件的方式不同,使用场合也不同。
1、DOM(Document Object Model)
DOM的解析器在解析一个XML文档时,一次性读取整个文档,把文档中所有元素保存在内存中的一个树结构里,之后利用DOM提供的不同函数来读取该文档的内容和结构,也可以把修改过的内容写入XML文件。
由于DOM是将XML读取到内存,然后解析成一个树,如果要处理的XML文本比较大的话,就会很耗内存,所以DOM一般偏向于处理一些小的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)
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("*********************")
四、lxml库中etree.HTML()和etree.tostring()用法
-
测试HTML代码
# 测试代码test.html
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div class='main-content'>
<h1 id="title">This is a test!</h1>
<p class="main-content ref">This is paragraph1</p>
<div>
<p>测试语句1</p>
</div>
</div>
<div>
<p>This is paragraph2</p>
<div>
<p class="ref">测试语句2</p>
</div>
</div>
</body>
</html>
-
etree.HTML( )
调用HTML类对HTML文本进行初始化,成功构造XPath解析对象,同时可以自动修正HMTL文本(标签缺少闭合自动添加上)
from lxml import etree #首先导入lxml库的etree模块
with open('test.html','r') as f:
c = f.read()
#调用HTML类进行初始化,成功构造XPath解析对象
tree = etree.HTML(c)
-
etree.tostring()
tostring( )方法可以输出修正之后的HTML代码,也可以直接读取文本进行解析,但是结果为bytes类型,因此需要利用decode()方法将其转成str类型
具体的decode( )格式需要浏览器审查网页查看
import requests
from lxml import etree
with open('real_case.html', 'r', encoding='utf-8') as f:
c = f.read()
tree = etree.HTML(c)
table_element = tree.xpath("//div[@class='table-box'][1]/table/tbody/tr")
for row in table_element:
try:
td1 = row.xpath('td')[0]
#具体的转成什么格式,需要审查网页元素,查看
s1 = etree.tostring(td1, encoding='utf-8').decode('utf-8')
print(s1)
except Exception as error:
pass
posted on 2022-04-27 09:19 springsnow 阅读(765) 评论(0) 编辑 收藏 举报