beautiful soup(了解)
和 lxml 一样,Beautiful Soup 也是一个HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML 数据。
lxml 只会局部遍历,而Beautiful Soup 是基于HTML DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml。
BeautifulSoup 用来解析 HTML 比较简单,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持 lxml 的 XML解析器。
Beautiful Soup 3 目前已经停止开发,推荐现在的项目使用Beautiful Soup 4。使用 pip 安装即可:pip install beautifulsoup4
快速使用
通过下面的一个例子,对bs4有个简单的了解,以及看一下它的强大之处:
from bs4 import BeautifulSoup
html = '''
<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>
'''
#创建 Beautiful Soup 对象
soup = BeautifulSoup(html,'lxml')
#格式化输出 soup 对象的内容
print(soup.prettify())
print(soup.title)
print(soup.title.name)
print(soup.title.string)
print(soup.title.parent.name)
print(soup.p)
print(soup.p["class"])
print(soup.a)
print(soup.find_all('a'))
print(soup.find(id='link3'))
<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 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>
<title>The Dormouse's story</title>
title
The Dormouse's story
head
<p class="title"><b>The Dormouse's story</b></p>
['title']
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</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>]
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
使用BeautifulSoup解析这段代码,能够得到一个 BeautifulSoup 的对象,并能按照标准的缩进格式的结构输出。
同时我们通过下面代码可以分别获取所有的链接,以及文字内容:
for link in soup.find_all('a'):
print(link.get('href'))
print(soup.get_text())
解析器
Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,如果我们不安装它,则 Python 会使用 Python默认的解析器,lxml 解析器更加强大,速度更快,推荐安装。
下面是常见解析器: 推荐使用lxml作为解析器,因为效率更高. 在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必须安装lxml或html5lib, 因为那些Python版本的标准库中内置的HTML解析方法不够稳定。
标签选择器
<html><head><title>The 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>
上面的 title
head
a
p
等等都是 HTML 标签
print(soup.title)
print(soup.head)
print(soup.p)
print(type(soup.title))
<title>The Dormouse's story</title>
<head><title>The Dormouse's story</title></head>
<p class="title"><b>The Dormouse's story</b></p>
<class 'bs4.element.Tag'>
通过这种我们使用soup.标签名
就可以获得这个标签的内容
这里有个问题需要注意,通过这种方式获取标签,如果文档中有多个这样的标签,返回的结果是第一个标签的内容,如上面我们通过
soup.p
获取p标签,而文档中有多个p标签,但是只返回了第一个p标签内容
获取名称name
当我们通过soup.title.name
的时候就可以获得该title
标签的名称,即title
获取属性attrs
# 在这里,我们把 p 标签的所有属性打印输出了出来,得到的类型是一个字典。
print(soup.p.attrs)
# 以下这两种方式都能获取p标签的class属性值
print(soup.p.attrs['class'])
print(soup.p['class'])
{'class': ['title']}
['title']
['title']
获取内容NavigableString
既然我们已经得到了标签的内容,那么问题来了,我们要想获取标签内部的文字怎么办呢?很简单,用 .string
即可
print(soup.p.string)
# 嵌套获取
print(soup.head.title.string)
结果就可以获取第一个p标签的内容:
The Dormouse's story
The story
子节点和子孙节点
1) contents
html = """
<html>
<head>
<title>The Dormouse's story</title>
</head>
<body>
<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">
<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 they lived at the bottom of a well.
</p>
<p class="story">...</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html,'lxml')
print(soup.p.contents)
结果是将p标签下的所有子节点存入到了一个列表中
2) children
使用children
也可以获取p标签下的所有子节点内容,和通过contents
获取的结果是一样的,但是不同的地方是soup.p.children
是一个迭代对象,而不是列表,只能通过循环的方式获取素有的信息
print(soup.p.children)
for i,child in enumerate(soup.p.children):
print(i,child)
通过contents
以及children
都是获取子节点,如果想要获取子孙节点可以通过descendantsprint(soup.descendants)
同时这种获取的结果也是一个迭代器
3) 父节点和祖先节点
通过soup.a.parent
就可以获取父节点的信息
通过list(enumerate(soup.a.parents))可以获取祖先节点,这个方法返回的结果是一个列表,会分别将a标签的父节点的信息存放到列表中,以及父节点的父节点也放到列表中,并且最后还会讲整个文档放到列表中,所有列表的最后一个元素以及倒数第二个元素都是存的整个文档的信息
4) 兄弟节点
soup.a.next_siblings
获取后面的兄弟节点soup.a.previous_siblings
获取前面的兄弟节点soup.a.next_sibling
获取下一个兄弟标签souo.a.previous_sinbling
获取上一个兄弟标签
find_all
find_all(name,attrs,recursive,text,**kwargs)
可以根据标签名,属性,内容查找文档
1) name的用法
html='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
import re
soup = BeautifulSoup(html, 'lxml')
# 传字符串.在搜索方法中传入一个字符串参数,Beautiful Soup会查找与字符串完整匹配的内容
print(soup.find_all('ul'))
# 传正则表达式
print(soup.find_all(re.compile("^l")))
print(type(soup.find_all('ul')[0]))
结果返回的是一个列表的方式
[<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>, <ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>]
[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>, <li class="element">Foo</li>, <li class="element">Bar</li>]
<class 'bs4.element.Tag'>
同时我们是可以针对结果再次find_all
,从而获取所有的li标签信息
for ul in soup.find_all('ul'):
print(ul.find_all('li'))
2) attrs
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">Jay</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(attrs={'id': 'list-1'}))
print(soup.find_all(attrs={'name': 'elements'}))
[<ul class="list" id="list-1" name="elements">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>]
[<ul class="list" id="list-1" name="elements">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>]
attrs
可以传入字典的方式来查找标签,但是这里有个特殊的就是class,因为class在python中是特殊的字段,所以如果想要查找class相关的可以更改attrs={'class_':'element'}
或者soup.find_all('',{"class":"element})
,特殊的标签属性可以不写attrs
,例如id
3) text
html='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all(text='Foo'))
结果返回的是查到的所有的text='Foo'的文本
['Foo', 'Foo']
其他一些类似的用法:
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
find的用法与find_all一样,区别在于find返回 第一个符合匹配结果,find_all则返回 所有匹配结果的列表。
CSS选择器
通过select()直接传入CSS选择器就可以完成选择,熟悉前端的人对CSS可能更加了解,其实用法也是一样的
.
表示class,#
表示id 标签1,标签2 找到所有的标签1和标签2 标签1 标签2 找到标签1内部的所有的标签2[attr]
可以通过这种方法找到具有某个属性的所有标签[atrr=value]
例子[target=_blank]
表示查找所有target=_blank
的标签- 在这里我们也可以利用类似的方法来筛选元素,用到的方法是 soup.select(),返回类型是 list
html='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
# 通过类名查找
print(soup.select('.panel .panel-heading'))
# 通过标签名查找
print(soup.select('ul li'))
# 通过 id 名查找
print(soup.select('#list-2'))
# 组合查找,查找 id=list-2 中 class=element 的标签,二者需要用空格分开
print(soup.select('#list-2 .element'))
# 属性查找,属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到
print(soup.select('ul[id="list-2"]'))
[<div class="panel-heading">
<h4>Hello</h4>
</div>]
[<li class="element">Foo</li>, <li class="element">Bar</li>, <li class="element">Jay</li>, <li class="element">Foo</li>, <li class="element">Bar</li>]
[<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>]
[<li class="element">Foo</li>, <li class="element">Bar</li>]
[<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>]
获取内容
以上的 select 方法返回的结果都是列表形式,可以遍历形式输出,然后通过get_text()就可以获取文本内容
html='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">Foo</li>
<li class="element">Bar</li>
<li class="element">Jay</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">Foo</li>
<li class="element">Bar</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
for li in soup.select('li'):
print(li.get_text())
Foo
Bar
Jay
Foo
Bar
总结
- 推荐使用lxml解析库,必要时使用html.parser
- 标签选择筛选功能弱但是速度快
- 建议使用find()、find_all() 查询匹配单个结果或者多个结果
- 如果对CSS选择器熟悉建议使用select()
- 记住常用的获取属性和文本值的方法