返回博客

lxml 教程:使用 lxml 处理 XML 和 Web 抓取

lxml 教程:使用 lxml 处理 XML 和 Web 抓取

Gabija Fatenaite

2022-06-302 min read

在这篇 lxml Python 教程中,我们将介绍什么是 lxml 库。我们会讲解用它创建 XML 文档的基础知识,然后来看看怎么处理 XLM 和 HTML 文档。最后,我们就能结合所有的知识点进行运用,尝试使用 lxml 提取数据。我们为本教程的每一步配备了实用的 Python lxml 示例。

目标读者

这份教程适用于具备 Python 基础知识的开发人员。需要对 XML 和 HTML 有一定的了解。如果您知道 XML 的属性是什么,那么要理解这篇文章不成问题。 

这份教程使用 Python 3 代码段,但所有内容都可以在 Python 2 上运行,只要稍作修改即可。

Python 中的 lxml 是什么?

lxml 是 Python 中处理 XML 和 HTML 最快且功能最多的库。这个库本质上是 C 库 libxml2 libxslt 的封装。这样一来,它既有 C 库的速度,又有 Python 的简洁。

使用 Python 中的 lxml 库可以创建、解析和查询 XML 和 HTML 文档。它需要依赖各种其他复杂包,例如 Scrapy。

安装步骤

如何在 Python 中安装 lxml?最好是从 Python Package Index(PyPI)下载和安装lxml库。如果您是 Linux(基于 debian)系统,只需运行以下代码:

sudo apt-get install python3-lxml

您也可以使用 pip 包管理器。pip 包管理器在 Windows、Mac 和 Linux 上都适用。

pip3 install lxml

如果您在 Windows 上运行 Python 3,可以使用 pip install lxml

创建简单 XML 文档

任何 XML 或任何符合 HTML 规则的 XML 都会呈现树形结构。一棵树有根也有树枝,还每个树枝可以延伸出更多树枝。所有这些树枝和树根分别都可以表示为元素(Element)

简单的 XML 文档就如以下这样:

<root>
    <branch>
        <branch_one>
        </branch_one>
        <branch_one>
        </branch_one >
    </branch>
</root>

如果 HTML 遵从 XML 的语法规则,那么它也遵循同样的概念。 

注意 HTML 既可以遵从也可以不遵从 XML 语法规则。例如,如果 HTML 的 <br> 缺少了对应的结束标记,但它仍然是有效的 HTML,只是并非有效的 XML。在这份教程的稍后部分,我们再来了解如何应对这些情形。  现在,我们重点了解 XML 遵从 HTML 规则的情况。

元素类

使用 Python xlml 来创建 XML 文档,首先我们就要导入 lxml 的 etree 模块:

>>> from lxml import etree

XML 文档都是从根元素开始。可以通过元素类型来创建。元素类型是个可以存储分层数据的灵活容器对象。可将它描述为字典和列表之间的交叉。

在本 Python lxml 示例中,我们要创建遵从 XML 规则的 HTML。因此根元素名称将为 html。

>>> root = etree.Element("html")

同样,每个 html 都有头部和主体:

>>> head = etree.Element("head")
>>> body = etree.Element("body")

使用 append() 方法可以创建父子关系。

>>> root.append(head)
>>> root.append(body)

可以通过 tostring() 函数将这份文档序列化并输出到终端。这个函数需要一个强制参数,也就是文档的根。我们可以将 pretty_print 设为 True,从而输出可读性更高的文档。注意,tostring() 序列化工具实际可以返回字节。这样就可以调用 decode(),从而转换为字符串。

>>> print(etree.tostring(root, pretty_print=True).decode())

子元素类

创建一个元素对象,然后调用 append() 函数,这样代码混乱和不可读。最直接的方法就是使用子元素SubElement)类型。它的构造函数需要两个参数,父节点和元素名称。通过 SubElement,将以下两行代码替换为一行。

body = etree.Element("body")
root.append(body)
# is same as 
body = etree.SubElement(root,"body")

设置文本和属性

通过 lxml 库设置文本非常简单。Element SubElement 的每个实例都可以使用两种方法:text 和 set:text 用来指定文本,set 则用来设置属性。示例如下:

para = etree.SubElement(body, "p")
para.text="Hello World!"

同样,通过键值规则也可以设置属性:

para.set("style", "font-size:20pt")

值得注意的是,在以 SubElement 构造的函数中,属性是可以传递的:

para = etree.SubElement(body, "p", style="font-size:20pt", id="firstPara")
para.text = "Hello World!"

这种方法的好处是可以减少代码行数,更加简洁。完整代码如上。将它保存到 Python 文件中并运行。此时会输出一个格式完好的 HTML。

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() 仅仅有利于调试,最好不要将它用于其他方面。 

在代码段底部添加代码如下并再次运行:

with open(‘input.html’, ‘wb’) as f:
    f.write(etree.tostring(root, pretty_print=True)

此时会将输入的信息保存到运行此脚本的同一个文件夹中的 input.html。这个文件同样是格式完好的 XML,可以解释为 XML 或者 HTML。

怎样通过 Python 中的 lxml 解析 XML?

在教程的前半部分,我们介绍了怎么用 Python lxml 创建 XML 文件。接下来,我们来看看如何使用 lxml 库遍历和处理现有 XML 文档。

首先,将以下代码段保存为 input.html。

<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() 方法就可以了。

from lxml import etree
 
tree = etree.parse('input.html')
elem = tree.getroot()
etree.dump(elem) #prints file contents to console

lxml.etree 模块导致另一种方法也可以用于解析来自 xml 的字符串内容。

xml = '<html><body>Hello</body></html>'
root = etree.fromstring(xml)
etree.dump(root)

这里一个值得注意的重要不同之处是,fromstring() 方法会返回元素对象。没有必要调用 getroot()

在 XML 中查找元素

大体而言,有两种方式可以使用 Python lxml 库查找元素。一种方式是使用 Python lxml 查询语言:XPath 和 ElementPath。例如,以下代码会返回第一个段落元素。

但是,选择器和 Xpath 很像。没有使用根元素名称,因为 elem 包含 XML 树的根。

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() 也会返回一个与选择器相匹配的所有元素的列表。

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 语法返回元素的实例、文本或任何属性的值。

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 文件输出所有段落的代码。

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

lmxl web 抓取教程

现在我们知道如何解析并在 XML 和 HTML 中查找元素,但还不知道如何提取网页上的 HTML。

要解决这个问题,不妨选择 ‘requests’ 库。可以通过 pip 包管理器安装 request 库。

pip install requests

安装 requests 库后,就可以通过简单的 get() method 检索所有 HTML。示例如下:

import requests
 
response = requests.get('http://books.toscrape.com/')
print(response.text)
# prints source HTML

也可以将这种方法和 lxml 结合起来检索所需的任意数据。

以下是一个简单示例,从维基百科提取国家/地区列表:

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 读取任何属性。例如,经修改后,以下代码可以输出国家/地区名称和国旗的图像连接。

for country in countries:
    flag = country.xpath('./img/@src')[0]
    country = country.xpath('./following-sibling::a/text()')[0]
    print(country, flag)

点击这里可以找到这篇教程中用过的完整代码。

总结

通过这篇教程,我们介绍了有关通过 lxml 库处理 XML 和 HTML 的方方面面的知识。Python lxml 库是个轻巧快速却功能丰富的工具。可以用它来创建XML文档,轻松读取现有文档,也可以用它查找特定元素。Python lxml 库的特点使它用来处理 XML 和 HTML 时效果也非常理想。结合 requests 库一起使用,还可以用来轻松抓取 web 数据。

欢迎前往我们的博客,进一步了解使用PythonC#或者Rust制作网页抓取工具,或者对于网络爬虫 API了解更多。

关于作者

Gabija Fatenaite

产品营销经理

Gabija Fatenaite 在 Oxylabs 担任一名产品营销经理。成长在视频游戏和互联网家庭背景下的她,多年以后逐渐发现对技术方面的东西越来越感兴趣。因此,如果您发现自己想了解更多关于代理(或视频游戏)方面的信息,请随时联系她。她将会非常乐意解疑答惑。

Oxylabs博客上的所有信息均按“原样”提供,仅供参考。对于您使用Oxylabs博客中包含的任何信息或其中可能链接的任何第三方网站中包含的任何信息,我们不作任何陈述,亦不承担任何责任。在从事任何类型的抓取活动之前,请咨询您的法律顾问,并仔细阅读特定网站的服务条款或取得抓取许可。

在这篇文章


  • Python 中的 lxml 是什么?

  • lmxl web 抓取教程

  • 总结

选择Oxylabs®,业务更上一层楼


隐私政策

oxylabs.cn© 2024 保留所有权利©