Python爬虫实战:使用BeautifulSoup解析HTML文档详解

苏见山
苏见山
发布时间:2026-10-13 | 2人浏览过
原创

本文深入讲解Python中BeautifulSoup库的使用,涵盖从安装到高级应用的全流程。内容详解HTML树形结构、Tag与NavigableString数据类型,并通过遍历文档树与搜索文档树(find/find_all)两种核心方法,演示如何高效定位和提取HTML中的目标数据,结合代码示例帮助开发者快速掌握网页数据抓取技巧。

前言

在Python爬虫系列的第3篇文章中,我们介绍了网络请求利器Requests。当成功获取数据后,下一步便是提取目标信息。不同网站返回的数据格式各异,包括对开发者友好的JSON格式、XML格式,以及最常见的HTML文档格式。本文将重点探讨如何从HTML文档中精准提取所需数据。

是自行编写HTML解析器,还是使用正则表达式?这些方法往往并非最优解。幸运的是,Python社区早已提供了成熟的解决方案,其中BeautifulSoup便是处理此类问题的强力工具。它专为HTML文档操作设计,其名称源自Lewis Carroll的同名诗歌。

BeautifulSoup是一个用于解析HTML文档的Python库。借助它,开发者仅需少量代码即可提取HTML中任意感兴趣的内容。此外,该库具备出色的HTML容错能力,即便面对格式不完整的HTML文档,也能进行正确处理。

安装BeautifulSoup

pip install beautifulsoup4

请注意,官方已停止维护BeautifulSoup3,请务必下载并安装最新版本BeautifulSoup4。

HTML标签

在学习BeautifulSoup4之前,有必要先对HTML文档结构有基本了解。如下代码所示,HTML采用树形组织结构。

<html> 
 <head>
  <title>hello, world</title>
 </head>
 <body>
  <h1>BeautifulSoup</h1>
  <p>如何使用BeautifulSoup</p>
 <body>
</html> 
  • HTML由众多标签(Tag)构成,例如html、head、title等均为标签。
  • 一对标签构成一个节点,例如 ... 构成了根节点。
  • 节点之间存在特定关系,例如h1和p互为邻居,它们属于相邻的兄弟(sibling)节点。
  • h1是body的直接子(children)节点,同时也是html的子孙(descendants)节点。
  • body是p的父(parent)节点,而html则是p的祖辈(parents)节点。
  • 嵌套在标签之间的字符串是该节点下的一个特殊子节点,例如“hello, world”也是一个节点,尽管它没有名称。

使用BeautifulSoup

构建BeautifulSoup对象需要两个参数:第一个参数是待解析的HTML文本字符串,第二个参数指定用于解析HTML的解析器。

解析器的作用是将HTML解析为相关对象,而BeautifulSoup则负责数据的增删改查操作。“html.parser”是Python内置的解析器,而“lxml”则是基于C语言开发的解析器,执行速度更快,但需要额外安装。

通过BeautifulSoup对象,即可定位到HTML中的任何一个标签节点。

from bs4 import BeautifulSoup 
text = """
<html> 
 <head>
  <title >hello, world</title>
 </head>
 <body>
  <h1>BeautifulSoup</h1>
  <p class="bold">如何使用BeautifulSoup</p>
  <p class="big" id="key1"> 第二个p标签</p>
  <a href="http://foofish.net" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >python</a>
 </body>
</html> 
"""
soup = BeautifulSoup(text, "html.parser")

# title 标签
>>> soup.title
<title>hello, world</title>

# p 标签
>>> soup.p
<p class="bold">\u5982\u4f55\u4f7f\u7528BeautifulSoup</p>

# p 标签的内容
>>> soup.p.string
u'\u5982\u4f55\u4f7f\u7528BeautifulSoup'

BeautifulSoup将HTML抽象为4类主要数据类型:Tag、NavigableString、BeautifulSoup和Comment。每个标签节点对应一个Tag对象,包裹在Tag中的字符串通常是NavigableString对象,而BeautifulSoup对象则代表整个HTML文档。例如:

>>> type(soup)
<class 'bs4.BeautifulSoup'>
>>> type(soup.h1)
<class 'bs4.element.Tag'>
>>> type(soup.p.string)
<class 'bs4.element.NavigableString'>

Tag

每个Tag都拥有一个名称,对应HTML的标签名。

>>> soup.h1.name
u'h1'
>>> soup.p.name
u'p'

标签还可以拥有属性,其访问方式类似于字典,返回一个列表对象。

>>> soup.p['class']
[u'bold']

NavigableString

要获取标签中的内容,可直接使用.string属性,这将返回一个NavigableString对象。你可以将其显式转换为Unicode字符串。

>>> soup.p.string
u'\u5982\u4f55\u4f7f\u7528BeautifulSoup'
>>> type(soup.p.string)
<class 'bs4.element.NavigableString'>
>>> unicode_str = unicode(soup.p.string)
>>> unicode_str
u'\u5982\u4f55\u4f7f\u7528BeautifulSoup'

基本概念介绍完毕后,正式进入主题:如何从HTML中查找关心的数据?BeautifulSoup提供了两种主要方式:遍历和搜索,通常需结合使用以完成任务。

遍历文档树

遍历文档树,顾名思义,是从根节点html标签开始遍历,直至找到目标元素。该方法的一个缺陷是,若目标内容位于文档末尾,则需遍历整个文档,导致速度较慢。因此,还需配合第二种方法使用。

通过遍历文档树获取标签节点,可直接通过.标签名的方式获取,例如:

获取body标签:

>>> soup.body
<body>\n<h1>BeautifulSoup</h1>\n<p class="bold">\u5982\u4f55\u4f7f\u7528BeautifulSoup</p>\n</body>

获取p标签

>>> soup.body.p
<p class="bold">\u5982\u4f55\u4f7f\u7528BeautifulSoup</p>

获取p标签的内容

>>> soup.body.p.string
\u5982\u4f55\u4f7f\u7528BeautifulSoup

前文提到,内容也是一个节点,此处可通过.string方式获取。遍历文档树的另一缺点是仅能获取匹配的第一个子节点。例如,若存在两个相邻的p标签,第二个标签无法通过.p方式获取,此时需借助next_sibling属性获取相邻且位于后方的节点。此外,还有许多较少使用的属性,如.contents用于获取所有子节点,.parent用于获取父节点,更多参考请查阅官方文档。

搜索文档树

搜索文档树通过指定标签名来搜索元素,还可通过指定标签的属性值来精确定位节点。最常用的两个方法是find和find_all,这两个方法在BeautifulSoup和Tag对象上均可调用。

find_all()

find_all( name , attrs , recursive , text , **kwargs )

find_all的返回值是由Tag组成的列表,方法调用非常灵活,所有参数均为可选。

第一个参数name为标签节点的名字。

# 找到所有标签名为title的节点
>>> soup.find_all("title")
[<title>hello, world</title>]
>>> soup.find_all("p")
[<p class="bold">\xc8\xe7\xba\xce\xca\xb9\xd3\xc3BeautifulSoup</p>, 
<p class="big"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]

第二个参数为标签的class属性值。

# 找到所有class属性为big的p标签
>>> soup.find_all("p", "big")
[<p class="big"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]

等效于

>>> soup.find_all("p", class_="big")
[<p class="big"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]

由于class是Python关键字,因此此处指定为class_。

kwargs为标签的属性名值对,例如:查找href属性值为“http://foofish.net”的标签。

>>> soup.find_all(href="http://foofish.net" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" )
[<a href="http://foofish.net" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >python</a>]

当然,它还支持正则表达式。

>>> import re
>>> soup.find_all(href=re.compile("^http"))
[<a href="http://foofish.net" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >python</a>]

属性值除具体值和正则表达式外,还可以是布尔值(True/False),表示存在或不存在该属性。

>>> soup.find_all(id="key1")
[<p class="big" id="key1"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]
>>> soup.find_all(id=True)
[<p class="big" id="key1"> \xb5\xda\xb6\xfe\xb8\xf6p\xb1\xea\xc7\xa9</p>]

遍历与搜索相结合查找,先定位到body标签以缩小搜索范围,再从body中查找a标签。

>>> body_tag = soup.body
>>> body_tag.find_all("a")
[<a href="http://foofish.net" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >python</a>]

find()

find方法与find_all类似,唯一区别在于它返回单个Tag对象而非列表。若未找到匹配节点则返回None;若匹配多个Tag,仅返回第0个。

>>> body_tag.find("a")
<a href="http://foofish.net" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >python</a>
>>> body_tag.find("p")
<p class="bold">\xc8\xe7\xba\xce\xca\xb9\xd3\xc3BeautifulSoup</p>

get_text()

获取标签内容时,除使用.string外,还可使用get_text方法。不同之处在于,前者返回NavigableString对象,后者返回Unicode类型的字符串。

>>> p1 = body_tag.find('p').get_text()
>>> type(p1)
<type 'unicode'>
>>> p1
u'\xc8\xe7\xba\xce\xca\xb9\xd3\xc3BeautifulSoup'

>>> p2 = body_tag.find("p").string
>>> type(p2)
<class 'bs4.element.NavigableString'>
>>> p2
u'\xc8\xe7\xba\xce\xca\xb9\xd3\xc3BeautifulSoup'
>>>

在实际场景中,我们通常使用get_text方法获取标签内容。

总结

BeautifulSoup是一个用于操作HTML文档的Python库。初始化BeautifulSoup时,需指定HTML文档字符串及具体解析器。BeautifulSoup包含3类常用数据类型:Tag、NavigableString和BeautifulSoup。查找HTML元素有两种方式:遍历文档树和搜索文档树,通常快速获取数据需二者结合。

以上即为本文的全部内容,希望本文内容对大家学习或使用Python有所帮助。如有任何疑问,欢迎留言交流,感谢大家对本站的支持。

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表