解析库就是在爬虫时自己制定一个规则,帮助我们抓取想要的内容时用的。常用的解析库有re模块的正则、beautifulsoup、pyquery等等。正则完全可以帮我们匹配到我们想要住区的内容,但正则比较麻烦,所以这里我们会用beautifulsoup。
beautifulsoup
Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库。它能够通过你喜欢的转换器实现惯用的文档导航、查找、修改文档的方式。Beautiful Soup会帮我们节省数小时甚至数天的工作时间。Beautiful Soup 3 目前已经停止开发,官网推荐在现在的项目中使用Beautiful Soup 4。
安装:
pip install beautifulsoup4
Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器。其中一个是 lxml 。我们平常在使用中推荐使用lxml。另一个可供选择的解析器是纯Python实现的 html5lib , html5lib的解析方式与浏览器相同,
pip install lxml pip install html5lib
下表列出了主要的解析器,以及它们的优缺点,官网推荐使用lxml作为解析器,因为效率更高. 在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必须安装lxml或html5lib, 因为那些Python版本的标准库中内置的HTML解析方法不够稳定.
解析器 | 使用方法 | 优势 | 劣势 |
---|---|---|---|
Python标准库 | BeautifulSoup(markup, "html.parser") |
|
|
lxml HTML 解析器 | BeautifulSoup(markup, "lxml") |
|
|
lxml XML 解析器 |
BeautifulSoup(markup, ["lxml", "xml"]) BeautifulSoup(markup, "xml") |
|
|
html5lib | BeautifulSoup(markup, "html5lib") |
|
|
中文文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
基本使用
容错处理:BeautifulSoup文档的容错能力指的是在html代码不完整的情况下,使用该模块可以识别该错误。使用BeautifulSoup解析某些没写完整标签的代码会自动补全该闭合标签,得到一个 BeautifulSoup 的对象,并能按照标准的缩进格式的结构输出。
举个栗子:

html_doc = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title"><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 soup=BeautifulSoup(html_doc,'lxml') #具有容错功能,第二个参数是解析器名,这里我们确定用lxml res=soup.prettify() #处理好缩进,结构化显示 print(res)
遍历文档树操作
遍历文档树:即直接通过标签名字选择,特点是选择速度快,但如果存在多个相同的标签则只返回第一个
-
1、用法
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') print(soup.p) #存在多个相同的标签则只返回第一个
- 2、获取标签的名称 ====> soup.p.name
- 3、获取标签的属性 ====> soup.p.attrs
- 4、获取标签的内容 ====> soup.p.string #p下的文本只有一个时,取到,否则为None
- 5、嵌套选择 ====> soup.body.a.string
- 6、子节点、子孙节点 ====> soup.p.contents soup.p.descendants
- 7、父节点、祖先节点 ====> soup.a.parent soup.a.parents
- 8、兄弟节点 ====>
soup.a.next_sibling #下一个兄弟 soup.a.previous_sibling#上一个兄弟 list(soup.a.next_siblings) #下面的兄弟们=>生成器对象 soup.a.previous_siblings)#上面的兄弟们=>生成器对象
具体操作示例:

#遍历文档树:即直接通过标签名字选择,特点是选择速度快,但如果存在多个相同的标签则只返回第一个 html_doc = """ <html><head><title>The Dormouse's story</title></head> <body> <p id="my p" class="title"><b id="bbb" class="boldest">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> """ #1、用法 from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') # soup=BeautifulSoup(open('a.html'),'lxml')#打开一个HTML文件 print(soup.p) #<p class="title" id="my p"><b class="boldest" id="bbb">The Dormouse's story</b></p> # 即使存在多个相同的标签也只返回第一个 #2、获取标签的名称 print(soup.p.name)#p #3、获取标签的属性 print(soup.p.attrs)#{'id': 'my p', 'class': ['title']} #4、获取标签的内容 print(soup.p.string) #The Dormouse's story p标签中的文本只有一个时,取到,否则为None print(soup.p.strings) #拿到一个生成器对象, 取到p下所有的文本内容 print(soup.p.text) #取到p下所有的文本内容 for line in soup.stripped_strings: #去掉空白 print(line) """ The Dormouse's story The Dormouse's story Once upon a time there were three little sisters; and their names were Elsie , Lacie and Tillie ; they lived at the bottom of a well. ... """ ''' 如果tag包含了多个子节点,tag就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None,如果只有一个子节点那么就输出该子节点的文本,比如下面的这种结构,soup.p.string 返回为None,但soup.p.strings就可以找到所有文本 <p id='list-1'> 哈哈哈哈 <a class='sss'> <span> <h1>aaaa</h1> </span> </a> <b>bbbbb</b> </p> ''' #5、嵌套选择 print(soup.head.title.string)#The Dormouse's story print(soup.body.a.string)#Elsie #6、子节点、子孙节点 print(soup.p.contents) # [<b class="boldest" id="bbb">The Dormouse's story</b>] p下所有子节点 print(soup.p.children) #得到一个迭代器,包含p下所有子节点 for i,child in enumerate(soup.p.children): print(i,child) """ 0 <b class="boldest" id="bbb">The Dormouse's story</b> <generator object descendants at 0x0000005FE37D3150> 0 <b class="boldest" id="bbb">The Dormouse's story</b> 1 The Dormouse's story """ print(soup.p.descendants) #获取子孙节点,p下所有的标签都会选择出来,返回一个对象 for i,child in enumerate(soup.p.descendants): print(i,child) """ 0 <b class="boldest" id="bbb">The Dormouse's story</b> 1 The Dormouse's story """ #7、父节点、祖先节点 print('dddddd',soup.a.parent) #获取a标签的父节点 """ <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> """ print(soup.a.parents) #找到a标签所有的祖先节点,父亲的父亲,父亲的父亲的父亲...返回一个对象 #8、兄弟节点 print(soup.a.next_sibling) #下一个兄弟 print(soup.a.previous_sibling) #上一个兄弟 print(list(soup.a.next_siblings)) #下面的兄弟们=>生成器对象 print(soup.a.previous_siblings) #上面的兄弟们=>生成器对象
搜索文档树操作
搜索文档树的方法主要是运用过滤器、find、CSS选择器等等,这里要注意find和find_all的区别。
- 过滤器的筛选功能相对较弱,但速度较快
- find和find_all是平常用的比较多的方法
- 前端的CSS游刃有余的前端大牛可以选择使用CSS选择器
五种过滤器
过滤器即用一种方法来获得我们爬虫想要抓取的内容。这里有5种过滤器,分别是字符串、正则、列表、True和自定义方法。下面我们进行详述
设我们从网站得到了这样一段html
html_doc = """ <html><head><title>The Dormouse's story</title></head> <body> <p id="my p" class="title"><b id="bbb" class="boldest">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 soup=BeautifulSoup(html_doc,'lxml') #这里我们用了find_all,find_all是找到所有的结果,以列表的形式返回。之后会做详述 print(soup.find_all('b'))#[<b class="boldest" id="bbb">The Dormouse's story</b>] print(soup.find_all('a')) """ [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>] """
二、正则表达式
正则表达在任何地方都适用,只要导入re模块就可以使用正则
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') import re print(soup.find_all(re.compile('^b')))#找到所有b开头的标签,结果是找到了body标签和b标签。他会将整个标签包含标签内容都返回 """ [<body> <p class="title" id="my p"><b class="boldest" id="bbb">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>, <b class="boldest" id="bbb">The Dormouse's story</b>] """
三、列表过滤器
列表过滤器的方法是将字符串过滤器中的参数由字符串变成列表,列表里面还是以字符串的形式进行过滤。列表中包含多个字符串,就会从文档中找到所有符合规则的并返回
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') print(soup.find_all(['a','b']))#找到文档中所有的a标签和b标签 """ ['<b class="boldest" id="bbb">The Dormouse's story</b>, <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>'] """
四、True过滤器
True过滤器其实是一种范围很大的过滤器,它的用法是只要满足某种条件都可以
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') print(soup.find_all(name=True))#只要是个标签就ok print(soup.find_all(attrs={"id":True}))#找到所有含有id属性的标签 print(soup.find_all(name='p',attrs={"id":True}))#找到所有含有id属性的p标签 #找到所有标签并返回其标签名 for tag in soup.find_all(True): print(tag.name)
五、自定义方法
自定义方法即自定义的过滤器,有的时候我们没有合适的过滤器时就可以写一个函数作为自定义的过滤器,该函数的参数只能是一个。
自定义函数的方法一般不常用,但我们得知道有这个方法,在特殊的情况下我们会用到。
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') #自定义函数,找到所有有class属性但没有id属性的p标签 def has_class_but_no_id(tag): res = (tag.name == 'p' and tag.has_attr("class") and not tag.has_attr('id')) return res print(soup.find_all(has_class_but_no_id))
find和find_all
find()方法和find_all()方法的用法是一样的,只不过他们搜寻的方式和返回值不一样
===>find()方法是找到文档中符合条件的第一个元素,直接返回该结果。元素不存在时返回None
===>find_all()方法是找到文档中所有符合条件的元素,以列表的形式返回。元素不存在时返回空列表
find( name , attrs={} , recursive=True , text=None , **kwargs ) find_all( name , attrs={} , recursive=True , text=None , limit=None , **kwargs ) #find_all比 find多一个参数:limit,下面会提到
下面我们就来详细说一下这五个参数
一、name参数
name即标签名,搜索name的过滤器可以是上述5中过滤器的任何一种
from bs4 import BeautifulSoup import re soup=BeautifulSoup(html_doc,'lxml') #2.1、name: 搜索name参数的值可以使任一类型的过滤器 ,字符串,正则表达式,列表,方法或是 True . print(soup.find_all(name=re.compile('^t')))#[<title>The Dormouse's story</title>] print(soup.find(name=re.compile('^t')))#<title>The Dormouse's story</title>
二、attr参数
attr就是标签的属性,所以该查找方式就是靠属性进行过滤,过滤器也可以是任意一种
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') print(soup.find_all('p',attrs={'class':'story'}))#所有class属性中有story的p标签组成的列表,好长的说。。 print(soup.find('p',attrs={'class':'story'}))#第一个符合条件的p标签
三、recursive参数
recursive参数默认为True,指的是在搜索某标签时会自动检索当前标签的所有子孙节点。若只想搜索直接子节点,不要孙节点,可以将该参数改为false
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') print(soup.html.find_all('a'))#列表你懂的 print(soup.html.find('a'))#<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a> print(soup.html.find_all('a',recursive=False))#[] print(soup.html.find('a',recursive=False))#None
四、text参数
text即文本,也就是按文本内容搜索。text参数一般不做单独使用,都是配合着name或者attr用的,作用是进一步缩小搜索的范围
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') #找到文本Elsie,单独使用没什么意义,一般配合前面两个参数使用 print(soup.find_all(text='Elsie'))#['Elsie'] print(soup.find(text='Elsie'))#'Elsie' #找到文本是Elsie的a标签 print(soup.find_all('a',text='Elsie'))#[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>] print(soup.find('a',text='Elsie'))#<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
五、**kwargs
键值对形式的搜索条件,键是name或者某个属性,值是过滤器的形式。支持除自定义形式意外的4种过滤器
from bs4 import BeautifulSoup import re soup=BeautifulSoup(html_doc,'lxml') print(soup.find_all(id=re.compile('my')))#[<p class="title" id="my p"><b class="boldest" id="bbb">The Dormouse's story</b></p>] print(soup.find_all(href=re.compile('lacie'),id=re.compile('d')))#[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>] print(soup.find_all(id=True)) #查找有id属性的标签 ###注意!!!按照类名查找时关键字是class_,class_=value,value可以是五种过滤器 print(soup.find_all('a',class_='sister')) #查找类为sister的a标签 print(soup.find_all('a',class_='sister ssss')) #查找类为sister和sss的a标签,顺序错误也匹配不成功 print(soup.find_all(class_=re.compile('^sis'))) #查找类为sister的所有标签
注:有些特殊的标签名不能用键值对的形式搜索,但支持属性attr的方式搜索。比如HTML5中的data-****标签
res = BeautifulSoup('<div data-foo="value">foo!</div>','lxml') #print(res.find_all(data-foo="value"))#报错:SyntaxError: keyword can't be an expression # 但是可以通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的tag: print(data_soup.find_all(attrs={"data-foo": "value"}))# [<div data-foo="value">foo!</div>]
六、limit参数
limit是限制的意思,如果文档特别大而我们又不需要所有符合条件的结果的时候会导致搜索很慢。比如我们只要符合条件的前3个a标签,而文档中包含200个a标签,这种情况我们就可以用到limit参数限制返回的结果的数量,效果与SQL中的limit类似。
find_all()中有limit参数而find()中没有的原因是因为find()本身就只返回第一个结果,不存在限制的条件。
from bs4 import BeautifulSoup import re soup=BeautifulSoup(html_doc,'lxml') print(soup.find_all('a',limit=3))
扩展:
find()和find_all()几乎是Beautiful Soup中最常用的方法,所以他们具有自己的简写方法
from bs4 import BeautifulSoup import re soup=BeautifulSoup(html_doc,'lxml') soup.find_all("a") soup("a")#find_all方法的简写版本 soup.find("head").find("title")# <title>The Dormouse's story</title> soup.head.title#find方法的简写版本 soup.title.find_all(text=True)#简写了find的版本 soup.title(text=True)#find和find_all均简写了的版本
CSS选择器
CSS选择器的使用方法与CSS定位标签的方式相似。精髓就是.class 和 #id
from bs4 import BeautifulSoup soup=BeautifulSoup(html_doc,'lxml') #1、CSS选择器 print(soup.p.select('.sister')) print(soup.select('.sister span')) print(soup.select('#link1')) print(soup.select('#link1 span')) print(soup.select('#list-2 .element.xxx')) print(soup.select('#list-2')[0].select('.element')) #可以一直select,但其实没必要,select支持链式操作,所以一条select就可以了 # 2、获取属性 print(soup.select('#list-2 h1')[0].attrs) # 3、获取内容 print(soup.select('#list-2 h1')[0].get_text())
当然,BeautifulSoup是一个很成熟的大模块,不会只具有这几种方法,但其实上述方法在爬虫中使用已经游刃有余了。
BeautifulSoup不仅可以搜索文档树,还能修改文档树,但在爬虫中用不到修改的功能,所以这里我们就不赘述了。
详情可研究官网 https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html