The Dormouse's story
Once upon a time there were three little sisters; and their names were
Lacie and
and they lived at the bottom of a well.
...
"""上面这段代码不是完整的html标签,为什么呢?它没有body和html的结束标签!
没关系,bs4有容错机制!
使用BeautifulSoup解析这段代码,能够得到一个 BeautifulSoup 的对象
from bs4 importBeautifulSoup
soup= BeautifulSoup(html_doc, 'html.parser')
从文档中找到所有标签的链接:
for link in soup.find_all('a'):print(link.get('href'))
执行输出:
http://example.com/elsie
http://example.com/lacie
http://example.com/tillie
View Code
从文档中获取所有文字内容:
print(soup.get_text())
执行输出:
The Dormouse's story
The Dormouse's story
Once upon a time there were three little sisters; andtheir names were
Elsie,
LacieandTillie;andthey lived at the bottom of a well.
...
View Code
2. 标签对象
通俗点讲就是 HTML 中的一个个标签,Tag 对象与XML或HTML原生文档中的tag相同:
from bs4 importBeautifulSoup
soup= BeautifulSoup('Extremely bold','html.parser')
tag=soup.bprint(type(tag))
执行输出:
Tag的名字
soup对象再以爱丽丝梦游仙境的html_doc为例,操作文档树最简单的方法就是告诉它你想获取的tag的name.如果想获取
标签,只要用 soup.head :from bs4 importBeautifulSoup
soup= BeautifulSoup(html_doc, 'html.parser')
soup= soup.head #获取head标签
print(soup)
soup= soup.title #获取title标签
print(soup)
执行输出:
The Dormouse's story The Dormouse's storyView Code
这是个获取tag的小窍门,可以在文档树的tag中多次调用这个方法.下面的代码可以获取
标签中的第一个 标签:soup = soup.body.b #
标签中的第一个 标签print(soup) #The Dormouse's story
通过点取属性的方式只能获得当前名字的第一个tag:
soup = soup.a #第一个a标签
print(soup)#Elsie
如果想要得到所有的标签,或是通过名字得到比一个tag更多的内容的时候,就需要用到 Searching the tree 中描述的方法,比如: find_all()
soup = soup.find_all('a') #所有a标签
print(soup)
执行输出:
View Code
结果是一个列表!
我们可以利用 soup加标签名轻松地获取这些标签的内容,注意,它查找的是在所有内容中的第一个符合要求的标签。
Tag的name和attributes属性
Tag有很多方法和属性,主要是 标签对象的名称,属性,文本。
现在介绍一下tag中最重要的属性: name和attributes
名称和属性
每个tag都有自己的名字,通过.name 来获取:
from bs4 importBeautifulSoup
soup= BeautifulSoup(html_doc, 'html.parser')
soup= soup.find_all('a') #所有a标签
for link insoup:print(link.name) # 标签名
print(link.get('id')) # id属性
print(link.get('class')) # class属性
print(link.get('href')) # herf属性
print(link.attrs) # 标签所有属性
#{'href': 'http://example.com/tillie', 'class': ['sister'], 'id': 'link3'}
tag的属性可以被添加,删除或修改. 再说一次, tag的属性操作方法与字典一样
修改
from bs4 importBeautifulSoup
soup= BeautifulSoup(html_doc, 'html.parser')
soup= soup.find_all('a') #所有a标签
soup[0]['class'] = 'verybold' # 修改class属性
soup[0]['id'] = 1 # 修改id属性
print(soup[0])#Elsie
删除
soup = soup.find_all('a') #所有a标签
del soup[0]['class']
del soup[0]['id']print(soup[0])#Elsie
文本获取
标签对象的文本获取
from bs4 importBeautifulSoup
soup= BeautifulSoup(html_doc, 'html.parser')print(soup.p.string) # 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
The Dormouse's story
The Dormouse's story
Once upon a time there were three little sisters; andtheir names were
Elsie
,
LacieandTillie
;andthey lived at the bottom of a well.
...
View Code
如果tag包含了多个子节点,tag就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None,如果只有一个子节点那么就输出该子节点的文本,比如下面的这种结构,soup.p.string 返回为None,但soup.p.strings就可以找到所有文本
from bs4 importBeautifulSoup
tmp_html= """
哈哈哈哈
aaaa
bbbbb
"""soup = BeautifulSoup(tmp_html, 'html.parser')
print(soup.p.string) # p下的文本只有一个时,取到,否则为None
print(soup.p.strings) # 拿到一个生成器对象, 取到p下所有的文本内容
for i in soup.stripped_strings:
print('-->',i)
执行输出:
None
-->哈哈哈哈-->aaaa--> bbbbb
因此,使用 string,标签下的文本只能有一个,否则返回None!
而使用text,返回所有文本!
这就是tag.text和tag.string的区别!
3.遍历文档树
关于DOM数,请参考链接:
1、嵌套选择
from bs4 importBeautifulSoup
soup= BeautifulSoup(html_doc, 'html.parser')print(soup.head.title.string) #The Dormouse's story
print(soup.body.a.string) #Elsie
2、子节点、子孙节点
from bs4 importBeautifulSoup
soup= BeautifulSoup(html_doc, 'html.parser')print(soup.p.contents) #p下所有子节点
print(soup.p.children) #得到一个迭代器,包含p下所有子节点
for i,child in enumerate(soup.p.children):
print(i,child)
print(soup.p.descendants) #获取子孙节点,p下所有的标签都会选择出来
for i,child in enumerate(soup.p.descendants):
print(i,child)
3、父节点、祖先节点
print(soup.a.parent) #获取a标签的父节点
print(soup.a.parents) #找到a标签所有的祖先节点,父亲的父亲,父亲的父亲的父亲...
soup.a.parents 最终返回的节点是...。也就是说,它直接回到顶层了!
4、兄弟节点
print('=====>')print(soup.a.next_sibling) #下一个兄弟
print(soup.a.previous_sibling) #上一个兄弟
print(list(soup.a.next_siblings)) #下面的兄弟们=>生成器对象
print(soup.a.previous_siblings) #上面的兄弟们=>生成器对象
4. 搜索文档树
BeautifulSoup定义了很多搜索方法,这里着重介绍2个: find() 和 find_all() .其它方法的参数和用法类似
1、五种过滤器
搜索文档树:BeautifulSoup定义了很多搜索方法,这里着重介绍2个: find() 和 find_all() .其它方法的参数和用法类似
html_doc = """
The Dormouse's storyThe Dormouse's story
Once upon a time there were three little sisters; and their names were
Lacie and
and they lived at the bottom of a well.
...
"""from bs4 importBeautifulSoup
soup=BeautifulSoup(html_doc,'lxml')
五种过滤器: 字符串、正则表达式、列表、True、方法
1.1、字符串:即标签名
print(soup.find_all('b'))
执行输出:
[The Dormouse's story]
1.2、正则表达式
importreprint(soup.find_all(re.compile('^b'))) #找出b开头的标签,结果有body和b标签
执行输出:
[
The Dormouse's story
Once upon a time there were three little sisters; andtheir names wereElsie,Lacie and
Tillie;and they lived at the bottom of a well.
...
, The Dormouse's story]
View Code
1.3、列表
如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回.下面代码找到文档中所有标签和标签:
print(soup.find_all(['a','b']))
执行输出:
[The Dormouse's story, Elsie, Lacie, Tillie]
View Code
1.4、True
可以匹配任何值,下面代码查找到所有的tag,但是不会返回字符串节点
print(soup.find_all(True))for tag insoup.find_all(True):print(tag.name)
这个不怎么用,可以忽略掉!
1.5、方法
如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数 ,如果这个方法返回 True 表示当前元素匹配并且被找到,如果不是则反回 False
from bs4 importBeautifulSoup
soup=BeautifulSoup(html_doc,'lxml')def has_class_but_no_id(tag): # 有class但是没有id属性的标签
return tag.has_attr('class') and not tag.has_attr('id')
print(soup.find_all(has_class_but_no_id)) # 将每一个标签传给这个方法过滤
执行输出:
[
Once upon a time there were three little sisters; andtheir names wereElsie,Lacie and
Tillie;and they lived at the bottom of a well.
,...
]View Code
最终输出的,都是有class属性,但是没有id属性的标签
2、find_all()
语法
find_all( name , attrs , recursive , text , **kwargs )
2.1、name
搜索name参数的值可以使任一类型的 过滤器 ,字符窜,正则表达式,列表,方法或是 True
from bs4 importBeautifulSoup
soup=BeautifulSoup(html_doc,'lxml')import re
print(soup.find_all(name=re.compile('^t')))
执行输出:
[
The Dormouse's story]2.2、keyword
key=value的形式,value可以是过滤器:字符串 , 正则表达式 , 列表, True
print(soup.find_all(id=re.compile('my'))) #查找id含有my属性的标签#查找herf含有lacle以及id带有数字的标签。#如果是class属性,需要使用class_,因为在python中,class是关键字!
print(soup.find_all(href=re.compile('lacie'),id=re.compile('\d')))print(soup.find_all(id=True)) #查找有id属性的标签
执行输出:
[
The Dormouse's story
][Lacie]
[
The Dormouse's story
, The Dormouse's story, Elsie, Lacie, Tillie]View Code
有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性:
data_soup = BeautifulSoup('
data_soup.find_all(data-foo="value") #报错:SyntaxError: keyword can't be an expression
但是可以通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的tag:
from bs4 importBeautifulSoup
soup=BeautifulSoup(html_doc,'lxml')
data_soup= BeautifulSoup('
2.3、按照类名查找
注意关键字是class_,class_=value,value可以是五种选择器之一
from bs4 importBeautifulSoup
soup=BeautifulSoup(html_doc,'lxml')importreprint(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的所有标签
执行输出:
[]
View Code
2.4、attrs
如果有多个属性,在attrs里面增加key-value即可
#查询p标签,class属性为story
print(soup.find_all('p',attrs={'class':'story'}))
执行输出:
[
Once upon a time there were three little sisters; andtheir names wereElsie,Lacie and
Tillie;and they lived at the bottom of a well.
,...
]View Code
2.5、text
值可以是:字符,列表,True,正则
print(soup.find_all(text='Elsie'))print(soup.find_all('a',text='Elsie'))
执行输出:
['Elsie']
[Elsie]
View Code
2.6、limit参数
如果文档树很大那么搜索会很慢.如果我们不需要全部结果,可以使用 limit 参数限制返回结果的数量.效果与SQL中的limit关键字类似,当搜索到的结果数量达到 limit 的限制时,就停止搜索返回结果
print(soup.find_all('a',limit=2))
执行输出:
View Code
2.7、recursive
调用tag的 find_all() 方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数 recursive=False
print(soup.html.find_all('a'))print(soup.html.find_all('a',recursive=False))
执行输出:
[]
View Code
备注
像调用 find_all() 一样调用tag
find_all() 几乎是Beautiful Soup中最常用的搜索方法,所以我们定义了它的简写方法. BeautifulSoup 对象和 tag 对象可以被当作一个方法来使用,
这个方法的执行结果与调用这个对象的 find_all() 方法相同,下面两行代码是等价的:
soup.find_all("a")
soup("a")
这两行代码也是等价的:
soup.title.find_all(text=True)
soup.title(text=True)
3、find()
语法
find( name , attrs , recursive , text , **kwargs )
find_all() 方法将返回文档中符合条件的所有tag,尽管有时候我们只想得到一个结果.比如文档中只有一个
标签,那么使用 find_all() 方法来查找标签就不太合适, 使用 find_all 方法并设置 limit=1 参数不如直接使用 find() 方法.下面两行代码是等价的:from bs4 importBeautifulSoup
soup=BeautifulSoup(html_doc,'lxml')print(soup.find_all('title', limit=1))#soup.find_all('title', limit=1)
print(soup.find('title'))#
The Dormouse's story唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果.
find_all() 方法没有找到目标是返回空列表, find() 方法找不到目标时,返回 None .
print(soup.find("nosuchtag"))#None
soup.head.title 是 tag的名字 方法的简写.这个简写的原理就是多次调用当前tag的 find() 方法:
print(soup.head.title)#
The Dormouse's storyprint(soup.find("head").find("title"))#
The Dormouse's story4、其他方法
5、css选择器
我们在写 CSS 时,标签名不加任何修饰,类名前加点,id名前加 #,在这里我们也可以利用类似的方法来筛选元素,用到的方法是 soup.select(),返回类型是 list
1. 通过标签名查找
from bs4 importBeautifulSoup
soup=BeautifulSoup(html_doc,'lxml')print(soup.select("title")) #[
The Dormouse's story]print(soup.select("b")) #[The Dormouse's story]
2. 通过类名查找
print(soup.select(".sister"))'''[Elsie,
Tillie]'''
3. 通过 id 名查找
print(soup.select("#link1"))#[Elsie]
4. 组合查找
组合查找即和写 class 文件时,标签名与类名、id名进行的组合原理是一样的,例如查找 p 标签中,id 等于 link1的内容,二者需要用空格分开
print(soup.select("p #link2"))#[Lacie]
直接子标签查找
print(soup.select("p > #link2"))#[Lacie]
5. 属性查找
查找时还可以加入属性元素,属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到。
print(soup.select("a[href='http://example.com/tillie']"))#[Tillie]
select 方法返回的结果都是列表形式,可以遍历形式输出,然后用 get_text() 方法来获取它的内容:
for title in soup.select('a'):print(title.get_text())'''Elsie
Lacie
Tillie'''
5. 修改文档树
二、Xpath模块
xpath简介
XPath在Python的爬虫学习中,起着举足轻重的地位,对比正则表达式 re两者可以完成同样的工作,实现的功能也差不多,但XPath明显比re具有优势,在网页分析上使re退居二线。
XPath介绍
是什么? 全称为XML Path Language 一种小型的查询语言
说道XPath是门语言,不得不说它所具备的优点:
可在XML中查找信息
支持HTML的查找
通过元素和属性进行导航
python开发使用XPath条件: 由于XPath属于lxml库模块,所以首先要安装库lxml。
XPath的简单调用方法:
from lxml importetree
selector=etree.HTML(源码) #将源码转化为能被XPath匹配的格式
selector.xpath(表达式)#返回为一列表
Xpath语法
查询
html_doc = """
Title百度