第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数据库。如果有图片,下载的图片存入单独的文件夹中。