Python万字博文教你玩透Beautiful Soup库,不信你学不会❤️建议收藏系列❤️

Posted 孤寒者

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python万字博文教你玩透Beautiful Soup库,不信你学不会❤️建议收藏系列❤️相关的知识,希望对你有一定的参考价值。

 👻👻相信不少小伙伴们通过我的两篇万字博文的轮番轰炸已经实现了从入坑到会完全学会requests库,并且可以独立开发出属于自己的小爬虫项目!!!——爬虫之路,永无止境~👻👻


  第一篇爬虫入坑:一篇万字博文带你入坑爬虫这条不归路(你还在犹豫什么&抓紧上车) 【❤️熬夜整理&建议收藏❤️】
  第二篇爬虫库requests库详解:两万字博文教你python爬虫requests库,看完还不会我把我女朋友都给你【❤️熬夜整理&建议收藏❤️】


 😬😬那么,第二步就是从页面解析到我们想要的数据!相信小伙伴们通过我的后两篇万字博文的轮番轰炸已经深刻地学会了网页(html)结构,并能够通过XPath熟练的进行页面解析。但是又有小伙伴说了——有些网页里的数据藏得深啊!靠XPath搞不出来啊怎么办呢?😬😬


  网页结构HTML必备知识总结文:前端HTML两万字图文大总结,快来看看你会多少!【❤️熬夜整理&建议收藏❤️】
  第一个解析库XPath库详解:万字博文教你python爬虫必备XPath库,看完还不会我把我女朋友都给你【❤️建议收藏系列❤️】


 😜😜解析不出来数据,原因无非有二:一是你功力不够(要多加练习哦!);二是XPath也不是万能的,终究有它也不行的时候(虽然它已经够牛笔了!)。所以,应粉丝们需求——本博主再来传授一技绝学:Beautiful Soup!!!😜😜


             重点来啦!重点来啦!! 💗💗💗

  对于一个网页来说,都有一定的特殊结构和层级关系,而且很多节点都有id或class来做区分,所以借助它们的结构和属性来提取不是很香吗?说的好!Beautiful Soup这一强大的解析工具,它就是因此而诞生的,它就是借助网页的结构和属性等特性来解析网页的哦!下面我们就来走进Beautiful Soup的世界!

学好解析库,网页数据任我取!!!


❤️Beautiful Soup万字博文详解❤️

1.BeautifulSoup

(1)简介:

BeautifulSoup 是一个可以从HTML或XML文件中提取数据的Python库,它的使用方式相对于正则来说更加的简单方便,常常能够节省我们大量的时间。它借助网页的结构和属性等特征来解析网页!

  🙉官方为其解释如下🙉:

  •  Beautiful Soup提供一些简单的,Python式的函数来处理导航,搜索,修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单所以不需要多少代码就可以写出一个完整的应用程序。
  •  Beautiful Soup自动将输入文档转换为Unicode编码,输出文档转换为UTF-8编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时你仅仅需要说明一下原始编码方式就可以了。
  •  Beautiful Soup已成为和lxml一样出色的Python解释器,为用户灵活地提供不同的解析策略或强劲的速度。

(小提示:Beautiful Soup我们也简称为bs4哦!别听到这个名就不认识了哈!)

官方中文文档在此!

(2)安装:

  😻CMD命令安装😻(一句命令一步到位【妈妈再也不用担心我不会安库啦!】):

pip install beautifulsoup4

(3)解析器:

  需要注意的是使用BeautifulSoup需要指定解析器(因为Beautiful Soup在解析时实际上依赖解析器,它除了支持Python标准库中的HTML解析器外,还支持一些第三方解析器【比如lxml】)

  BeautifulSoup解析网页必须要指定一个可用的解析器哦!以下是几种主要的解析器:

  由于这个解析的过程在大规模的爬取中是会影响到整个爬虫系统的速度的,并且通过以上对比可以看出,lxml解析器有解析HTML和XML的功能,而且速度快,容错能力强,所以推荐使用它,而本博主一贯使用的也是它哦!注意——lxml需要单独安装(也是CMD一句命令搞定!):

pip install lxml
温馨小提示:如果一段HTML或XML文档格式不正确的话,那么在不同的解析器中返回的结果可能是不一样的,所以一定要指定一个确定的解析器!

(4)实例引入:

(我们定义一个html_doc字符串模拟爬取到的待解析的页面数据)

html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>

<p class="story">...</p>
"""

from bs4 import BeautifulSoup

# 第一个参数:上述HTML字符串;第二个参数:解析器的类型(使用lxml)——完成BeautifulSoup对象的初始化!
soup = BeautifulSoup(html_doc, "lxml")   # 将html转化为可操作的对象
print(type(soup))                     

  输出如下(会发现已经是一个BeautifulSoup对象,下面我们就可以调用soup的各个方法和属性对其进行为所欲为的解析了!):


  首先来个骚操作——调用prettify()方法功能将要解析的字符串以标准的缩进格式输出。

print(soup.prettify())

  输出为:

<html>
 <head>
  <title>
   The Dormouse's story
  </title>
 </head>
 <body>
  <p class="title" name="dromouse">
   <b>
    The Dormouse's story
   </b>
  </p>
  <p class="story">
   Once upon a time there were three little sisters; and their names were
   <a class="sister" href="http://example.com/elsie" id="link1">
    Elsie
   </a>
   ,
   <a class="sister" href="http://example.com/lacie" id="link2">
    Lacie
   </a>
   and
   <a class="sister" href="http://example.com/tillie" id="link3">
    Tillie
   </a>
   ;
and they lived at the bottom of a well.
  </p>
  <p class="story">
   ...
  </p>
 </body>
</html>

  我们观察输出和原HTML字符串发现——对于不标准的HTML字符串BeautifulSoup可以自动更正其格式(小伙伴们想想看是不是上一个etree模块也可以!)。但是需要注意的一点是:这一自动更正其格式的操作是在初始化BeautifulSoup时就完成了,而不是调用prettify()方法造就的哦!

(5)各种常用操作/选择器详解:

初级阶段——必备操作:

1.练气期——基操勿6:

练气期(吸纳天地灵气,入体化为元力,寿元可达至百来岁;丹田为气状,初步掌握灵气术法运用,已具备神识,不
能辟谷,体内元气并不能支撑太久的御剑飞行)
print(soup.title) 		  # 获取title标签
print(soup.title.name)    # 获取title标签的标签名         获取节点名称,调用name属性即可!
print(soup.title.string)  # 获取title标签的文本内容
print(soup.title.parent)  # 获取title标签的父标签
print(soup.find(id="link2"))  # 找到id=link2的标签

2.筑基期——Tag对象:

筑基期(丹田为液态状,可辟谷,神识倍增,寿元可达两百余岁,不能遁光飞行,能长时间御剑飞行)
tag对象可以说是BeautifulSoup中最为重要的对象,通过BeautifulSoup来提取数据基本都围绕着这个对象来进行操作——我们通过选择器选择的结果都是Tag类型!
两大作用:一是Tag具有一些属性,比如上面展示的string属性:获取节点的文本内容;二是Tag类型的基础上再次选择得到的依然还是Tag类型,我们就可以无限套娃,这样就实现了嵌套选择!
print("我是Tag对象:", type(soup.a))  

# 1.获取标签             仅获取第一个符合条件的标签,其他后面的节点都会忽略!
print(soup.a)           # 输出为:<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

# 2.获取属性             仅获取第一个符合条件的标签的属性值
print(soup.a["href"])   # 输出为:http://example.com/elsie

拓展——获取属性骚操作:
1.每个节点可能有多个属性,比如id和class等,选择这个节点元素后,可以调用attrs获取所有属性:
print(soup.p.attrs)
print(soup.p.attrs['name'])

2.可以看到,attrs的返回结果是字典形式。如果要获取name属性,就相当于从字典中获取某个键值。也可以不写attrs而直接像上述一样,直接在节点元素后面加中括号,传入属性名:
print(soup.p['name'])
print(soup.p['class'])

3.注意:有的返回字符串,有的返回结果是字符串组成的列表。比如:name属性的值是唯一的,返回的结果就是单个字符串;而对于class,一个节点元素可能有多个class,所以返回的是列表!

3.金丹期——获取文本内容:

金丹期(丹田内有规则的圆形固态丹元,已辟谷,寿元可达五百载,可驾遁光飞行)
# 第一种方法:
print(soup.a.text)          
print(soup.a.get_text())

# 第二种方法:
print(soup.body.get_text())  # 注意:这个方法会获取选中标签下的所有文本内容

中级阶段——关联选择:

在实际使用中,我们很多时候不能做到一步就宣导想要的节点元素,这时就需要先选中一个节点元素,然后再以它为基准选择它的子节点,父节点,兄弟节点等,这就是接下来要讲解的关联选择:

战前准备:

  先获取到整个body标签,后续在body里进行嵌套选择:

body = soup.body  # 获取整个body标签
print(body)


1.元婴期——子代标签的选择:

元婴期(破丹成婴,寿元可达千年岁月,遁光飞行,破体元婴可瞬移)
contents和children:
通过contents可以获取某个节点所有的直接子节点,包括里面的NavigableString对象。获取的子节点是列表格式;
而通过children同样的是获取某个节点的所有直接子节点,但是返回结果是生成器类型,这种方式会比列表格式更加的节省内存。
①contents
print(body.contents)

②children
tags = body.children   # 打印body.children可知这是个迭代器。获取的同样也是所有直接子节点。
print(tage)


可以使用for循环输出迭代器内容,也可转类型为列表直接输出其所有内容:

拓展——简要讲一下NavigableString对象:

  NavigableString的意思是可以遍历的字符串,一般被标签包裹在其中的的文本就是NavigableString格式。我们在此类型的基础上再次选择得到的依然还是此类型,每次返回的结果都相同!这样就可以做到嵌套选择(前面所讲的Tag对象也可这样耍——实现嵌套选择)!

print(type(soup.body.get_text()))  # 输出为:<class 'str'>
print(type(soup.p.string)) 		   # 输出为:<class 'bs4.element.NavigableString'>


2.化神期——后代标签的选择:

化神期(体内元婴倍增,元婴向元神过渡,寿元可超两千余年,掌握元力领域,可破元婴瞬移)
descendants:
contents和children获取的是某个节点的直接子节点,而无法获得子孙节点。
通过descendants可以获得所有子孙节点,返回的结果跟children一样,需要迭代或者转类型使用。
tags_des = body.descendants  # 打印body.descendants可知这是个生成器。获取的是后代(子孙)标签
print(list(tags_des))

3.炼虚期——兄弟标签的选择:

炼虚期(寿元可达近五千岁,体内元婴化为元神,返虚出本体,化为分身或众多分身)
兄弟节点——指的是父节点相同的节点。
next_sibling(获取节点的下一个兄弟元素) 和 previous_sibling(获取节点的上一个兄弟元素); next_siblings 和 previous_siblings(加s是获取上级或者下级所有的兄弟节点)
注意:如果选中标签在body子节点的第一个位置,那么它没有上一级标签;同理,如果选中标签在body子节点的最后一个位置,那么它没有下一级标签。

①先获取body标签里的第一个p标签:

p = body.p  # 可以一层层获取指定标签,但是也只可以获取第一个符合条件的标签
print(p)

②获取符合条件的第二个标签,使用.next_sibling方法(注意:上一步选中的body标签里的第一个p标签的往下第一个兄弟节点是\\n!):

print(p.next_sibling.next_sibling)             

③获取符合条件的同级标签的上一个标签,使用.previous_sibling方法(注意:body标签的往上第一个兄弟节点是\\n!):

print(body.previous_sibling.previous_sibling)


4.合体期——父节点标签的选择:

合体期(分身与本体合二为一,达到返朴归真,初掌神通,可破炼虚万千化身,寿元可达上万年载)
父节点parent和parents:
有时我们也需要去获取某个节点的父节点,也就是包裹着当前节点的节点;
使用parent可以获得当前节点的父亲元素;而使用parents则可以获得当前节点递归到顶层的所有父辈元素。
①parent——只能获取选中标签的父亲节点:
p = body.p
print(p.parent) 

②parents——可以获取选中标签的所有的父辈元素:
p_parents = p.parents     
print(p_parents)            #打印可知这是个生成器
print(list(p_parents))

5.大乘期——信息的提取:

大乘期(寿元可达两万余年,熟练使用或自创神通,法力肉身已具备飞升上界的条件,谌为修士大能)
string和strings:
我们常常会遇到需要获取某个节点中的文本值的情况,如果这个节点中只有一个字符串,那么使用string可以正常将其取出。 而如果这个节点中有多个字符串的时候,BeautifulSoup就无法确定要取出哪个字符串了,这时候需要使用strings。
拓展:使用stripped_strings可以将全是空白的行去掉。
①string——提取当前节点中含有单个字符串的情况:
p = body.p
print(p.string)

②strings——提取当前节点中含有多个字符串的情况:
gg = body.strings  # 返回的是一个generator(生成器)
print(type(gg))
print(list(gg))

骚操作——使用stripped_strings将全是空白的行去掉:
print(list(body.stripped_strings))  # 会发现这个返回的相比上面直接用strings的区别:没有了空白

究极阶段——方法选择器(渡劫期):

渡劫期(凡人向仙人过渡的境界,修士飞升上界以后,初掌天地法则,体内元力逐步向仙元力或仙灵力转换,失败则
为渡劫修士,随岁月而消亡;转换成功,则融会贯通天地法则,可与天地同寿,是为仙)
以上方法都是通过属性来选择,速度快是快!但是如果要选取的节点位置十分复杂的话就会很麻烦很麻烦(很多时候只能适用于比较简单的一些场景,)!!!所以BeautifulSoup还提供了搜索整个文档树的方法find_all()。
源码:
        def find_all(self, name=None, attrs={}, recursive=True, text=None)
    功能:获取所有符合条件的元素
    返回值:一个可迭代对象(列表)

  注意:find_all获取到的内容在列表里哦!!!

1.通过name搜索,根据节点名查询:

  find_all(‘p’)可以直接查找出整个文档树中所有的p标签,并返回列表:

print(soup.find_all("p"))


  拓展骚操作——获取所有的p标签和a标签:

print(soup.find_all(["p", "a"]))  

2.通过属性搜索,这时候我们可以通过传递给attrs一个字典参数来进行查询。

  获取p标签里class为story的p标签:

3.通过文本内容搜索:

  结果返回所有匹配text参数值的节点文本组成的列表:

print(soup.find_all(text="Elsie"))  


  获取a标签里文本为Elsie的a标签/soup.find_all(text=“Elsie”)[0].parent也可以:

print(soup.find_all("a", text="Elsie"))


拓展——获取属性值:
  获取满足条件的a标签的href:

print(soup.find_all("a", text="Elsie")[0]["href"]) 

4.限制查找范围为子节点:

find_all()方法会默认的去所有的子孙节点中搜索,而如果将recursive参数设置为False,则可以将搜索范围限制在直接子节点中。
recursive为True时可以递归拿到后代元素;为False时不可以递归只能拿到子代元素。
例如:此处:html标签下的body不管为True还是False都可以拿到,因为body是html的子代元素也是后代元素;但是如果此处是a标签,为False就拿不到东西,因为a标签是html标签的后代元素,而非子元素。
print(soup.html.find_all("body", recursive=False)) 

5.拓展——结合使用正则表达式进行查找:

通过find_all()方法查询在BeautifulSoup中,也是可以与re模块进行相互配合的,
将re.compile编译的对象传入find_all()方法,即可通过正则来进行搜索。
tags = soup.find_all(re.compile("^b"))

  查找以b开头的标签(b标签和body标签):

import re

tags = soup.find_all(re.compile("^b"))
print(tags)


升华阶段(多重渡劫):

第一部分——方法选择器:

  Beautiful Soup提供的方法选择器远远不止一个find_all()哦!与find_all()相对的是find()方法。二者唯一区别是:find_all()方法返回的是所有匹配的元素组成的列表;而find()方法返回的是第一个匹配的元素!

第二部分——CSS选择器:

  此外,如果你对WEB开发有所研究的话,这边Beautiful Soup也为你量身打造了一种选择器——CSS选择器。

1.使用方法:
只需要调用select()方法,传入相应的CSS选择器即可!
2.实战讲解:
①获取所有p标签:
print(soup.select("p")) 
print(type(soup.select("p")[0]))

注意:我们观察到通过CSS选择器选中的结果类型仍旧是Tag类型!这也就说明我们依旧可以对使用CSS选择器选中的节点为所欲为哦!(比如嵌套选择,获取属性,获取文本啥的…)

②获取p标签下面的a标签,返回结果在列表里:
print(soup.select("p>a"))

2.In The End!

从现在做起,坚持下去,一天进步一小点,不久的将来,你会感谢曾经努力的你!

本博主会持续更新爬虫基础分栏及爬虫实战分栏,认真仔细看完本文的小伙伴们,可以点赞收藏并评论出你们的读后感。并可关注本博主,在今后的日子里阅读更多爬虫文!

如有错误或者言语不恰当的地方可在评论区指出,谢谢!
如转载此文请联系我征得本人同意,并标注出处及本博主名,谢谢 !

以上是关于Python万字博文教你玩透Beautiful Soup库,不信你学不会❤️建议收藏系列❤️的主要内容,如果未能解决你的问题,请参考以下文章

万字博文教你python爬虫必备Beautiful Soup库,看完还不会我把我女朋友都给你❤️建议收藏系列❤️

万字博文教你python爬虫pyquery库详解篇

❤️万字博文教你python爬虫必备XPath库,看完还不会我把我女朋友都给你❤️建议收藏系列

❤️万字博文教你python爬虫必备XPath库,看完还不会我把我女朋友都给你❤️建议收藏系列

万字博文教你python爬虫必备XPath库,看完还不会我把我女朋友都给你❤️建议收藏系列❤️

万字博文教你搞懂java源码的日期和时间相关用法