bs4(Beautiful Soup 4.4.0) 是一个可以从HTML或XML文件中提取数据的Python库,其中的 BeautifulSoup类,可以用来获取html页面的内容,配合requests库可以构建简单的爬虫程序。
bs4是第三方库,需要手动安装。
一、bs4模块的基本使用方法
首先构建一个BeautifulSoup实例对象,传入Web页面的 html 文档内容(字符串格式)或 本地html 文件的句柄,传入所使用的解析器,然后就可以使用BeautifulSoup实例对象的属性和方法获取 html 文档中的相应数据了。
传入的 html 文档内容可以使用requests模块从网络中获取,也可以打开本地 html 文件进行读取,或者直接将本地 html 文件的句柄传给BeautifulSoup实例对象。
示例代码:
import requests
from bs4 import BeautifulSoup
# 使用requests模块获取Web页面的文档内容,创建BeautifulSoup实例对象
url = 'https://www.baidu.com'
resp = requests.get(url)
resp.encoding = 'utf-8'
html_doc = resp.text
soup1 = BeautifulSoup(html_doc, 'html.parser')
# 打开并读取本地html文件内容,创建BeautifulSoup实例对象
with open('test.html', encoding='utf-8') as fin:
html_doc = fin.read()
soup2 = BeautifulSoup(html_doc, 'html.parser')
# 直接传入本地html文件句柄,创建BeautifulSoup实例对象
soup3 = BeautifulSoup(open('test.html', encoding='utf-8'), 'html.parser')
print('soup1的网页标题是:', soup1.title.text)
print('soup2的网页标题是:', soup2.title.text)
print('soup3的网页标题是:', soup3.title.text)
运行结果:
soup1的网页标题是: 百度一下,你就知道
soup2的网页标题是: 测试网页
soup3的网页标题是: 测试网页
【注】上面示例代码中的html.parser,是创建BeautifulSoup实例对象时所使用的解析器。html.parser是Python自带的内置标准库,可以直接使用,另外还有第三方解析器,使用时需要手动安装。
二、解析器
Beautiful Soup 能够从 html 文档中解析出各种有用数据,例如网页标题、文本、链接等,依赖于强大的解析器。它支持Python标准库中的HTML解析器,还支持一些第三方的解析器,详细情况如下表:
解析器 | 调用方法 | 优势 | 劣势 |
Python标准库 | "html.parser" |
|
|
lxml HTML 解析器 | "lxml" |
|
|
lxml XML 解析器 | "lxml-xml" |
|
|
html5lib |
|
|
【注】以上解析器中,只有Python标准库不需要手动安装,可以直接使用"html.parser"的形式调用,其它解析器都是第三方库,需要手动安装,安装方法与其它第三方模块的安装方法相同。Python内置的"html.parser"解析器,在简单爬虫中最为常用。
三、常用属性和方法
为了方便演示和理解,我们首先创建一个html文档,然后针对该文档进行数据解析演示。
测试html文档(test.html)代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>测试网页</title>
</head>
<body>
<h1>标题1<a href="test.html">链接</a> </h1>
<h2>标题2</h2>
<h3>标题3</h3>
<h4>标题4</h4>
<h5></h5>
<div id="content" class="default">
<p>段落</p>
<a href="https://www.baidu.com" id="link1">百度</a><br />
<a href="https://www.163.com" id="link2">网易</a><br />
<a href="https://www.iqiyi.com" id="link3">爱奇艺</a><br />
<img src="https://img10.360buyimg.com/img/jfs/t1/192028/25/33459/5661/63fc2af2F1f6ae1b6/d0e4fdc2f126cbf5.png" alt="京东">
</div>
</body>
</html>
1、text属性
text属性返回标签内或html文档的全部文本内容,内容为空则返回空字符
示例代码:
from bs4 import BeautifulSoup
# 打开并读取上面创建的html文件内容,创建BeautifulSoup实例对象
with open('test.html', encoding='utf-8') as fin:
html_doc = fin.read()
soup = BeautifulSoup(html_doc, 'html.parser')
# 返回h1标签中的全部文本内容
print(soup.h1.text)
print('='*30)
# 返回整个html文档的全部文本内容
print(soup.text)
运行结果:
标题1链接
==============================
测试网页
标题1链接
标题2
标题3
标题4
段落
百度
网易
爱奇艺
2、string属性
string与text属性都是返回文本内容,但又有很大的不同。string属性返回的是标签内的文本内容,如果标签内的文本为空或者有多处文本,则返回None。
部分示例代码:
# 打印输出h1、h2和h5标签内的文本内容
print(soup.h1.string)
print(soup.h2.string)
print(soup.h5.string)
运行结果:
None
标题2
None
3、Tag标签对象属性
Tag标签对象对应 html 文档中的标签,可以返回 html 标签的相应内容
部分示例代码:
# 输出h1标签的内容
print(soup.h1)
# 输出h1标签的名称
print(soup.h1.name)
# 输出h1标签的文本内容
print(soup.h1.text)
# 输出a标签内的链接地址,只会输出html文档内的第一个a标签的内容
print(soup.a['href'])
# 输出div标签内的a标签的链接地址,第一个div里面的第一个a标签
print(soup.div.a['href'])
运行结果:
<h1>标题1<a href="test.html">链接</a> </h1>
h1
标题1链接
test.html
https://www.baidu.com
4、get_text()方法
get_text()方法的结果与text属性相同,即获取标签或整个文档的文本内容
部分示例代码:
# 输出网页标题文本内容
print(soup.title.getText())
运行结果:
测试网页
5、find()方法
find()方法是根据标签及标签中的属性值进行查找,通常用于爬虫中进行精确定位。
示例代码:
from bs4 import BeautifulSoup
# 打开并读取本地html文件内容,创建BeautifulSoup实例对象
with open('test.html', encoding='utf-8') as fin:
html_doc = fin.read()
soup = BeautifulSoup(html_doc, 'html.parser')
# 查找h1标签
data = soup.find('h1')
print(data.text)
# 根据a标签的id属性查找
data = soup.find('a', id='link3')
print(data.text)
# 根据div标签的class属性查找,由于class是python的关键字,因此使用class_进行查找
data = soup.find('div', class_='default')
print(data.text)
运行结果:
标题1链接
爱奇艺
段落
百度
网易
爱奇艺
6、find_all()方法
find_all()与find()方法的功能和使用方法基本相同,只是find_all()查找的是所有指定标签,返回一个结果集,可以通过循环进行遍历。find_all()方法也是实现批量爬取的总要手段。
示例代码:
from bs4 import BeautifulSoup
# 打开并读取本地html文件内容,创建BeautifulSoup实例对象
with open('test.html', encoding='utf-8') as fin:
html_doc = fin.read()
soup = BeautifulSoup(html_doc, 'html.parser')
# 查找所有a标签,循环输出a标签的链接地址和文本内容
links = soup.find_all('a')
for link in links:
print(link['href'], link.getText())
print('='*30)
# 先定位到div区块,再在该区块内查找,精确定位
div_node = soup.find('div', id='content')
img = div_node.find('img')
print(img['src'])
运行结果:
test.html 链接
https://www.baidu.com 百度
https://www.163.com 网易
https://www.iqiyi.com 爱奇艺
==============================
https://img10.360buyimg.com/img/jfs/t1/192028/25/33459/5661/63fc2af2F1f6ae1b6/d0e4fdc2f126cbf5.png
四、简单爬虫实例
(一)目标
链接地址:https://www.qidian.com/all/chanId21-subCateId8/
爬取上面链接地址的Web页面中的所有小说的标题以及对应的链接地址,输出到屏幕上
(二)网页内容分析
使用浏览器打开链接地址,在任意一个小说标题上点鼠标右键→检查,分屏中的代码会自动定位到该小说标题位置。经过观察分析可以确定,每个小说标题的最外层都由一对 li 标签包裹,再向里有一对class属性为'book-mid-info'的 div 标签,我们使用find_all()方法查找这个div标签,就可以获取所有的数据。如下图:
(三)实例代码
import requests
from bs4 import BeautifulSoup
url = 'https://www.qidian.com/all/chanId21-subCateId8/'
r = requests.get(url)
if r.status_code != 200:
raise Exception()
html_doc = r.text
soup = BeautifulSoup(html_doc, 'html.parser')
div_nodes = soup.find_all('div', class_='book-mid-info')
for div_node in div_nodes:
link = div_node.find('a')
print(link.get_text(), 'https:' + link['href'])
运行结果:
道诡异仙 https://book.qidian.com/info/1031794030/
万古神帝 https://book.qidian.com/info/3546912/
乱世书 https://book.qidian.com/info/1036010291/
女侠且慢 https://book.qidian.com/info/1034840014/
万相之王 https://book.qidian.com/info/1027368101/
诸界第一因 https://book.qidian.com/info/1029701421/
当不成赘婿就只好命格成圣 https://book.qidian.com/info/1034730904/
开局签到荒古圣体 https://book.qidian.com/info/1021378513/
保护我方族长 https://book.qidian.com/info/1024121691/
帝霸 https://book.qidian.com/info/3258971/
最初进化 https://book.qidian.com/info/1017021237/
白骨大圣 https://book.qidian.com/info/1020884972/
猎命人 https://book.qidian.com/info/1032636821/
人族镇守使 https://book.qidian.com/info/1026225232/
我在聊天群模拟长生路 https://book.qidian.com/info/1035776910/
修炼从简化功法开始 https://book.qidian.com/info/1034284970/
神诡世界,我能修改命数 https://book.qidian.com/info/1031996546/
从肉体凡胎到粉碎星球 https://book.qidian.com/info/1036613110/
完美世界 https://book.qidian.com/info/2952453/
苟在东宫涨天赋,发现太子女儿身 https://book.qidian.com/info/1035677938/
五、进阶案例
(一)目标
链接地址:https://m.890h.com/10_10447/
爬取链接地址中小说的所有章节,每个章节使用标题为文件名保存成一个文本文件,共19章,即生成19个文本文件。
(二)网页内容分析
- 使用浏览器打开链接地址,页面中有两部分:一是小说的最新章节,二是小说的全部章节,第二部分是我们需要爬取的内容。
- 在小说第一章标题上点击右键→检查,代码中有两个相同的div标签,内容为:<div class="directoryArea">,第一个div标签里是小说的最新章节部分,第二个div标签里面就是小说的全部章节标题和链接。
- 每个章节的小说正文都是一个静态html文件,在正文页面空白处点右键→检查,发现文字内容在一个 id 属性为'chaptercontent'的div标签中,查找该标签即可。
(三)爬虫代码分析
1、获取小说所有章节标题和链接的函数
由于我们本次要爬取的是小说的所有正文,因此第一步需要构建一个函数,用来获取小说的所有章节标题和正文链接地址。
函数代码如下:
def get_novel_chapters():
root_url = 'https://m.890h.com/10_10447/'
url_list = []
resp = requests.get(root_url)
resp.encoding = 'utf-8'
if resp.status_code != 200:
raise Exception('爬取小说目录时发生请求错误!')
soup = BeautifulSoup(resp.text, 'html.parser')
links = soup.find_all('div', class_='directoryArea')[1].find_all('a')
for link in links:
url_list.append(('https://m.890h.com' + link['href'], link.get_text()))
return url_list
注释:
- 首先使用requests库获取小说章节页面的html内容,如果状态码不等于200,即网站未正常响应,则抛出错误信息
- 创建bs4的实例对象,查找所有class属性为'directoryArea'的div标签,结果集中一共有两个数据元素,我们取第二个,即第二个div标签的内容。代码中采用soup.find_all('div', class_='directoryArea')[1] 这种形式获取,然后再继续查找所有的a标签,就是我们需要的内容
- 遍历查找结果数据集,将链接和标题以元组的形式追加到列表中,最后返回函数结果
2、获取一个章节正文内容的函数
再创建一个函数,根据传入的链接地址,获取到链接中的正文内容,并将文本内容返回给函数。
函数代码如下:
def get_chapter_content(url):
resp = requests.get(url)
resp.encoding = 'utf-8'
if resp.status_code != 200:
raise Exception('爬取小说内容时发生请求错误!')
soup = BeautifulSoup(resp.text, 'html.parser')
return soup.find('div', id='chaptercontent').get_text()
3、主程序
在主程序中,首先使用第一个函数获取所有章节的链接地址和标题。遍历所有章节内容,使用小说标题创建同名文本文件,使用第二个函数获取每个章节的正文文本内容,并写入到文本文件中。
完整代码如下:
import requests
from bs4 import BeautifulSoup
# 获取小说所有章节标题和链接的函数
def get_novel_chapters():
root_url = 'https://m.890h.com/10_10447/'
url_list = []
resp = requests.get(root_url)
resp.encoding = 'utf-8'
if resp.status_code != 200:
raise Exception('爬取小说目录时发生请求错误!')
soup = BeautifulSoup(resp.text, 'html.parser')
links = soup.find_all('div', class_='directoryArea')[1].find_all('a')
for link in links:
url_list.append(('https://m.890h.com' + link['href'], link.get_text()))
return url_list
# 获取一个章节正文内容的函数
def get_chapter_content(url):
resp = requests.get(url)
resp.encoding = 'utf-8'
if resp.status_code != 200:
raise Exception('爬取小说内容时发生请求错误!')
soup = BeautifulSoup(resp.text, 'html.parser')
return soup.find('div', id='chaptercontent').get_text()
# 主程序
if __name__ == '__main__':
for chapter in get_novel_chapters():
url, title = chapter
print('正在爬取:', title)
with open('%s.txt' % title, 'w', encoding='utf-8') as wfile:
wfile.write(get_chapter_content(url))
运行结果:
正在爬取: 第一章 重生
正在爬取: 第二章 向家小姐
正在爬取: 第三章 救下美女
正在爬取: 第四章 苏珂的相亲
正在爬取: 第五章 你可以滚了
正在爬取: 第六章 我陪你玩
正在爬取: 第七章 等五分钟
正在爬取: 第八章 推拿按摩
正在爬取: 第九章 废物老公
正在爬取: 第十章 害怕的应该不是我
正在爬取: 第十一章 再见刀疤
正在爬取: 第十二章 不受恩惠
正在爬取: 第十三章 我等皆下等
正在爬取: 第十四章 向心雨的姐姐?
正在爬取: 第十五章 我真是路过
正在爬取: 第十六章 那个男生
正在爬取: 第十七章 枯木逢春
正在爬取: 第十八章 任何条件
正在爬取: 第十九章 解决了苏珂的事
正在爬取: 第十九章 何必逞强
【注】在屏幕输出提示信息的同时,在程序目录下生成了19个文本文件,就是小说的所有内容。