BeautifulSoup提供一些简单的、python式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单所以不需要多少代码就可以写出一个完整的应用程序。BeautifulSoup自动将输入文档转换为Unicode编码,输出文档转换为utf-8编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,此时BeautifulSoup就不能自动识别编码方式了。这时只需要说明一下原始编码方式就可以了。
BeautifulSoup已成为和lxml、html6lib一样出色的python解释器,为用户灵活地提供不同的解析策略或强劲的速度。
两种安装方法:
pip install BeautifulSoup4
easy_install BeautifulSoup4
创建BeautifulSoup对象时需要传递一个解析的类库,这里使用lxml,故需要安装:
pip install lxml
#!/usr/bin/python
脚本语言的第一行,目的就是指出你想要该文件中的代码用什么可执行程序去运行它
#!/usr/bin/python 是告诉操作系统执行这个脚本的时候调用/usr/bin下的python解释器
#!/usr/bin/env python这种用法是为了防止操作系统用户没有将python装在默认的/usr/bin路径里。当系统看到这一行的时候,首先会到env设置里查找python
的安装路径,再调用对应路径下的解释器程序完成操作。这种写法会去环境变量设置中查找python的安装路径,推荐这种写法
# -*- coding: UTF-8 -*-
指定文件的编码格式为utf-8,必须放在python文件的第一行或第二行。如果没有声明则默认是ASCII编码方式。
声明编码方式的3种格式:
1.带等号的:
# coding=<encoding name>
2.最常见的,带冒号的:
#!/usr/bin/python
# -*- coding: <encoding name> -*-
3.vim的:
#!/usr/bin/python
# vim: set fileencoding=<encoding name> :
直接上用法的实例
#!/usr/bin/python
# -*- coding: UTF-8 -*-
from bs4 import BeautifulSoup
import re # 正则表达式模块
html = """
<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>
"""
soup = BeautifulSoup(html, 'lxml')
print(soup.prettify()) # 格式化输出soup对象的内容
# 格式化后的内容是
"""
<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>
"""
# 输出第一个 title 标签的标签名及其内容
print(soup.title) # <title>The Dormouse's story</title>
# 输出第一个 title 标签的标签名
print(soup.title.name) # title
# 输出第一个 title 标签的文本内容。只有在此标签下没有子标签或者只有一个子标签的情况下才能返回其文本内容,否则返回None
print(soup.title.string) # The Dormouse's story
print(soup.p.string) # The Dormouse's story
print(soup.html.string) # None
'''
string输出的其实是不包括注释符号的,如果处理不当,会有问题,如果网页原文本是:
<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
的话,print(soup.a.string)输出的也是Elsie,这是可能不是我们想要的,故需要做下特殊处理,可以先看下它的类型:
print(type(soup.a.string)) # <class 'bs4.element.Comment'>
所以可以做类型过滤:
if(type(soup.a.string) == bs4.element.Comment):
print(soup.a.string)
'''
# 输出第一个 body 标签中所有子孙节点的文本内容生成器对象
print(soup.body.strings) # <generator object _all_strings at 0x02AF2B10>
for string in soup.body.strings:
print(string)
# 输出结果
'''
The Dormouse's story
Once upon a time there were three little sisters; and their names were
Elsie
,
Lacie
and
Tillie
;
and they lived at the bottom of a well.
...
'''
# 可以看到strings属性输出的字符串中包含了很多空格和空行,所以可以使用stripped_strings属性去除空格和空行
for string in soup.body.stripped_strings:
print(string)
# 输出结果
'''
The Dormouse's story
Once upon a time there were three little sisters; and their names were
Elsie
,
Lacie
and
Tillie
;
and they lived at the bottom of a well.
...
'''
# 输出第一个 title 标签的父标签的标签名
print(soup.title.parent.name) # head
# 输出第一个 p 标签的标签名及其内容
print(soup.p) # <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
# 输出第一个 p 标签的所有属性信息
print(soup.p.attrs) # {'class': ['title'], 'name': 'dromouse'}
# 输出第一个 p 标签的所有直接子节点列表
print(soup.p.contents) # [<b>The Dormouse's story</b>]
# 输出第一个 p 标签的第一个子节点
print(soup.p.contents[0]) # <b>The Dormouse's story</b>
# 输出第一个 p 标签的直接子节点,是一个list生成器对象,所以可以循环访问
print(soup.p.children) # <list_iterator object at 0x005E5650>
# 循环输出第一个 p 标签的子节点信息
for child in soup.p.children:
print(child) # <b>The Dormouse's story</b>
'''
contents和children属性都是仅包含tag的直接子节点,
descendants属性可以对所有tag的子孙节点进行递归循环,返回的是生成器对象,可以循环访问
'''
# 输出第一个 body 标签的所有子孙节点对象
print(soup.body.descendants) # <generator object descendants at 0x02B21B10>
# 输出第一个 body 标签的所有子孙节点的信息
for child in soup.body.descendants:
print(child)
'''
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<b>The Dormouse's story</b>
The Dormouse's story
下面省略, 由上面输出的第一个子节点可以看出,先打印子标签,然后一层层剥离。
'''
# 输出第一个 p 标签的 class 属性的值
print(soup.p['class']) # ['title']
print(soup.p.attrs['class']) # ['title']
'''
soup的属性可以被添加、删除或修改。soup的属性操作方法与字典一样
'''
# 输出第一个 a 标签的 href 属性值
print(soup.a['href']) # http://example.com/elsie
print(soup.a.get('href')) # http://example.com/elsie
# 修改第一个 a 标签的 href 属性值为 http://www.baidu.com/,并打印
soup.a['href'] = 'http://www.baidu.com/'
print(soup.a['href']) # http://www.baidu.com/
# 给第一个 a 标签添加 name 属性,并打印
soup.a['name'] = '百度'
print(soup.a) # <a class="sister" href="http://www.baidu.com/" id="link1" name="百度">Elsie</a>
# 刪除第一个 a 标签的 class 属性,并打印
del soup.a['class']
print(soup.a) # <a href="http://www.baidu.com/" id="link1" name="百度">Elsie</a>
'''
find_all(name, attrs, recursive, text, **kwargs)
find_all()方法搜索符合过滤条件的当前tag的所有tag子节点
1.name参数:可以查找所有标签名字为 name 的标签列表
'''
# 输出所有的 a 标签,以列表形式显示
print(soup.find_all('a'))
# 输出结果
'''
[<a href="http://www.baidu.com/" id="link1" name="百度">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 标签下的 href 属性的内容
for link in soup.find_all('a'):
print(link.get('href'))
# 输出结果
'''
http://www.baidu.com/
http://example.com/lacie
http://example.com/tillie
'''
# 输出标签中有 属性值是 sister 且标签名是 a 的标签列表
print(soup.find_all('a', 'sister'))
# 输出结果:
'''
[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
'''
# 正则匹配,输出标签名以 b 开头的标签列表
print(soup.find_all(re.compile('^b')))
# 输出结果
'''
[<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://www.baidu.com/" id="link1" name="百度">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>The Dormouse's story</b>]
'''
# 循环输出标签名字以 b 开头的标签的标签名
for tag in soup.find_all(re.compile('^b')):
print(tag.name)
# 输出结果
'''
body
b
'''
# 同时查询多个标签
print(soup.find_all(['a', 'b']))
# 输出结果
'''
[<b>The Dormouse's story</b>,
<a href="http://www.baidu.com/" id="link1" name="百度">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>]
'''
# 传递方法查询,传递时只需要传递方法名
def has_class_and_id(tag):
return tag.has_attr('class') and tag.has_attr('id')
# 查询同时包含 class 和 id 属性的标签列表
print(soup.find_all(has_class_and_id))
# 输出结果
'''
[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
'''
'''
2.关键字参数kwargs和属性参数attrs
'''
# 查找所有 id 属性值为 link3 的标签列表
print(soup.find_all(id='link3')) # [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
# 传入多个参数,由于 class 是python的关键字,故 class属性不能直接用,需要在末尾加上下划线,即 class_
print(soup.find_all(class_='sister', id='link2')) # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
# 传入正则表达式,查找所有 href 属性值为 tillie 的标签列表
print(soup.find_all(href=re.compile('tillie'))) # [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
'''
有些tag属性在搜索时不能使用,如HTML5中的data-*属性
data_soup = BeautifulSoup('<div data-foo="value">foo!</div>', 'lxml')
print(data_soup.find_all(data-foo='value'))
上面例子会报:SyntaxError: keyword can't be an expression异常
但是可以通过find_all()方法的attrs参数定义一个字典参数来搜索包含特殊属性的tag,如:
'''
data_soup = BeautifulSoup('<div data-foo="value">foo!</div>', 'lxml')
print(data_soup.find_all(attrs={'data-foo':'value'})) # [<div data-foo="value">foo!</div>]
# 上面的print(soup.find_all(id='link3'))语句也相当于:
print(soup.find_all(attrs={'id':'link3'}))
'''
3.recursive参数:调用tag的find_all()方法时,BeautifulSoup会检索当前tag的所有子孙节点,
如果只想搜索tag的直接子节点的话可以使用参数 recursive=False,如:
'''
for tag in soup.html.find_all(re.compile('^b'), recursive=False):
print(tag.name) # body
'''
4.text参数:搜索文档中等于text参数的文本内容
'''
# 搜索文本内容包含 class 字符串的文本
print(soup.find_all(text='class')) # []
print(soup.find_all(text='story')) # []
print(soup.find_all(text="The Dormouse's story")) # ["The Dormouse's story", "The Dormouse's story"]
'''
5.limit参数:find_all()方法返回所有的搜索结果列表,可以使用limit参数设定想要结果的数量,如:
'''
# 查找 tag为 a 的标签列表,最多要两个
print(soup.find_all('a', limit=2))
# 输出结果
'''
[<a href="http://www.baidu.com/" id="link1" name="百度">Elsie</a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
'''
'''
find( name , attrs , recursive , text , **kwargs )
find()方法与find_all()方法的唯一区别是find_all()方法返回的是一个元素列表,而find()方法返回的是第一个匹配到的元素
'''
# 查找第一个 id 属性等于 link3 的标签及其内容
print(soup.find(id = 'link3')) # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
'''
CSS选择器
在写CSS时,标签名不加任何修饰,类名前加点,id名前加#
这里我们也可以用类似的方法来筛选元素, select方法返回的结果与find_all()方法一样都是列表形式。
'''
# 1.通过标签名查找
# 查找标签名为 title 的标签列表
print(soup.select('title')) # [<title>The Dormouse's story</title>]
# 2.通过类名查找
# 查找类名即class的属性值为 sister 的标签列表
print(soup.select('.sister'))
# 输出结果
'''
[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
'''
# 3.通过id名查找
# 查找id属性值为 link1 的标签列表
print(soup.select('#link1')) # [<a href="http://www.baidu.com/" id="link1" name="百度">Elsie</a>]
# 4.组合查找
# 组合查找即和写class文件时标签名与类名、id名进行的组合原理是一样的,两个条件之间用空格隔开
# 查找 p 标签中, id 等于 link1的标签列表
print(soup.select('p #link1')) # [<a href="http://www.baidu.com/" id="link1" name="百度">Elsie</a>]
# 直接子标签查找
# 查找 head 标签下所有的 title 标签列表
print(soup.select('head > title')) # [<title>The Dormouse's story</title>]
# 5.属性查找
# 查找时还可以加入属性元素,属性需要用中括号括起来,由于属性和标签属于同一个节点,故中间不能加空格,否则无法匹配
print(soup.select("a[id='link1']")) # [<a href="http://www.baidu.com/" id="link1" name="百度">Elsie</a>]
# 获取标签中所有文本内容,包括子孙节点的内容
print(soup.get_text())
# 输出结果
'''
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;
and they lived at the bottom of a well.
...
'''