使用lxml处理XML及网页抓取
在本教程中,我们会学习lxml库和创建XML文档的基础知识,然后会处理XML和HTML文档。最后,我们将利用以上所学,融会贯通,看看如何使用lxml提取数据。本教程的每一步都配有实用的Python lxml示例。
阅读人群
本教程适用于对Python、XML和HTML有基本的了解的开发人员。简单地说,如果您知道XML中的属性是什么,那么就足以理解本文。
本教程使用Python3代码段,但所有内容都可以在Python2上运行,只需进行少量更改。
Python中的lxml是什么?
lxml是在Python中处理XML和HTML最快且功能丰富的库之一。该库本质上是C库libxml2和libxslt的封装。因此结合了C库的速度和Python的简单性。
使用Python lxml库,可以创建、解析和查询XML和HTML文档。它依赖于许多其他复杂的包,如Scrapy。
#安装
下载和安装lxml库的最佳方法是去Python Package Index(PyPI)下载
如果您使用的是Linux(基于debian),只需运行:
代码语言:javascript复制 sudo apt-get install python3-lxml
另一种方法是使用pip包管理器。这适用于Windows、Mac和Linux:
代码语言:javascript复制 pip3 install lxml
在Windows上,假设您正在运行Python3,只需使用pip install lxml。
#创建一个简单的XML文档
任何XML或任何符合XML的HTML都可以看作一棵树。一棵树有根和树枝。树的每个分支可以具有更多分支。所有这些分支和根都分别表示一个Element。
一个非常简单的XML文档如下所示:
代码语言:javascript复制<root>
<branch>
<branch_one>
</branch_one>
<branch_one>
</branch_one>
<branch>
</root>
如果HTML是兼容XML的,它将遵循相同的概念。
请注意,HTML可能兼容也可能不兼容XML。例如,如果HTML的<br>没有相应的结束标记,它仍然是有效的HTML,但它不会是有效的XML。
在本教程的后半部分,我们将看看如何处理这些情况。接下来让我们专注于兼容XML的HTML。
#元素类
使用python lxml创建XML文档,第一步是导入lxml的etree模块:
代码语言:javascript复制>>> from lxml import etree
每个XML文档都以根元素开始。可以使用元素类型创建。元素类型是一个灵活的容器对象,可以存储分层数据。可以描述为字典和列表之间的交叉。
在这个python lxml示例中,目标是创建一个兼容XML的HTML。这意味着根元素的名称将是html:
代码语言:javascript复制>>> root = etree.Element("html")
同样,每个html都会有一个头部和一个主体:
代码语言:javascript复制>>> head = etree.Element("head")
>>> body = etree.Element("body")
要创建父子关系,我们可以简单地使用append()方法。
代码语言:javascript复制>>> root.append(head)
>>> root.append(body)
在tostring()函数的帮助下,这个文档可以被序列化并输出到终端。此函数需要一个强制参数,即文档的根。我们可以选择将pretty_print设置为True以使输出更具可读性。
请注意,tostring()序列化程序会实际返回字节。这可以通过调用decode()转换为字符串:
代码语言:javascript复制>>> print(etree.tostring(root, pretty_print=True).decode())
#子元素类
创建一个Element对象并调用append()函数会使代码变得混乱和不可读。最简单的方法是使用SubElement类型。它的构造函数有两个参数——父节点和元素名称。使用SubElement,以下两行代码可以替换为一行。
代码语言:javascript复制body = etree.Element("body")
root.append(body)
# is same as
body = etree.SubElement(root,"body")
#设置文本和属性
使用lxml库设置文本非常容易。Element和SubElement的每个实例都公开了两个方法——text和set,前者用于指定文本,后者用于设置属性。以下是示例:
代码语言:javascript复制para = etree.SubElement(body, "p")
para.text="Hello World!"
同样,可以使用键值约定设置属性:
代码语言:javascript复制para.set("style", "font-size:20pt")
要注意的是,可以在SubElement的构造函数中传递该属性:
代码语言:javascript复制para = etree.SubElement(body, "p", style="font-size:20pt", id="firstPara")
para.text = "Hello World!"
这种方法的节省了代码行数,代码可读性也更强。这是完整的代码。将它保存在一个python文件中并运行它。它将输出一个HTML,它也是一个格式良好的XML。
代码语言:javascript复制from lxml import etree
root = etree.Element("html")
head = etree.SubElement(root, "head")
title = etree.SubElement(head, "title")
title.text = "This is Page Title"
body = etree.SubElement(root, "body")
heading = etree.SubElement(body, "h1", style="font-size:20pt", id="head")
heading.text = "Hello World!"
para = etree.SubElement(body, "p", id="firstPara")
para.text = "This HTML is XML Compliant!"
para = etree.SubElement(body, "p", id="secondPara")
para.text = "This is the second paragraph."
etree.dump(root) # prints everything to console. Use for debug only
请注意,这里我们使用了etree.dump()而不是调用etree.tostring()。不同之处在于dump()只是将所有内容写入控制台而不返回任何内容,tostring()用于序列化并返回一个字符串,您可以将其存储在变量中或写入文件。dump()仅适用于调试,不应用于任何其他目的。
在代码段的底部添加以下几行并再次运行它:
代码语言:javascript复制with open(‘input.html’, ‘wb’) as f:
f.write(etree.tostring(root, pretty_print=True)
代码会将内容保存到您运行脚本的同一文件夹中的input.html。同样,这是一个格式良好的XML,可以看作XML或HTML。
如何在Python中使用LXML
解析XML文件?
上一节是关于创建XML文件的Python lxml教程。在本节中,我们将研究如何使用lxml库遍历和操作现有的XML文档。
在我们继续之前,将以下代码段保存为input.html。
代码语言:javascript复制<html>
<head>
<title>This is Page Title</title>
</head>
<body>
<h1 style="font-size:20pt" id="head">Hello World!</h1>
<p id="firstPara">This HTML is XML Compliant!</p>
<p id="secondPara">This is the second paragraph.</p>
</body>
</html>
解析XML文档时,结果是内存中的ElementTree对象。
原始XML内容可以在文件系统或字符串中。如果它在文件系统中,则可以使用parse方法加载它。请注意,parse方法将返回一个ElementTree类型的对象。要获取根元素,只需调用getroot()方法。
代码语言:javascript复制from lxml import etree
tree = etree.parse('input.html')
elem = tree.getroot()
etree.dump(elem) #prints file contents to console
lxml.etree模块公开了另一种可用于有效解析xml字符串中内容的方法—fromstring()
代码语言:javascript复制xml ='<html><body>Hello</body></html>'
root = etree.fromstring(xml)
etree.dump(root)
这里要注意的一个重要区别是fromstring()方法会返回一个元素对象。无需调用getroot()。
如果您想深入了解解析,可以查看BeautifulSoup教程的详细内容:
这是一个用于解析HTML和XML文档的Python包。但是为了快速回答BeautifulSoup中的lxml是什么,lxml可以使用BeautifulSoup作为解析器后端。同样,BeautifulSoup可以使用lxml作为解析器。
在XML中查找元素
从广义上讲,有两种使用Python lxml库查找元素的方法。第一种是使用Python lxml查询语言:XPath和ElementPath。例如,以下代码将返回第一个段落元素。
请注意,选择器与XPath非常相似。另请注意,未使用根元素名称,因为elem包含XML树的根。
代码语言:javascript复制tree = etree.parse('input.html')
elem = tree.getroot()
para = elem.find('body/p')
etree.dump(para)
# Output
# <p id="firstPara">This HTML is XML Compliant!</p>
类似地,findall()将返回与选择器匹配的所有元素的列表。
代码语言:javascript复制elem = tree.getroot()
para = elem.findall('body/p')
for e in para:
etree.dump(e)
# Outputs
# <p id="firstPara">This HTML is XML Compliant!</p>
# <p id="secondPara">This is the second paragraph.</p>
选择元素的第二种方法是直接使用XPath。熟悉XPath的开发人员更容易使用这种方法。此外,XPath可用于使用标准XPath语法返回元素的实例、文本或任何属性的值。
代码语言:javascript复制para = elem.xpath('//p/text()')
for e in para:
print(e)
# Output
# This HTML is XML Compliant!
# This is the second paragraph.
使用lxml.html处理HTML
在本文中,我们一直在使用兼容XML的格式良好的HTML。很多时候情况并非如此。对于这些场景,您可以简单地使用lxml.html而不是lxml.etree。
请注意,不支持直接从文件中读取。文件内容应首先以字符串形式读取。这是从同一HTML文件输出所有段落的代码。
代码语言:javascript复制from lxml import html
with open('input.html') as f:
html_string = f.read()
tree = html.fromstring(html_string)
para = tree.xpath('//p/text()')
for e in para:
print(e)
# Output
# This HTML is XML Compliant!
# This is the second paragraph
lxml网页抓取教程
现在我们知道如何解析和查找XML和HTML中的元素,唯一缺少的部分是获取网页的HTML。
为此,“requests”库是一个不错的选择。它可以使用pip包管理器安装:
代码语言:javascript复制pip install requests
一旦安装了requests库,就可以使用简单的get()方法检索任何网页的HTML。示例如下:
代码语言:javascript复制import requests
response = requests.get('http://books.toscrape.com/')
print(response.text)
# prints source HTML
可以与lxml结合以检索所需的任何数据。
这是一个输出维基百科国家列表的简单示例:
代码语言:javascript复制import requests
from lxml import html
response = requests.get('https://en.wikipedia.org/wiki/List_of_countries_by_population_in_2010')
tree = html.fromstring(response.text)
countries = tree.xpath('//span[@class="flagicon"]')
for country in countries:
print(country.xpath('./following-sibling::a/text()')[0])
在这段代码中,response.text返回的HTML被解析为变量树。可以使用标准XPath语法进行查询,连接XPath。请注意,xpath()方法返回一个列表,因此在此代码片段中仅获取第一项。
这可以很容易地扩展为从HTML读取任何属性。例如,以下修改后的代码输出结果为国旗的国家名称和图像URL。
代码语言:javascript复制for country in countries:
flag = country.xpath('./img/@src')[0]
country = country.xpath('./following-sibling::a/text()')[0]
print(country, flag)
结论
在这个Python lxml教程中,介绍了使用lxml库处理XML和HTML。Python lxml库是一个轻量级、快速且功能丰富的库。可用于创建XML文档、读取现有文档和查找特定元素。这个库对于XML和HTML文档同样强大。结合Requests库,它也可以很容易地用于网页抓取。
您可以阅读使用Selenium或其他有用库(例如Beautiful Soup)的文章并了解有关网络抓取的更多信息。