第3章

网络爬虫App




网络爬虫能够实现数据的自动化、批量化采集操作,应用广泛。根据爬虫的功能定位,爬虫主要分为通用式与主题式两类。前者常见于各大搜索引擎,后者常见于各类主题定制应用。从软件结构上看,爬虫一般包括控制器、解析器、资源库三部分。控制器负责抓取页面,解析器负责页面内容的分类提取,资源库用于数据存储。
本章以苹果树病虫害数据的爬取为例完成一个主题定向的爬虫设计,遵循爬虫控制器、解析器和资源库的设计逻辑,采用Python自身集成的HTTP编程库urllib完成页面采集,采用第三方网页数据解析库BeautifulSoup完成页面解析,采用SQLite数据库完成数据存储。


视频讲解


3.1主模块概要设计
爬虫程序的运行逻辑如图3.1所示,包括读取URL列表、抓取页面、页面解析、写入数据库、创建数据库、下载图片六个二级子模块,输入文件input.txt中包含待爬取页面的URL地址列表,输出文件apple.db是一个SQLite数据库,存放爬取的数据。虚线箭头表示数据获取和流动的方向。


图3.1爬虫程序的运行逻辑


在NetworkProgram项目下新建子目录chapter3,在chapter3中新建主程序spider_page.py,完成其初始编码逻辑,如程序段P3.1所示,主函数main()中只包含两个打印语句,创建数据库、读取URL列表、抓取页面、页面解析、写入数据库这五个二级子模块编码完成后,再对主程序段P3.1做迭代设计。

P3.1:爬虫主程序

01import os

02import argparse

03# 主逻辑函数

04def main(database:str, input_urls:str):

05print(f'存储数据的数据库是:{database}')

06print(f'网页地址列表文件是:{input_urls}')

07if __name__ == '__main__':

08# 定义命令参数行

09parse = argparse.ArgumentParser()

10parse.add_argument('-db', '--database', help='SQLite数据库名称')

11parse.add_argument('-i', '--input', help='包含url的文件名称')

12# 读取命令参数

13args = parse.parse_args()

14database_file = args.database

15input_file = args.input

16# 调用主函数

17main(database=database_file, input_urls=input_file)







为了能够在PyCharm的IDE环境中对包含命令参数的爬虫主程序做初步测试,需要配置程序spider_page.py的运行参数,打开配置运行参数对话框,如图3.2所示,对程序spider_page.py配置命令参数i input.txt db apple.db,指定输入文件和输出文件两个参数,单击OK按钮。



图3.2配置运行参数


运行程序spider_page.py,观察输出结果,厘清主程序逻辑。



视频讲解


3.2子模块概要设计
在chapter3目录下新建Python包utility,在utility中新建用于处理URL的Python程序url_handle.py,在url_handle.py中新建三个函数: read_url()读取URL列表; get_page()抓取页面; extract_page()解析页面。读取URL列表、抓取页面和解析页面如程序段P3.2所示。

P3.2:读取URL列表、抓取页面和解析页面

01# 读取URL列表,从文本文件中读取URL

02def read_url(file_path: str):

03pass

04# 抓取页面,从Web下载页面

05def get_page(url: str):

06pass

07# 解析页面,提取需要的内容

08def extract_page(page_contents: str):

09pass

在utility包下面新建用于数据库操作的Python程序db_handle.py,在db_handle.py中新建三个函数: create_database()创建数据库和数据表; save_to_database()将页面数据写入数据库; download_image()读取图片URL,完成图片下载。数据库操作如程序段P3.3所示。

P3.3:数据库操作

01# 创建数据库

02def create_database(db_path: str):

03pass

04# 写入数据库

05def save_to_database(db_path: str, records: list):

06pass

07# 根据数据库中的图片URL去下载图片

08def download_image(db_path: str, img_path: str):

09pass

各模块详细设计与实现将在随后各节一一给出。


视频讲解


3.3抓取页面
在PyCharm中选择chapter3文件夹,右击,在弹出的快捷菜单中选择New命令新建文本文件input.txt,将苹果树病虫害防治页面的URL存放其中,input.txt作为URL的种子文件存放于chapter3的根目录下。
修改、完善url_handle.py中的read_url()函数和get_page()函数设计,读取URL列表并抓取页面如程序段P3.4所示。

P3.4:读取URL列表并抓取页面

01from urllib.request import urlopen

02from bs4 import BeautifulSoup

03# 读取URL列表,从文本文件中读取URL

04def read_url(file_path: str):

05try:

06with open(file_path) as f:

07url_list = f.readlines()

08return url_list

09except FileNotFoundError:

10print(f'找不到文件{file_path}')

11exit(2)

12# 抓取页面,从Web下载页面

13def get_page(url: str):

14response = urlopen(url)

15html = response.read().decode('utf-8')

16return html

程序段P3.4首先导入urllib和BeautifulSoup库。其中,BeautifulSoup库需要在项目的虚拟环境中单独安装配置。可以通过两种方法安装BeautifulSoup: 一是在项目虚拟环境的命令窗口中执行pip install beautifulsoup4命令; 二是在PyCharm的项目解释器中配置。
read_url()函数读取指定的文本文件后,返回以行为单位的字符串列表,每一行代表一个页面的URL。
get_page()函数根据URL发送HTTP请求,获得页面响应后,读取并返回页面的HTML格式的数据内容。


视频讲解


3.4页面解析
页面解析之前,需要首先查看和理解页面的HTML结构信息,明确提取数据的结构特点,根据HTML标签的组织与排列,灵活运用BeautifulSoup库提供的页面解析函数,完成数据的提取。采用html5lib作为页面解析器,所以完成本节内容之前,需要在项目的虚拟环境中安装html5lib模块,安装命令为pip install html5lib。
苹果树病虫害页面中包含27种病虫害的名称、图片、症状、发病规律和防治方法,图3.3所示为其中一个数据片段,以第一条数据为例,数字标注部分是需要提取的内容,提取数据的逻辑通过extract_page()函数实现。


图3.3页面数据结构解析


不难看出,病虫害名称在<p><strong>标签中,症状、发病规律和防治方法在<p>标签中,图片在<p><img>中。<p>节点是所有数据的父节点,是并列关系。
修改完善url_handle.py中的extract_page()函数设计,如程序段P3.5所示。

P3.5:页面解析

01# 解析页面,提取需要的内容

02def extract_page(page_contents: str):

03article = BeautifulSoup(page_contents, 'html5lib').article  # 正文

04print('**********显示所有的数据条目**********')

05print(article.prettify())

06p_tags = article.find_all('p')  # 所有<p>节点

07p_tags = BeautifulSoup(str(p_tags), 'html5lib').find_all('p')  # 重组<p>列表

08i = 1  # 数据条目序号

09records = []  # 存放所有数据条目

10for tag in p_tags:  # 遍历

11if tag.children is not None:  # 有子节点

12for child in tag.children:  # 遍历子节点

13if f'{i}.' in str(child.string):  # 是病虫害名称

14disease_name = str(child.string)

15next_tag = tag.find_next_sibling()  # 指向下一个兄弟节点

16img_url = next_tag.img.get('src')  # 提取图片的URL

17# 向下移动标签,跳过无用的数据,遇到<p><strong>节点为止

18while True:

19next_tag = next_tag.find_next_sibling()

20if next_tag.strong:

21break

22# 提取症状

23disease_feature = ''

24if '症状' in str(next_tag.strong.string):

25while True:

26next_tag = next_tag.find_next_sibling()

27if '发病规律:' in str(next_tag.string):

28break

29disease_feature += next_tag.string

30# 提取发病规律

31disease_regular = ''

32if '发病规律:' in str(next_tag.string):

33while True:

34next_tag = next_tag.find_next_sibling()

35if '防治方法:' in str(next_tag.string):

36break

37disease_regular += next_tag.string

38# 提取防治方法

39disease_cure = ''

40if '防治方法:' in str(next_tag.string):

41while True:

42next_tag = next_tag.find_next_sibling()

43if next_tag.children is not None:

44if next_tag.strong:

45break

46disease_cure += str(next_tag.string)

47# 构建数据字典,存储提取的数据

48record = {'name': disease_name, 'feature': disease_feature, 

49'regular': disease_regular, 'cure': disease_cure, 'img_url': img_url}

50records.append(record)  # 字典数据加入列表中

51i += 1  # 条目数量加1

52return records  # 返回数据列表



视频讲解




3.5创建数据库
创建apple.db数据库,存放于chapter3的根目录下。如果安装了PyCharm专业版,则以PyCharm的数据库面板作为辅助工具,完成数据库和数据表的创建,复制数据库和数据表的SQL脚本,完成数据库的程序设计。
打开PyCharm的数据库面板,新建SQLite数据库文件apple.db。
PyCharm是用Java语言编写的,首次创建数据库时,需要安装SQLite数据库的JDBC驱动程序。SQLite是文件型数据库,其连接字符串即为文件路径,如图3.4所示。可以即时测试数据库的连通情况。


在apple.db数据库中创建数据表apples,其结构如图3.5所示,包含id、name、feature、regular、cure、img_url六个字段,其中id为主键,类型为integer,其他字段类型为text。



图3.4创建数据库并配置驱动程序




图3.5apples数据表结构



为了能够根据需要在程序中动态创建数据库和数据表,借助数据库面板的生成DDL语句功能,分别生成创建数据库和数据表的SQL语句,完成db_handle.py模块中的create_database()函数设计,如程序段P3.6所示。


P3.6:创建数据库和数据表

01import sqlite3 as lite

02# 创建数据库

03def create_database(db_path: str):

04conn = lite.connect(db_path)  # 创建或打开数据库

05with conn:

06cur = conn.cursor()  # 数据库游标

07cur.execute('drop table if exists apples')  # 删除已存在的数据表

08# 创建新的数据表apples

09ddl = 'create table apples (id integer not null  constraint apples_pk primary key

10autoincrement, name text not null,feature text,regular text,cure text

11,img_url text);'

12cur.execute(ddl)

13# 创建索引

14ddl = 'create unique index apples_id_uindex	on apples (id);'

15cur.execute(ddl)

如果没有安装PyCharm专业版,则可以采用DB Browser for SQLite完成数据库的可视化设计和DDL脚本的定义。如果对SQL非常熟悉,也可以在PyCharm社区版中直接采用程序段P3.6中给出的数据库的DDL定义。


视频讲解


3.6写入数据库
db_handle.py模块中的save_to_database()函数负责将解析页面返回的数据列表写入数据库中,如程序段P3.7所示。

P3.7:页面数据写入数据库

01# 写入数据库

02def save_to_database(db_path: str, records: list):

03conn = lite.connect(db_path)  # 打开数据库

04with conn:

05cur = conn.cursor()

06for record in records:  # 遍历数据条目表

07print(record)  # 显示当前条目

08# 根据条目名称查询数据表

09name = record['name']  # 名称

10sql = f"select count(name) from apples where name='{name}'"

11cur.execute(sql)

12count = cur.fetchone()[0]

13if count <= 0:  # 数据条目不存在

14feature = record['feature']  # 症状

15regular = record['regular']  # 发病规律

16cure = record['cure']  # 防治方法

17img_url = record['img_url']  # 图像URL

18# 数据条目插入数据表中

19sql = f"insert into apples(name, feature, regular, cure, img_url) 

20values ('{name}', '{feature}', '{regular}', '{cure}', '{img_url}')"

21cur.execute(sql)

22   print('数据存储工作已完成!')



视频讲解


3.7下载图片
数据库apple.db中已经存储了所有图片的URL,据此可以逐个下载对应的图片,为了让图片与数据条目一一对应,图片文件用id.jpg命名,例如1.jpg、2.jpg等。
图像数据的获取有多种方法,例如urllib模块或者requests模块。urllib模块内置在Python标准库中,前面抓取页面时采用的即是urllib模块。而requests是第三方库,需要用pip install requests命令单独安装。
模块urllib与requests均面向HTTP通信,requests在用法上较urllib更为简洁。图片下载逻辑由db_handle.py模块的download_image()函数实现,如程序段P3.8所示。

P3.8:下载图片

01# 根据数据库中的图片URL去下载图片

02def download_image(db_path: str, img_path: str):

03records = []

04conn = lite.connect(db_path)  # 打开数据库

05with conn:

06cur = conn.cursor()

07sql = "select id,img_url from apples"  # 所有记录

08cur.execute(sql)

09records = cur.fetchall()  # 列表包含所有数据

10print('\n开始图片下载……')

11for record in records:  # 遍历所有图片

12file = img_path + '\\' + str(record[0]) + '.jpg'  # 用ID命名文件

13img_data = requests.get(record[1])  # 获取图像数据

14with open(file, 'wb') as f:

15f.write(img_data.content)  # 保存

16print('\n已完成图片下载!')



视频讲解


3.8集成测试
创建数据库、读取URL列表、抓取页面、页面解析、写入数据库、下载图片六个二级子模块已经全部完成,重新回到主程序模块spider_page.py,完成主函数和主逻辑设计。
主函数中需要调用各个二级子模块,所以程序头部需要添加模块引用:

from utility import url_handle, db_handle

主程序逻辑如程序段P3.9所示。

P3.9:主程序逻辑

01import os

02import argparse

03from utility import url_handle, db_handle

04# 主逻辑函数

05def main(database: str, input_urls: str):

06print(f'存储数据的数据库是:{database}')

07print(f'网页地址列表文件是:{input_urls}')

08all_records = []   # 所有数据

09urls = url_handle.read_url(input_urls)  # 读取URL列表

10for url in urls:  # 遍历URL

11print('读取url:' + url)

12html = url_handle.get_page(url)  # 抓取页面

13records = url_handle.extract_page(html)  # 解析页面

14all_records.extend(records)  # 扩展数据集

15db_path = os.path.join(os.getcwd(), database)  # 数据库路径

16db_handle.create_database(db_path=db_path)  # 创建数据库

17db_handle.save_to_database(db_path=db_path, records=all_records)  # 写入数据库

18# 下载图片

19directory = 'images'

20if not os.path.exists(directory):

21os.makedirs(directory)

22img_path = os.path.join(os.getcwd(), directory)  # 图片存放路径

23download_image = threading.Thread(target=db_handle.download_image, 

24args=(db_path, img_path))

25download_image.start()

26if __name__ == '__main__':

27# 定义命令参数行

28parse = argparse.ArgumentParser()

29parse.add_argument('-db', '--database', help='SQLite数据库名称')

30parse.add_argument('-i', '--input', help='包含url的文件名称')


31# 读取命令参数

32args = parse.parse_args()

33database_file = args.database  # 数据库名称

34input_file = args.input  # 地址列表文件

35# 调用主函数

36main(database=database_file, input_urls=input_file)

项目结构如图3.6所示。


图3.6项目结构


运行主程序spider_page.py,分别在数据库、图片文件夹和控制台观察运行结果。
数据库中包含27条苹果树病虫害数据信息,图片文件夹images中包含27幅图片,图片以数据条目的ID命名,控制台上会输出数据采集过程的一些提示信息。



视频讲解


3.9小结
本章基于urllib、requests模块的HTTP数据获取能力,基于BeautifulSoup、html5lib的页面解析能力,基于SQLite的数据管理能力,完成了以苹果病虫害为主题的爬虫设计,展示了爬虫采集数据的过程与逻辑设计,为基于HTTP的网络应用与编程提供了很好的案例引导。本章爬虫生成的数据库apple.db,将直接部署到后面第5章和第6章开发的App中。
3.10习题
一、 简答题
1. 通用式爬虫与主题式爬虫有何不同?
2. 搜索引擎一般采用什么样的爬虫采集数据?
3. urllib库的作用是什么?有哪些常用函数?
4. BeautifulSoup库的作用是什么?有哪些常用函数?
5. 采用html5lib作为页面解析器的优点是什么?
6. SQLite是一种什么类型的数据库?有何特点?
7. 第三方requests库模块与Python自带的库模块urllib均面向HTTP通信,有何不同?
8. HTML页面结构有何特点?用什么方法可以根据URL获取页面内容?
9. 爬虫为什么需要对图片数据单独下载?
二、 编程题
参照本章苹果树病虫害主题爬虫的设计逻辑,自由选择一个主题,完成新主题爬虫的程序设计。爬取的文本数据存入SQLite数据库。如果有图片,下载的图片存入单独的文件夹中。