天天看点

Python学习笔记 | 爬虫基础之 bs4 数据解析模块及爬虫实例

bs4(Beautiful Soup 4.4.0) 是一个可以从HTML或XML文件中提取数据的Python库,其中的 BeautifulSoup类,可以用来获取html页面的内容,配合requests库可以构建简单的爬虫程序。

bs4是第三方库,需要手动安装。

Python学习笔记 | 爬虫基础之 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"
  • Python的内置标准库
  • 执行速度适中
  • 文档容错能力强
  • Python3.2.2之前版本的中文档容错能力差
lxml HTML 解析器 "lxml"
  • 速度快
  • 文档容错能力强
  • 需要安装C语言库
lxml XML 解析器 "lxml-xml"
  • 速度快
  • 唯一支持XML的解析器
  • 需要安装C语言库
html5lib
  • 最好的容错性
  • 以浏览器的方式解析文档
  • 生成HTML5格式的文档
  • 最好的容错性
  • 以浏览器的方式解析文档
  • 生成HTML5格式的文档

【注】以上解析器中,只有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标签,就可以获取所有的数据。如下图:

Python学习笔记 | 爬虫基础之 bs4 数据解析模块及爬虫实例

(三)实例代码

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个文本文件,就是小说的所有内容。