python爬虫之BeautifulSoup4使用

打印 上一主题 下一主题

主题 877|帖子 877|积分 2631

钢铁知识库,一个学习python爬虫、数据分析的知识库。人生苦短,快用python。
上一章我们讲解针对结构化的html、xml数据,使用Xpath实现网页内容爬取。本章我们再来聊另一个高效的神器:Beautiful Soup4。相比于传统正则表达方式去解析网页源代码,这个就简单得多,实践是检验真理的唯一标准,话不多说直接上号开搞验证。
Beautiful Soup 简介

首先说说BeautifulSoup是什么。简单来说,这是Python的一个HTML或XML的解析库,我们可以用它方便从网页中提取数据,官方解释如下:
BeautifulSoup 提供一些简单的、Python 式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。 BeautifulSoup 自动将输入文档转换为 Unicode 编码,输出文档转换为 utf-8 编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时你仅仅需要说明一下原始编码方式就可以了。 BeautifulSoup 已成为和 lxml、html5lib 一样出色的 Python 解释器,为用户灵活地提供不同的解析策略或强劲的速度。
所以,利用它可以省去很多繁琐的提取工作,提高解析效率。
BeautifulSoup 安装

BeautifulSoup3 目前已经停止开发,推荐使用 BeautifulSoup4,不过它也被移植到bs4了,也就是说导入时我们需要import bs4
在开始之前,请确保已经正确安装beautifulsoup4和lxml,使用pip安装命令如下:
  1. pip install beautifulsoup4
  2. pip install lxml
复制代码
解析器

BeautifulSoup在解析时实际上依赖解析器。除了支持Python标准库中的HTML解析器,还支持一些第三方的解析器,如果不安装它,则Python会使用默认的解析器。
下面列出BeautifulSoup支持的解析器
解析器使用方法优势劣势Python 标准库BeautifulSoup(markup, "html.parser")Python 的内置标准库、执行速度适中 、文档容错能力强Python 2.7.3 or 3.2.2) 前的版本中文容错能力差LXML HTML 解析器BeautifulSoup(markup, "lxml")速度快、文档容错能力强需要安装 C 语言库LXML XML 解析器BeautifulSoup(markup, "xml")速度快、唯一支持 XML 的解析器需要安装 C 语言库html5libBeautifulSoup(markup, "html5lib")最好的容错性、以浏览器的方式解析文档、生成 HTML5 格式的文档速度慢、不依赖外部扩展通过上面可以看出,lxml 有解析HTML和XML的功能,相比默认的HTML解析器更加强大,速度,容错能力强。
推荐使用它,下面统一使用lxml进行演示。使用时只需在初始化时第二个参数改为 lxml 即可。
  1. from bs4 import BeautifulSoup
  2. soup = BeautifulSoup('<p>Hello</p>', 'lxml')
  3. print(soup.p.string)
  4. '''
  5. Hello
  6. '''
复制代码
基本使用

下面举个实例来看看BeautifulSoup的基本用法:
  1. html = """
  2. <html><head><title>The Dormouse's story</title></head>
  3. <body>
  4. <p  name="dromouse"><b>The Dormouse's story</b></p>
  5. <p >Once upon a time there were three little sisters; and their names were
  6. <a target="_blank" href="http://example.com/elsie"  id="link1"></a>,
  7. <a target="_blank" href="http://example.com/lacie"  id="link2">Lacie</a> and
  8. <a target="_blank" href="http://example.com/tillie"  id="link3">Tillie</a>;
  9. and they lived at the bottom of a well.</p>
  10. <p >...</p>
  11. """
  12. from bs4 import BeautifulSoup
  13. soup = BeautifulSoup(html, 'lxml')  # 初始化
  14. print(soup.prettify())
  15. print(soup.title.string)
复制代码
运行结果,你们也可以将上面代码复制到编辑器执行看看:
  1. <html>
  2. <head>
  3.   <title>
  4.    The Dormouse's story
  5.   </title>
  6. </head>
  7. <body>
  8.   <p  name="dromouse">
  9.    <b>
  10.     The Dormouse's story
  11.    </b>
  12.   </p>
  13.   <p >
  14.    Once upon a time there were three little sisters; and their names were
  15.    <a  target="_blank" href="http://example.com/elsie" id="link1">
  16.    
  17.    </a>
  18.    ,
  19.    <a  target="_blank" href="http://example.com/lacie" id="link2">
  20.     Lacie
  21.    </a>
  22.    and
  23.    <a  target="_blank" href="http://example.com/tillie" id="link3">
  24.     Tillie
  25.    </a>
  26.    ;
  27. and they lived at the bottom of a well.
  28.   </p>
  29.   <p >
  30.    ...
  31.   </p>
  32. </body>
  33. </html>
  34. The Dormouse's story
复制代码
首先声明一个html变量,它是一个HTML字符串,注意html和body标签都没有闭合。
经过初始化,使用prettify()方法把要解析的字符串以标准缩进格式输出,发现结果中自动补全了html和body标签。这一步不是prettify()方法做的,而是在初始化BeautifulSoup时就完成了。然后调用soup.title.string拿到title里面的文本内容。
通过简单调用几个属性完成文本提取,是不是非常方便呢?
节点选择器

直接调用节点的名称就可以选择节点元素,再调用 string 属性就可以得到节点内的文本了,这种选择方式速度非常快。如果单个节点结构层次非常清晰,可以选用这种方式来解析。
选择元素

还是以上面的HTML代码为例,详细说明选择元素的方法:
  1. from bs4 import BeautifulSoup
  2. soup = BeautifulSoup(html, 'lxml')
  3. print(soup.title)
  4. print(type(soup.title))
  5. print(soup.title.string)
  6. print(soup.head)
  7. print(soup.p)
  8. '''
  9. <title>The Dormouse's story</title>
  10. <class 'bs4.element.Tag'>
  11. The Dormouse's story
  12. <head><title>The Dormouse's story</title></head>
  13. <p  name="dromouse"><b>The Dormouse's story</b></p>
  14. '''
复制代码
首先输出title节点的选择结果,包含标签。
接下来输出它的类型,是一个bs4.element.Tag类型,Tag具有一些属性,比如string。
调用string属性可以看到输出节点的文本内容。
继续尝试head、p节点。发现p只取了第一个匹配的节点。说明当有多个节点时只取一个。
获取属性

每个节点可能有多个属性比如id 、class等,选择元素后可以调用attrs获取所有属性:
  1. print(soup.p.attrs)
  2. print(soup.p.attrs['name'])
  3. '''
  4. {'class': ['title'], 'name': 'dromouse'}
  5. dromouse
  6. '''
复制代码
可以看到attrs返回结果是字典,它把选择节点所有属性都组合成一个字典。取值直接按字典方式即可。
当然还有一种更简单的获取方式:不写attrs,直接在元素后面中括号取值也行:
  1. print(soup.p['name'])
  2. print(soup.p['class'])
  3. '''
  4. dromouse
  5. ['title']
  6. '''
复制代码
但是注意区分:有的返回字符串、有的返回字符串组成的列表。
对于class,一个节点元素可能有多个class,所以返回的是列表。
子节点和子孙节点

选取节点元素之后,如果想要获取它的直接子节点,可以调用 contents 属性,示例如下:
  1. html4 = """
  2. <html>
  3.     <head>
  4.         <title>The Dormouse's story</title>
  5.     </head>
  6.     <body>
  7.         <p >
  8.             钢铁知识库
  9.             <a target="_blank" href="http://a.com"  id="link1">
  10.                 Elsie
  11.             </a>
  12.             <a target="_blank" href="http://b.com"  id="link2">Lacie</a>
  13.             and
  14.             <a target="_blank" href="http://example.com"  id="link3">Tillie</a>
  15.             钢铁学爬虫.
  16.         </p>
  17.         <p >...</p>
  18. """
  19. from bs4 import BeautifulSoup
  20. soup = BeautifulSoup(html4, 'lxml')
  21. print(soup.p.contents)
  22. '''
  23. ['\n            钢铁知识库\n            ', <a  target="_blank" href="http://a.com" id="link1">
  24. Elsie
  25. </a>, '\n', <a  target="_blank" href="http://b.com" id="link2">Lacie</a>, ' \n            and\n            ', <a  target="_blank" href="http://example.com" id="link3">Tillie</a>, '\n            钢铁学爬虫.\n        ']
  26. '''
复制代码
可以看到返回结果是列表形式。p 节点里既包含文本,又包含文本,最后统一返回列表。
需要注意,列表中的每个元素都是 p 节点的直接子节点。比如第一个 a 节点里面的span节点,这相当于子孙节点了,但返回结果并没有单独把span节点列出来。所以说,contents属性得到的结果是直接子节点的列表。
同样,我们可以调用children属性得到相应的结果:
  1. from bs4 import BeautifulSoup
  2. soup = BeautifulSoup(html, 'lxml')
  3. print(soup.p.children)
  4. for i, child in enumerate(soup.p.children):
  5.     print(i, child)
  6. '''
  7. <list_iterator object at 0x0000000001D9A1C0>
  8. 0
  9.             钢铁知识库
  10.             
  11. 1 <a  target="_blank" href="http://a.com" id="link1">
  12. Elsie
  13. </a>
  14. 2
  15. 3 <a  target="_blank" href="http://b.com" id="link2">Lacie</a>
  16. 4  
  17.             and
  18.             
  19. 5 <a  target="_blank" href="http://example.com" id="link3">Tillie</a>
  20. 6
  21.             钢铁学爬虫.
  22. '''
复制代码
还是同样的 HTML 文本,这里调用了 children 属性来选择,返回结果是生成器类型。接下来,我们用 for 循环输出相应的内容。
如果要得到所有的子孙节点的话,可以调用 descendants 属性:
  1. <generator object Tag.descendants at 0x000001D77A90E570>
  2. 0
  3.             钢铁知识库
  4.             
  5. 1 <a  target="_blank" href="http://a.com" id="link1">
  6. Elsie
  7. </a>
  8. 2
  9. 3 Elsie
  10. 4 Elsie
  11. 5
  12. 6
  13. 7 <a  target="_blank" href="http://b.com" id="link2">Lacie</a>
  14. 8 Lacie
  15. 9  
  16.             and
  17.             
  18. 10 <a  target="_blank" href="http://example.com" id="link3">Tillie</a>
  19. 11 Tillie
  20. 12
  21.             钢铁学爬虫.
复制代码
此时返回结果还是生成器。遍历输出一下可以看到,这次的输出结果就包含了 span 节点。descendants 会递归查询所有子节点,得到所有的子孙节点。
除此之外,还有父节点parent 和祖先节点parents,兄弟节点next_sibling和previous_siblings 日常用得少不再演示,后续需要自行查官方文档即可。
方法选择器

前面聊的通过属性选择节点,但如果进行比较复杂的话还是比较繁琐。幸好BeautifulSoup还为我们提供另外一些查询方法,比如find_all 和 find ,调用他们传入相应参数就可以灵活查询。
find_all

顾名思义,就是查询所有符合条件的元素,可以给它传入一些属性或文本来得到符合条件的元素,功能十分强大。
它的 API 如下:
  1. find_all(name , attrs , recursive , text , **kwargs)
复制代码
我们可以根据节点名来查询元素,下面我们用一个实例来感受一下:
  1. html5='''
  2.    
  3.         <h4>Hello</h4>
  4.    
  5.    
  6.         <ul  id="list-1">
  7.             <li >钢铁</li>
  8.             <li >知识</li>
  9.             <li >仓库</li>
  10.         </ul>
  11.         <ul  id="list-2">
  12.             <li >python</li>
  13.             <li >java</li>
  14.         </ul>
  15.    
  16. '''
  17. from bs4 import BeautifulSoup
  18. soup = BeautifulSoup(html5, 'lxml')
  19. print(soup.find_all(name='ul'))
  20. print(type(soup.find_all(name='ul')[0]))
  21. '''
  22. [<ul  id="list-1">
  23. <li >钢铁</li>
  24. <li >知识</li>
  25. <li >仓库</li>
  26. </ul>, <ul  id="list-2">
  27. <li >python</li>
  28. <li >java</li>
  29. </ul>]
  30. <class 'bs4.element.Tag'>
  31. '''
复制代码
可以看到返回了一个列表,分别是两个ul长度为2,且类型依然是bs4.element.Tag类型。
因为都是Tag类型,所以依然可以继续嵌套查询,还是同样文本,查询ul节点后再继续查询内部li节点。
  1. from bs4 import BeautifulSoup
  2. soup = BeautifulSoup(html5, 'lxml')
  3. for ul in soup.find_all(name='ul'):
  4.     print(ul.find_all(name='li'))
  5. '''
  6. [<li >钢铁</li>, <li >知识</li>, <li >仓库</li>]
  7. [<li >python</li>, <li >java</li>]
  8. '''
复制代码
返回结果是列表类型,元素依然是Tag类型。
接下来我们可以遍历每个li获取它的文本:
  1. for ul in soup.find_all(name='ul'):
  2.     print(ul.find_all(name='li'))
  3.     for li in ul.find_all(name='li'):
  4.         print(li.string)
  5. '''
  6. [<li >钢铁</li>, <li >知识</li>, <li >仓库</li>]
  7. 钢铁
  8. 知识
  9. 仓库
  10. [<li >python</li>, <li >java</li>]
  11. python
  12. java
  13. '''
复制代码
find

除了 find_all 方法,还有 find 方法,不过 find 方法返回的是单个元素,也就是第一个匹配的元素,而 find_all 返回的是所有匹配的元素组成的列表。示例如下:
  1. html5='''
  2.    
  3.         <h4>Hello</h4>
  4.    
  5.    
  6.         <ul  id="list-1">
  7.             <li >钢铁</li>
  8.             <li >知识</li>
  9.             <li >仓库</li>
  10.         </ul>
  11.         <ul  id="list-2">
  12.             <li >python</li>
  13.             <li >java</li>
  14.         </ul>
  15.    
  16. '''
  17. from bs4 import BeautifulSoup
  18. soup = BeautifulSoup(html5, 'lxml')
  19. print(soup.find(name='ul'))
  20. print(type(soup.find(name='ul')))
  21. print(soup.find(class_='list'))
  22. '''
  23. <ul  id="list-1">
  24. <li >钢铁</li>
  25. <li >知识</li>
  26. <li >仓库</li>
  27. </ul>
  28. <class 'bs4.element.Tag'>
  29. <ul  id="list-1">
  30. <li >钢铁</li>
  31. <li >知识</li>
  32. <li >仓库</li>
  33. </ul>
  34. '''
复制代码
返回结果不再是列表形式,而是第一个匹配的节点元素,类型依然是 Tag 类型。
其它方法

另外还有许多的查询方法,用法与前面介绍的 find_all、find 方法完全相同,只不过查询范围不同,在此做一下简单的说明。
find_parents 和 find_parent:前者返回所有祖先节点,后者返回直接父节点。
find_next_siblings 和 find_next_sibling:前者返回后面所有的兄弟节点,后者返回后面第一个兄弟节点。
find_previous_siblings 和 find_previous_sibling:前者返回前面所有的兄弟节点,后者返回前面第一个兄弟节点。
find_all_next 和 find_next:前者返回节点后所有符合条件的节点,后者返回第一个符合条件的节点。
find_all_previous 和 find_previous:前者返回节点前所有符合条件的节点,后者返回第一个符合条件的节点。
CSS选择器

BeautifulSoup还提供了另外一种选择器,CSS选择器。如果对 Web 开发熟悉的话,那么对 CSS 选择器肯定也不陌生。如果不熟悉的话,可以参考 http://www.w3school.com.cn/cssref/css_selectors.asp 了解。
使用 CSS 选择器,只需要调用 select 方法,传入相应的 CSS 选择器即可,我们用一个实例来感受一下:
  1. html5='''
  2.    
  3.         <h4>Hello</h4>
  4.    
  5.    
  6.         <ul  id="list-1">
  7.             <li >钢铁</li>
  8.             <li >知识</li>
  9.             <li >仓库</li>
  10.         </ul>
  11.         <ul  id="list-2">
  12.             <li >python</li>
  13.             <li >java</li>
  14.         </ul>
  15.    
  16. '''
  17. from bs4 import BeautifulSoup
  18. soup = BeautifulSoup(html5, 'lxml')
  19. print(soup.select('.panel .panel-heading'))
  20. print(soup.select('ul li'))
  21. print(soup.select('#list-2 .element'))
  22. print(type(soup.select('ul')[0]))
  23. '''
  24. [
  25. <h4>Hello</h4>
  26. ]
  27. [<li >钢铁</li>, <li >知识</li>, <li >仓库</li>, <li >python</li>, <li >java</li>]
  28. [<li >python</li>, <li >java</li>]
  29. <class 'bs4.element.Tag'>
  30. '''
复制代码
结果为所有匹配的节点。例如select('ul li')则是所有ul节点下面的所有li节点,返回结果是列表。
select 方法同样支持嵌套选择(soup.select('ul'))、属性获取(ul['id']),以及文本获取(li.string/li.get_text())
---- 钢铁知识库 2022.08.22
结语

到此 BeautifulSoup 的使用介绍基本就结束了,最后钢铁知识库做一下简单的总结:

  • 推荐使用 LXML 解析库,速度快、容错能力强。
  • 建议使用 find、find_all 方法查询匹配单个结果或者多个结果。
  • 如果对 CSS 选择器熟悉的话可以使用 select 匹配,可以像Xpath一样匹配所有。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

忿忿的泥巴坨

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表