Beautiful Soup的使用
本文主要分为三个部分,首先是将网页内容转为Beautiful Soup解析对象,然后通过该对象进行节点的选择以及文本和属性的提取。
注:来源于崔庆才<<python3爬虫实战开发2>>本人学习并分享,略有补充。
安装Beautiful Soup库
pip install beautifulsoup4
Beautiful Soup 解析对象
Beautiful Soup 在解析时需要以来解析器,比如html.parser、lxml、xml、html5lib,利用他们,可以构建Beatuiful Soup对象,解析成对象以后,后面提取内容使用的方法都是一致的。
下面列出各个解析器的优劣
| 解析器 | 使用方法 | 优势 | 劣势 |
|---|---|---|---|
| Python标准库 | BeautifulSoup(markup,‘html.parser’) | Python的内置标准库、执行熟读适中、文档容错能力强、速度快、文档容错能力强 | Pyton2.7.3或3.2.2前的版本中文容错能力差,需要安装c语言库 |
| LXML HTML解析器 | BeautifulSoup(markup,‘lxml’) | 速度快、文档容错能力强 | 需要安装c语言库 |
| LXML XML解析器 | BeautifulSoup(markup,‘xml’) | 速度快、唯一支持XML的解析器 | 需要安装c语言库 |
| html5lib | BeautifulSoup(markup,‘html5lib’) | 提供最好的容错性、以浏览器的方式解析文档、生成HTML5格式的文档 | 速度慢、不依赖外部扩展 |
LXML解析器有解析HTML和XML的功能,并且速度快,容错能力强,所以推荐使用它
下面我们来尝试一下
from bs4 import BeautifulSoup
soup = BeautifulSoup("<p>hello</p>",'lxml')
print(type(soup))
print(soup)
print(soup.prettify())
输出的结果类型是bs4.BeautifulSoup对象,通过prettify()方法可以将要解析的字符串以标准的缩进格式输出
<class 'bs4.BeautifulSoup'>
下面我们都以
lxml解析器来进行解析成对象。
选择节点、提取信息(重点)
节点选择器
选择节点
from bs4 import BeautifulSoup
from bs4 import BeautifulSoup
html = """
<html>
<head>
<meta charset="utf-8">
<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 htir 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 htey lived at the bottom of a well </P>
<p class="story">.....</p>
"""
soup = BeautifulSoup(html,'lxml')
print(soup.title)
print(soup.title.string)
print(soup.head)
print(soup.p)
print(type(soup.title))
print(type(soup.title.string))
print(type(soup.head))
print(type(soup.p))
从打印结果中可以看出,有tag和NavigableString两种类型。tag类型对象有一些属性(例如:string属性)等,通过这些属性,我们轻松的获取该节点的内容
提取信息
获取名称(name)
print(soup.title.name)
print(type(soup.title.name))
title
<class 'str'>
从上面的打印结果可以看出,name属性打印的是节点的名称,倘若我们不知道节点的名称,可以使用这个得到,例如下方这个例子。
print(soup.select(".title"))
print(type(soup.select(".title")[0]))
print(soup.select(".title")[0].name)
获取属性(attrs)
print(soup.p.attrs)
print(soup.p.attrs['name'])
print(soup.p['name'])
{'class': ['title'], 'name': 'dromouse'}
dromouse
dromouse
从上面的例子中,我们通过attrs方法获取了p节点的属性,以字典的形式输出,如果要选择其中的一个属性值,可以通过中括号加属性名就可以了。当然我们也可以直接在节点的后面补上中括号加属性即可,正如第三个打印一样。
获取内容(string)
print(soup.p)
print(soup.p.string)
The Dormouse's story
我们直接通过string属性获取了第一个p节点下的内容
嵌套选择(tag)
html = """
<html><head><meta charset="utf-8"><title>The Dormouse's story</title></head><body>
"""
soup = BeautifulSoup(html,'lxml')
print(type(soup.head))
print(type(soup.head.title))
print(soup.head)
print(soup.head.title)
<class 'bs4.element.Tag'>
<class 'bs4.element.Tag'>
<head><meta charset="utf-8"/><title>The Dormouse's story</title></head>
<title>The Dormouse's story</title>
首先我们先打印了head``title两个节点选择后的类型,发现都是tag类型,我们可以在这基础上可以继续往下选择。
关联选择
在有些情况下,我们不能直接或通过属性筛选得到节点,这是后我们就要通过该节点的子节点、父节点、兄弟节点来得到我们想要的节点。
子节点和子孙节点
下面这段是下面要选择的内容,我们先选择子节点,然后再选择子孙节点
html = """
<html>
<head>
<meta charset="utf-8">
<title>The Dormouse's story</title>
</head>
<body>
<p class="story">
Once upon a time there were three little sisters;and htir names were
<a href="http://example.com/elsie" class="sister" id="link1"><span>ELSIE</span></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 htey lived at the bottom of a well.
</P>
<p class="story">.....</p>
"""
子节点
contentssoup = BeautifulSoup(html,'lxml') print(soup.p.contents)['\n\t\tOnce upon a time there were three little sisters;and htir names were\n\t\t', <a class="sister" href="http://example.com/elsie" id="link1"><span>ELSIE</span></a>, '\n', <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, ' and\n\t\t', <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>, ';\n\t\tand htey lived at the bottom of a well.\n\t\t']我们先是选择了
p节点,然后再通过contents属性得到了p节点的子节点,并以列表形式返回,当然我们可以继续往下选择,例子如下:print(type(soup.p.contents[1])) print(soup.p.contents[1].span)<class 'bs4.element.Tag'> <span>ELSIE</span>上面我们先是选择了列表中的序号为1的元素,然后打印了这个元素的类型,发现是
tag类型,然后我们在此基础上继续选择了span节点childrenprint(type(soup.p.children)) for i in enumerate(soup.p.children): print(i[0],i[1])我们先是打印了
children属性返回的类型,发现是迭代器类型,故我们用for循环输出了相应的内容。
子孙节点(
descendants)print(type(soup.p.descendants)) for i in enumerate(soup.p.descendants): print(i[0],i[1])我们先是打印了
descendants属性返回的类型,发现是生成器类型,故我们也用for循环输出。
父节点和祖先节点
下面这段是下面要选择的内容,我们先选择父节点,然后再选择祖先节点
html = """
<html>
<title>This is a title</title>
<body>
<p>
Once upon a time there were three little sisters;and their names were
<a><span>Elsie</span></a>
</p>
</body>
</html>
"""
父节点
print(type(soup.a.parent)) print(soup.a.parent))我们先是打印了
parent属性的类型,然后直接打印出这父节点本身祖先节点
print(type(soup.a.parents)) print(list(enumerate(soup.a.parents)))我们先是打印了
parents属性的类型,发现是生成器类型,然后通过enumerate方法转为enumerate类型,最后再转为list类型,并打印出。
兄弟节点
print('Next Sibling',soup.a.next_sibling)
print('prev Sibling',soup.a.previous_sibling)
print('Next Siblings',list(enumerate(soup.a.next_siblings)))
print('Next sibling',list(enumerate(soup.a.previous_siblings)))
上面两个是一个前面或后面的兄弟节点,后面两个获取了前面或后面的全部兄弟节点
提取信息
获取关联节点中的内容和属性,同样我们也可以使用string、attrs等属性获取,但它们返回的是迭代器或是生成器形式,这时候我们需要将它转为列表形式来进行提取其中的一个节点,然后在获取这一个节点中的内容和属性。
html = """
<html>
<head>
<meta charset="utf-8">
<title>The Dormouse's story</title>
</head>
<body>
<p class="story">
Once upon a time there were three little sisters;and htir names were
<a href="http://example.com/elsie" class="sister" id="link1"><span>ELSIE</span></a><a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
</P>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print('Next Sibling:')
print(type(soup.a.next_sibling))
print(soup.a.next_sibling)
print(soup.a.next_sibling.string)
print('Parent:')
print(type(soup.a.parents))
print(list(soup.a.parents)[0])
print(list(soup.a.parents)[0].attrs['class'])
对于单个节点,我们可以直接获取其中的内容,但对于迭代器生成器之类的。我们将它转为列表的形式,然后再获取其中的一个来提取其中的信息。
方法选择器
find_all()
find_all(name,attrs,recursive,text,**kwargs)
html="""
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1" name="elements">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jar</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
"""
name参数
print(soup.find_all(name='ul'))
print(type(soup.find_all(name='ul')[0]))
我们先是打印了ul节点,返回的是一个列表形式,里面有两个ul,然后我们打印其中一个的类型,发现是tag类型,那么还是可以继续选择下去,正如下方这个例子:
print(soup.find_all(name='ul')[0].li)
attrs参数
print(soup.find_all(attrs={'class':'list-small','id':'list-2'}))
print(soup.find_all(attrs={'name':'elements'}))
我们往attrs参数传入了一个字典,字典是由属性和属性值组成,可以传入多对的键值对。
当然对于常用的属性,我们可以不用写
attrs直接进行筛选,例如下面这个例子
print(soup.find_all(id='list-1'))
print(soup.find_all(class_='list-small'))
因为class是python的关键词,所以我们这里需要加下划线_来进行区分。
text参数
text参数可以用来匹配节点的文本,其传入形式可以是字符串,也可以是正则表达式对象,如下:
import re
html="""
<div class="panel">
<div class="panel-body">
<a>Hello, this is a link</a>
<a>Hello, this is a link,too</a>
</div>
</div>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.find_all(text=re.compile(('link'))))
上面我们选择了包含于正则表达式相匹配的节点文本组成的所有列表
find()
该方法与find_all()方法一致,不过返回的是单个元素,或是列表中的一个。
html="""
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1" name="elements">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jar</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
"""
print(soup.find(name='ul'))
print(type(soup.find(name='ul')))
print(soup.find(class_='list'))
上面我们先是选择了ul节点,如果使用find_all(),那么返回的是一个列表,但find()方法返回的是只有一个。并用我们打印了类型,发现是tag类型。
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:前者返回节点前面所有所有符合条件的节点,后者返回前面第一个符合条件的节点。下面这里例子是使用了条件的
find_all_nextprint(soup.find(name='ul').find_all_next(class_='list-small'))
CSS选择器
select()方法在tag类型下进行选择,返回的也是tag类型,然后再进行嵌套选择。往select()方法中传入CSS选择器即可,选择器我们可以参考菜鸟教程。
html="""
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1" name="elements">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jar</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
"""
节点选择
print(soup.select('.list-small'))
print(type(soup.select('.list-small')[0]))
上面我们选择了class为list-small的节点,返回的是一个列表,我们选择了第一个,然后打印得到是tag类型,下面我们尝试着嵌套选择
嵌套选择
print(soup.select(’.list-small’)[0].find_all_next())
我们在节点选择的基础上选择了它下面的那些节点,返回了一个列表,我们可以直接中括号加序号进行选择。
获取属性
因为select()方法返回的对象类型也是tag类型,所以我们可以使用attrs或者直接使用中括号加属性直接进行选择。
print(soup.select('.list-small')[0].attrs)
print(soup.select('.list-small')[0]['id'])
上面我们打印的attrs是字典类型,可以使用字典的选择就可以选择节点的属性值了。
获取文本
因为是tag类型,所以我们可以使用string直接选择,当然也可以使用get_text()方法。
for i in soup.select('li'):
print('get_text:',i.get_text())
print('string:',i.string)
两者选择效果一致,都可以获取到节点的文本值
总结
到这里,我们再从解析成Beautiful Soup对象,然后通过三个不同的选择方法选择了节点并且获取其中的内容和属性,当然最重要的还是他们返回的节点类型都是tag,所以可以连着使用这三种,不必太分开。
- 推荐使用LXML解析库,必要时使用html.parser
- 节点选择器筛选功能弱,但是速度快
- 建议使用
find()、find_all()方法查询匹配的单个结果或者多个结果 - 如果对CSS选择器熟悉,则可以使用select选择器