BeautifulSoup 的引入
我们学习了正则表达式的相关用法,但是一旦正则写的有问题,可能得到的就不是我们想要的结果了,而且对于一个网页来说,都有一定的特殊的结构和层级关系,而且很多标签都有 id 或 class 来对作区分,所以我们借助于它们的结构和属性来提取不也是可以的吗?
所以,这一节我们就介绍一个强大的解析工具,叫做 BeautiSoup,它就是借助网页的结构和属性等特性来解析网页的工具,有了它我们不用再去写一些复杂的正则,只需要简单的几条语句就可以完成网页中某个元素的提取。
废话不多说,接下来我们就来感受一下 BeautifulSoup 的强大之处吧。
BeautifulSoup 简介
简单来说,BeautifulSoup 就是 Python 的一个 HTML 或 XML 的解析库,我们可以用它来方便地从网页中提取数据,官方的解释如下:
BeautifulSoup 提供一些简单的、python 式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。BeautifulSoup 自动将输入文档转换为 Unicode 编码,输出文档转换为 utf-8 编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时你仅仅需要说明一下原始编码方式就可以了。BeautifulSoup 已成为和 lxml、html6lib 一样出色的 python 解释器,为用户灵活地提供不同的解析策略或强劲的速度。
所以说,利用它我们可以省去很多繁琐的提取工作,提高解析效率。
BeautifulSoup 的安装
使用之前,我们当然需要首先说明一下它的安装方式。目前 BeautifulSoup 的最新版本是 4.x 版本,之前的版本已经停止开发了,推荐使用 pip 来安装,安装命令如下:
pip install beautifulsoup4
好,安装完成之后可以验证一下,写一段 Python 程序试验一下。
from bs4 import BeautifulSoup soup = BeautifulSoup('<p>Hello</p>', 'html.parser')print(soup.p.string)
运行结果
Hello
如果没有报错,则证明安装没有问题。
解析器:
BeautifulSoup 在解析的时候实际上是依赖于解析器的,它除了支持 Python 标准库中的 HTML 解析器,还支持一些第三方的解析器比如 lxml,下面我们对 BeautifulSoup 支持的解析器及它们的一些优缺点做一个简单的对比。
解析器使用方法优势劣势
Python 标准库 BeautifulSoup (markup, "html.parser") Python 的内置标准库、执行速度适中 、文档容错能力强 Python 2.7.3 or 3.2.2) 前的版本中文容错能力差
lxml HTML 解析器 BeautifulSoup (markup, "lxml") 速度快、文档容错能力强需要安装 C 语言库
lxml XML 解析器 BeautifulSoup (markup, "xml") 速度快、唯一支持 XML 的解析器需要安装 C 语言库
html5libBeautifulSoup (markup, "html5lib") 最好的容错性、以浏览器的方式解析文档、生成 HTML5 格式的文档速度慢、不依赖外部扩展
所以通过以上对比可以看出,lxml 这个解析器有解析 HTML 和 XML 的功能,而且速度快,容错能力强,所以推荐使用这个库来进行解析,但是这里的劣势是必须安装一个 C 语言库,它叫做 lxml,我们在这里依然使用 pip 安装即可,命令如下:
pip3 install lxml
安装完成之后,我们就可以使用 lxml 这个解析器来解析了,在初始化的时候我们可以把第二个参数改为 lxml,如下:
from bs4 import BeautifulSoup soup = BeautifulSoup('<p>Hello</p>', 'lxml') print(soup.p.string)
运行结果是完全一致的。
基本使用:
下面我们首先用一个实例来感受一下 BeautifulSoup 的基本使用:
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>""" from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') # lxml写成html.parser也可以,同样的效果,都是解析器 print(soup.prettify()) print("#"*30) print(soup.title.string)
运行结果
<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 :="" class="sister" elsie"="" example.com="" id="link1"> <!-- Elsie --> , <a href="http: class="sister" example.com="" id="link2" lacie"=""> Lacie and <a href="http: class="sister" example.com="" id="link3" tillie"=""> Tillie ;and they lived at the bottom of a well. <p class="story"> ... </p class="story"> </a href="http:> </a href="http:> </a href="http> </p class="story"> </body> </html> ############################## The Dormouse's story
首先我们声明了一个变量 html,它是一个 HTML 字符串,但是注意到,它并不是一个完整的 HTML 字符串,<body> 和 <html> 标签都没有闭合,但是我们将它当作第一个参数传给 BeautifulSoup 对象,第二个参数传入的是解析器的类型,在这里我们使用 lxml,这样就完成了 BeaufulSoup 对象的初始化,将它赋值给 soup 这个变量。
那么接下来我们就可以通过调用 soup 的各个方法和属性对这串 HTML 代码解析了。
我们首先调用了 prettify () 方法,这个方法可以把要解析的字符串以标准的缩进格式输出,在这里注意到输出结果里面包含了 </body> 和 </html> 标签,也就是说对于不标准的 HTML 字符串 BeautifulSoup 可以自动更正格式,这一步实际上不是由 prettify () 方法做的,这个更正实际上在初始化 BeautifulSoup 时就完成了。
然后我们调用了 soup.title.string,这个实际上是输出了 HTML 中 <title> 标签的文本内容。所以 soup.title 就可以选择出 HTML 中的 <title> 标签,再调用 string 属性就可以得到里面的文本了,所以我们就可以通过简单地调用几个属性就可以完成文本的提取了,是不是非常方便?
标签选择器:
刚才我们选择元素的时候直接通过调用标签的名称就可以选择节点元素了,然后再调用 string 属性就可以得到标签内的文本了,这种选择方式速度非常快,如果单个标签结构话层次非常清晰,可以选用这种方式来解析。
选择元素
下面我们再用一个例子详细说明一下它的选择方法。
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>""" from bs4 import BeautifulSoup soup = BeautifulSoup(html, 'lxml') print(soup.title) print(type(soup.title)) print(soup.title.string) print(soup.head) print(soup.p)
运行结果
<title>The Dormouse's story</title> <class 'bs4.element.Tag'> The Dormouse's story <head><title>The Dormouse's story</title></head> <p class="title" name="dromouse"><b>The Dormouse's story</b></p>
在这里我们依然选用了刚才的 HTML 代码,我们首先打印输出了 title 标签的选择结果,输出结果正是 title 标签加里面的文字内容。接下来输出了它的类型,是 bs4.element.Tag 类型,这是 BeautifulSoup 中的一个重要的数据结构,经过选择器选择之后,选择结果都是这种 Tag 类型,它具有一些属性比如 string 属性,调用 Tag 的 string 属性,就可以得到节点的文本内容了,所以接下来的输出结果正是节点的文本内容。
接下来我们又尝试选择了 head 标签,结果也是标签加其内部的所有内容,再接下来选择了 p 标签,不过这次情况比较特殊,我们发现结果是第一个 p 标签的内容,后面的几个 p 标签并没有选择到,也就是说,当有多个标签时,这种选择方式只会选择到第一个匹配的标签,其他的后面的标签都会忽略。
提取信息:
在上面我们演示了调用 string 属性来获取文本的值,那我们要获取标签属性值怎么办呢?获取标签名怎么办呢?下面我们来统一梳理一下信息的提取方式
获取名称:
可以利用 name 属性来获取标签的名称。还是以上面的文本为例,我们选取 title 标签,然后调用 name 属性就可以得到标签名称。
print(soup.title.name)
运行结果
title
属性获取:
每个标签可能有多个属性,比如 id,class 等等,我们选择到这个节点元素之后,可以调用 attrs 获取所有属性。
print(soup.p.attrs) print(soup.p.attrs['name'])
运行结果
{'class': ['title'], 'name': 'dromouse'} dromouse
BeautifulSoup 案例
基本流程:
# 创建BeautifulSoup对象
from bs4 import BeautifulSoup
# 根据HTML网页字符创建BeautifulSoup对象
soup=BeautifulSoup(
"html_doc", # HTML文档字符串
"html.parser", # HTML解析器
from_encoding="utf-8" # HTML文档的编码
)
# 搜索节点(find_all,find)
# 方法:find_all(name,attrs,string)
# 查找所有标签为a的节点
soup.find_all("a")
# 查找所有标签为a,链接符合/view/123.htm形式的节点
soup.find_all("a",href="/view/123.htm")
# 查找所有标签为div,class为abc,文字为Python的节点
soup.find_all("div",class_="abc",string="Python")
获取疯狂的蚂蚁网站首页的文章:
url="http://www.crazyant.net"
import requests
r=requests.get(url)
if r.status_code!=200:
raise Exception
html_doc=r.text
from bs4 import BeautifulSoup
soup=BeautifulSoup(html_doc,"html.parser")
h2_nodes=soup.find_all("h2",class_="entry-title")
for h2_node in h2_nodes:
link=h2_node.find("a")
print(link["href"],link.get_text())
爬取三国演义小说所有文章:
from bs4 import BeautifulSoup
import requests
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36'}
url = 'https://www.shicimingju.com/book/sanguoyanyi.html'
page_text = requests.get(url=url,headers=headers).content.decode("utf-8")
soup = BeautifulSoup(page_text,'html.parser')
li_list = soup.select('.book-mulu > ul > li')
fp = open('./三国演义小说.txt','w',encoding='utf-8')
for li in li_list:
title = li.a.string
detail_url = 'https://www.shicimingju.com'+li.a['href']
detail_page_text = requests.get(url=detail_url,headers=headers).content.decode("utf-8")
detail_soup = BeautifulSoup(detail_page_text, 'html.parser')
div_tag = detail_soup.find('div',class_='chapter_content')
content = div_tag.text
fp.write('\n' + title + ':' + content +'\n')
print(title,'爬取成功')