一、前言
这一期Python爬虫学习博客将来学习一个强大的解析工具——Beautiful Soup,有了它我们将告别繁琐的正则表示的书写,我们利用简单的几段代码就可以从HTML文档中提取我们想要的信息了。
这是我的学习专栏:Python爬虫学习
里面有很多我在爬虫学习过程中总结的一些知识点,希望能帮助大家从中学到一点知识,我也会逐渐优化自己的博客质量,得到更多人的认可,谢谢!
好啦,废话不多说,我们一起开始今天的学习之旅叭!
二、我的环境
- 电脑系统:Windows 11
- 语言版本:Python 3.10
- 编译器:PyCharm 2022.2
三、准备工作
1、Beautiful Soup强在哪?
它是一个HTML或者XML的解析库,提供了一些简单的Python函数来进行解析处理,例如处理导航、搜索等,在使用它的时候我们不需要考虑编码问题,它会自动将输入的文档转换为Unicode编码,将输出文档转换为UTF-8编码,灵活正常的使用这个解析工具箱一样的库可以帮我们省去很多繁琐的工作。
2、下载安装
和其他库的安装一样,命令如下:
pipinstallbeautifulsoup4
3、该怎么选解析器?
Beautiful Soup需要使用解析器来完成它的解析任务的,你可以使用官方解析器也可以使用一些第三方解析器,这里我列出几个解析器及它们的优缺点。
解析器 | 优点 | 缺点 |
Python标准库 | 内置标准库,执行速度适中,文档容错能力强 | Python2.7.3或3.2.2前的版本中文容错能力差 |
LXML HTML | 速度快,文档容错能力强 | 需要安装C语言库 |
LXML XML | 速度快,唯一支持XML的解析器 | 需要安装C语言库 |
html5lib | 提供最好的容错性,以浏览器的方式解析文档,生成HTML5格式的文档 | 速度慢,不依赖外部扩展 |
解析器的使用格式都是一样的:
BeautifulSoup(网页源代码, "解析器")
最常用的解释器就是LXML,推荐使用这个。
四、Beautiful Soup的几种基本使用方法
1、节点选择器
我们可以直接使用节点名称就可以直接选择节点,然后使用string属性就可以获得节点内的文本了。
frombs4importBeautifulSoupimportrequestshtml=requests.get('http://exercise.kingname.info/exercise_bs_1.html').content.decode() soup=BeautifulSoup(html, 'lxml') print(soup.title) print("--------------------") print(type(soup.title)) print("--------------------") print(soup.title.string) print("--------------------") print(soup.head)
它运行的结果是:
<title>测试</title>--------------------<class'bs4.element.Tag'>--------------------测试--------------------<head><title>测试</title></head>
可以看出后面跟什么节点就会返回该节点的内容,另外type返回title节点的类型是“bs4.element.Tag”,这是Beautiful Soup中一个重要的数据结构,Tag有一些属性,例如string属性,调用它可以直接获得节点的文本信息。
值得注意的一点是当有多个节点时,这种选择方式只会选择第一个匹配到的节点。
2、提取各类信息
- 获取名称
使用name属性就可以获取节点的名称,例如:
print(soup.title.name)
- 它运行的结果是:
title
- 它返回了title节点的节点名称
- 获取属性
使用“节点[]”的形式去获取属性值:
print(soup.li["class"])
- 它运行的结果是:
['info']
- 这里要注意的是,如果某个属性的值是唯一的就直接返回属性值单个字符串,如果对于某个属性有多个值,例如class属性有很多值,就会以列表的形式返回,并且只会返回第一个匹配到的值。
- 获取内容
获取节点内的文本内容之前我们试过了,直接使用“节点.string”的形式匹配节点内的信息。 - 嵌套选择
经过选择器选择出来的结果都是Tag类型,所以我们可以在前一个结果的基础上再次嵌套选择下一个节点,例如:
print(soup.title.string) print(soup.head.title.string)
- 它运行的结果是:
测试测试
- 可以看出两者结果一样。
3、关联选择
- 选择子节点和子孙节点
想要获取一个节点的子节点,直接调用contents属性就行。
print(soup.div.contents)
- 它运行的结果是:
['\n', <ul><liclass="info">我需要的信息1</li><liclass="test">我需要的信息2</li><liclass="iamstrange">我需要的信息3</li></ul>, '\n']
- 可以看到,返回的结果是列表形式,所以contents属性得到的结果是直接子节点组成的列表。
我们也可也使用children属性来获得相应的信息:
print(soup.div.children) fori, childinenumerate(soup.div.children): print(child)
- 它运行的结果是:
<list_iteratorobjectat0x0000019657BBBDF0><ul><liclass="info">我需要的信息1</li><liclass="test">我需要的信息2</li><liclass="iamstrange">我需要的信息3</li></ul>
- 可以看出children返回的结果是生成器类型,需要使用for循环把结果遍历出来。
获取子孙节点的话,直接使用descendants属性:
print(soup.div.descendants) fori, childinenumerate(soup.div.descendants): print(child)
- 它运行的结果是:
<generatorobjectTag.descendantsat0x00000201C06E3F40><ul><liclass="info">我需要的信息1</li><liclass="test">我需要的信息2</li><liclass="iamstrange">我需要的信息3</li></ul><liclass="info">我需要的信息1</li>我需要的信息1<liclass="test">我需要的信息2</li>我需要的信息2<liclass="iamstrange">我需要的信息3</li>我需要的信息3
- 返回的结果也是生成器类型,仍然需要使用for循环遍历出结果。
- 选择父节点和祖先节点
获取某一节点的父节点,可以直接使用parent属性:
print(soup.li.parent)
- 它运行的结果是:
<ul><liclass="info">我需要的信息1</li><liclass="test">我需要的信息2</li><liclass="iamstrange">我需要的信息3</li></ul>
- 如果需要继续获取所有祖先节点,可以直接使用parents属性:
print(type(soup.ul.parents)) print("------------------------------------------") print(list(enumerate(soup.ul.parents)))
- 它运行的结果是:
<class'generator'>------------------------------------------[(0, <divclass="useful"><ul><liclass="info">我需要的信息1</li><liclass="test">我需要的信息2</li><liclass="iamstrange">我需要的信息3</li></ul></div>), (1, <body><divclass="useful"><ul><liclass="info">我需要的信息1</li><liclass="test">我需要的信息2</li><liclass="iamstrange">我需要的信息3</li></ul></div><divclass="useless"><ul><liclass="info">垃圾1</li><liclass="info">垃圾2</li></ul></div></body>), (2, <html><head><title>测试</title></head><body><divclass="useful"><ul><liclass="info">我需要的信息1</li><liclass="test">我需要的信息2</li><liclass="iamstrange">我需要的信息3</li></ul></div><divclass="useless"><ul><liclass="info">垃圾1</li><liclass="info">垃圾2</li></ul></div></body></html>), (3, <html><head><title>测试</title></head><body><divclass="useful"><ul><liclass="info">我需要的信息1</li><liclass="test">我需要的信息2</li><liclass="iamstrange">我需要的信息3</li></ul></div><divclass="useless"><ul><liclass="info">垃圾1</li><liclass="info">垃圾2</li></ul></div></body></html>)]
- 可以看到,返回的结果是生成器类型,如果需要返回各个结果的需要使用for循环遍历。
- 选择兄弟节点
获取同级节点的方法如下:
frombs4importBeautifulSouphtml="""<html><body> <p class="search"> 谷歌搜索 <a href="https://www.google.com" class="sister" id="link1"> <span>google</span> </a> 百度 <a href="https://www.baidu.com" class="sister" id="link2">baidu</a> 火狐 <a href="https://www.firefox.com" class="sister" id="link3">firefox</a> 三个都是常用、好用的浏览器 </p></body></html>"""soup=BeautifulSoup(html, 'lxml') print("下一个兄弟节点:", soup.a.next_sibling) print("上一个兄弟节点:", soup.a.previous_sibling) print("前面所有的兄弟节点:", list(enumerate(soup.a.next_siblings))) print("后面所有的兄弟节点:", list(enumerate(soup.a.previous_siblings)))
- 它运行的结果是:
下一个兄弟节点:百度上一个兄弟节点:谷歌搜索前面所有的兄弟节点: [(0, '\n 百度\n '), (1, <aclass="sister"href="https://www.baidu.com"id="link2">baidu</a>), (2, '\n 火狐\n '), (3, <aclass="sister"href="https://www.firefox.com"id="link3">firefox</a>), (4, '\n 三个都是常用、好用的浏览器\n ')] 后面所有的兄弟节点: [(0, '\n 谷歌搜索\n ')]
4、方法选择器
前面我们总结啦一些基础的基于属性来选择的方法,它们都适用于简单的场景,如果进行比较复杂的选择时,就会比较麻烦,所有我们需要学习一些方法选择器的使用。
- find()和find_all()的语法格式:
find_all(name, attrs, recursive, text, **kwargs)
name:就是HTML的标签名,类似于body、div、ul、li。
attrs:参数的值是一个字典,字典的Key是属性名,字典的Value是属性值,attrs={'class: ‘useful’}。
recursive:值为True或者False,当它为False的时候,BS4不会搜索子标签。
text:可以是一个字符串或者是正则表达式,用于搜索标签里面的文本信息。
**kwargs:表示Key=Value形式的参数,这种方式也可以用来根据属性和属性值进行搜索。这里的Key是属性,Value是属性值。 - find()方法
frombs4importBeautifulSouphtml="""<div class="panel"> <div class="panel-heading"> <h4>Hello World</h4> </div> <div class="panel-body"> <ul class="list" id="list-1"> <li class="element">Java</li> <li class="element">Python</li> <li class="element">C++</li> </ul> <ul class="list list-small" id="list-2"> <li class="element">Go</li> <li class="element">JavaScript</li> </ul> </div></div>"""soup=BeautifulSoup(html, 'lxml') print(soup.find(name='ul')) print("------------------------------------") print(type(soup.find(name='ul'))) print("------------------------------------") print(soup.find(class_='list'))
- 它运行的结果是:
<ulclass="list"id="list-1"><liclass="element">Java</li><liclass="element">Python</li><liclass="element">C++</li></ul>------------------------------------<class'bs4.element.Tag'>------------------------------------<ulclass="list"id="list-1"><liclass="element">Java</li><liclass="element">Python</li><liclass="element">C++</li></ul>
- find()方法返回的是第一个匹配的节点元素,类型仍然是Tag类型。
- find_all()方法
frombs4importBeautifulSouphtml="""<div class="panel"> <div class="panel-heading"> <h4>Hello World</h4> </div> <div class="panel-body"> <ul class="list" id="list-1"> <li class="element">Java</li> <li class="element">Python</li> <li class="element">C++</li> </ul> <ul class="list list-small" id="list-2"> <li class="element">Go</li> <li class="element">JavaScript</li> </ul> </div></div>"""soup=BeautifulSoup(html, 'lxml') print(soup.find_all(name='ul')) print("------------------------------------") print(type(soup.find_all(name='ul')[0]))
- 它运行的结果是:
[<ulclass="list"id="list-1"><liclass="element">Java</li><liclass="element">Python</li><liclass="element">C++</li></ul>, <ulclass="list list-small"id="list-2"><liclass="element">Go</li><liclass="element">JavaScript</li></ul>] ------------------------------------<class'bs4.element.Tag'
- find_all()方法返回的是列表类型,并且返回了所有符合条件的元素,类型也是Tag类型。
- 两者的区别:
find()与find_all()的不同点如下:
① find_all()返回的是Beautiful Soup Tag对象组成的列表,
如果没有找到任何满足要求的标签,就会返回空列表。
② find()返回的是一个Beautiful Soup Tag对象,
如果有多个符合条件的HTML标签,则返回第1个对象,
如果找不到就会返回None。
5、CSS选择器
CSS选择器直接使用select方法,然后再传入相应的CSS选择器就行。
frombs4importBeautifulSouphtml="""<div class="panel"> <div class="panel-heading"> <h4>Hello World</h4> </div> <div class="panel-body"> <ul class="list" id="list-1"> <li class="element">Java</li> <li class="element">Python</li> <li class="element">C++</li> </ul> <ul class="list list-small" id="list-2"> <li class="element">Go</li> <li class="element">JavaScript</li> </ul> </div></div>"""soup=BeautifulSoup(html, 'lxml') print(soup.select('.panel .panel-heading')) print("----------------------------------------------------------") print(soup.select('ul li')) print("----------------------------------------------------------") print(soup.select('#list-2 .element'))
它运行的结果是:
[<divclass="panel-heading"><h4>HelloWorld</h4></div>] ----------------------------------------------------------[<liclass="element">Java</li>, <liclass="element">Python</li>, <liclass="element">C++</li>, <liclass="element">Go</li>, <liclass="element">JavaScript</li>] ----------------------------------------------------------[<liclass="element">Go</li>, <liclass="element">JavaScript</li>]
熟悉Web开发的人肯定对CSS选择器不陌生,如果不懂得话,这部分可以跳过,不过学习爬虫推荐去学习一下有关前端的一些基础知识,方便我们进行数据的爬取。
五、最后我想说
本期的博客内容大致就是这么多了,如果大家觉得内容不够的话,我后续还可以补充一些,但是别人的东西总归没有自己总结的好理解一些,所以我们都需要去自己去学习上网查阅各种资料或者阅读各种书籍,来不断理解并学习透彻某一知识点。
下一期的爬虫博客,我将来总结有关pyquery的使用,进一步学习有关CSS选择器的使用,弄懂前端的一些知识,提高爬取效率。
本人水平有限,上述文章如有错误之处还请大家为我指出,谢谢!
最后,创作不易,期待得到大家的认可,谢谢大家!