利用Python爬取中国科学技术部人类遗传资源管理7000+数据
- 一、需求
- 1. 目的
- 2. 需求分析
- 方法一
- 方法二
- 二、爬虫实现
- 1. 整体思路
- 2. 抓取每张表格的网址
- 3. 抓取每张表格的网页
- 4. 解析爬取到的网页
- 5. 保存数据到Excel
- 6. 完整代码
- 三、总结
一、需求
1. 目的
把中国科学技术部网站下,科技部门户 > 办事服务 > 行政许可 > 人类遗传资源管理 > 结果查询 网址,里面所有的结果保存到一张Excel里面。如下图所示:
2. 需求分析
总共有95个链接,共计93张表,其中有两个链接没有表格。
方法一
直接打开一个新的Excel,一个一个链接去打开,然后复制、粘贴表格内容。
优点:简单粗暴、不用动脑,数据的准确性可靠
缺点:机械化操作、比较枯燥,而且遇到一些有换行符的表格,粘贴会分行,处理耗时巨大。
一开始我想着爬虫应该比较合适做这样的事,结果leader说爬虫同事说这个表格复制粘贴比较好,爬虫写起来会麻烦。我想数据量不算大,先按leader的建议,直接复制粘贴,等完成任务,回头再试试爬虫代码去爬,结果到了第20个,出现了换行符的情况,并且后面很多张表都是这样。这样每张表都要一行一行去重新复制、粘贴、删除,工作量将大大增加。所以只好先暂停,试试爬虫的处理方式。
方法二
利用爬虫爬取。接下来主要就是讲解爬虫处理的思路。
二、爬虫实现
1. 整体思路
- 拿到每张表格的网址
- 依次爬取每张表格的网页
- 解析爬取到的网页
- 保存到Excel中
2. 抓取每张表格的网址
通过右击检查,如下图,href的值就是表格的网址。再查看一下源码,发现总共有115个这样的标签,而网页明明显示只有95个。这是因为它把第一页的20个表格重复了。所以95+20=115。所以后续抓取的时候,一定要注意抓取过的不能再抓。
因为这个国家官网没有什么反爬措施,所以requests+re直接搞定。因为比较简单,直接放出代码:
def get_url_name_list(self):
"""
获取每张表的url和name
:return: [(url, name), ..., (url, name)]
"""
response = requests.get(self.start_url, headers=self.headers)
# print(response.status_code)
# 网页是'gb2312'编码
content = response.content.decode("gb2312")
# 正则进行匹配
pattern = re.compile(r'<a target="_blank" href=".(.*?)" >(.*?)</a>')
# findall 返回一个list,元素是一个元组(url, name),用finditer返回一个迭代器
url_name_list = pattern.findall(content)
return url_name_list
这里稍微注意一下,我返回的是一个包含元组的列表。
3. 抓取每张表格的网页
抓取每张表格的网页,这个跟第一个基本一样,比较简单,直接用requests解决。
上代码:
def get_html(self, goal_url):
"""
请求包含表格内容的网页
:param goal_url: 表格所在的网址
:return:
"""
response = requests.get(goal_url, headers=self.headers)
return response.content
4. 解析爬取到的网页
因为要抓取的是表格内容,它的标签层级非常明显,所以我用lxml进行处理,然后xpath解析。
这里主要麻烦的地方在于,93张表格的格式不统一,所以解析的时候就需要用不同的解析方法,我要用四个解析函数,其中后面两个是单独为两张表写的,因为第一次爬,那两张表报错了。
这里稍微再介绍一下第四个方法。其他的表格都是一个tr标签是一行,但是已批准的人类遗传资源行政许可项目信息汇总(2017年第十四批)
这张表的网页写的就不一样,表头用了两个tr标签,后面的一行数据用三个tr标签来写。所以就有了那一串的if
,后面的数据是3个tr一个循环算一条记录。
5. 保存数据到Excel
保存数据这里我直接用的openpyxl。需要注意openpyxl会自动覆盖掉已有的Excel文件,相当于先删除,再写入,不是追加写入,所以一定要注意不要重名,避免数据被误删了。这个里面我还用了set()
对于已爬取的链接进行判断,避免重复抓取。
6. 完整代码
最后我把上面的代码封装成了类进行调用,因为代码里面注释也比较清楚,所以不赘述了,完整代码如下:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Created on 2019/8/1 13:37
@author: Jock
"""
import re
import requests
from lxml import etree
import openpyxl
class YiChuanSpider(object):
def __init__(self):
"""
初始化: 起始url,base_url, headers
:return:
"""
self.start_url = 'http://www.most.gov.cn/bszn/new/rlyc/jgcx'
self.base_url = 'http://www.most.gov.cn/bszn/new/rlyc/jgcx/{}'
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
}
def get_url_name_list(self):
"""
获取每张表的url和name
:return: [(url, name), ..., (url, name)]
"""
response = requests.get(self.start_url, headers=self.headers)
# print(response.status_code)
# 网页是'gb2312'编码
content = response.content.decode("gb2312")
# 正则进行匹配
pattern = re.compile(r'<a target="_blank" href=".(.*?)" >(.*?)</a>')
# findall 返回一个list,元素是一个元组(url, name),用finditer返回一个迭代器
url_name_list = pattern.findall(content)
return url_name_list
def get_html(self, goal_url):
"""
请求包含表格内容的网页
:param goal_url: 表格所在的网址
:return:
"""
response = requests.get(goal_url, headers=self.headers)
return response.content
def parse_html_1(self, data):
"""
解析网页,提取表格内容,用于解析最新的表格格式
:param data:
:return:
"""
tree = etree.HTML(data)
table = tree.xpath('//tbody/tr')
# table为None,说明网页中不存在表格,不解析
if not table:
raise ValueError("网页中没有表格")
for each in table:
row = each.xpath('./td')
column_1 = row[0].xpath('./p//text()')[0]
column_2 = ''.join(row[1].xpath('./p//text()')).replace('\n', '').replace(' ', '')
column_3 = ''.join(row[2].xpath('./p//text()')).replace('\n', '').replace(' ', '')
column_4 = ''.join(row[3].xpath('./p//text()'))
column_5 = ''.join(row[4].xpath('./p//text()'))
column_6 = ''.join(row[5].xpath('./p//text()')).replace('\n', '').replace(' ', '')
column_7 = ''.join(row[6].xpath('./p//text()')).replace('\n', '').replace(' ', '')
yield [str(column_1), str(column_2) , str(column_3), str(column_4), str(column_5),str(column_6), str(column_7)]
# print([column_1, column_2 , column_3, column_4, column_5,column_6, column_7])
def parse_html_2(self, data):
"""
解析网页,提取表格内容,用于解析旧的表格格式
:param data:
:return:
"""
tree = etree.HTML(data)
table = tree.xpath('//tbody/tr')
if not table:
raise ValueError("网页中没有表格")
for each in table:
# xpath进行解析,然后结合join()方法replace()方法对数据进行简单清洗
row = each.xpath('./td')
column_1 = row[0].xpath('./p//text()')[0]
column_2 = ''.join(row[1].xpath('./p//text()')).replace('\n', '').replace(' ', '')
column_3 = ''.join(row[2].xpath('./p//text()')).replace('\n', '').replace(' ', '')
column_4 = ''.join(row[3].xpath('./p//text()'))
column_5 = ''.join(row[4].xpath('./p//text()'))
column_6 = ''.join(row[5].xpath('./p//text()')).replace('\n', '').replace(' ', '')
yield [str(column_1), str(column_2), str(column_3), str(column_4), str(column_5), str(column_6)]
# print([column_1, column_2 , column_3, column_4, column_5,column_6])
def parse_html_3(self, data):
"""
解析网页,提取表格内容,专门提取2017年第十五批
:param data:
:return:
"""
tree = etree.HTML(data)
table = tree.xpath('//tbody/tr')
# table为None,说明网页中不存在表格,不解析
if not table:
raise ValueError("网页中没有表格")
for each in table:
row = each.xpath('./td')
column_1 = row[0].xpath('.//text()')[0]
column_2 = ''.join(row[1].xpath('.//text()')).replace('\n', '').replace(' ', '')
column_3 = ''.join(row[2].xpath('.//text()')).replace('\n', '').replace(' ', '')
column_4 = ''.join(row[3].xpath('.//text()'))
column_5 = ''.join(row[4].xpath('.//text()'))
column_6 = ''.join(row[5].xpath('.//text()')).replace('\n', '').replace(' ', '')
column_7 = ''.join(row[6].xpath('.//text()')).replace('\n', '').replace(' ', '')
yield [str(column_1), str(column_2) , str(column_3), str(column_4), str(column_5),str(column_6), str(column_7)]
# print([column_1, column_2 , column_3, column_4, column_5,column_6, column_7])
def parse_html_4(self, data):
"""
解析网页,提取表格内容,专门提取2017年第十四批,这个比较麻烦
column_2被拆分为到了3个tr里面,所以除了第1,2个tr标签外,剩下的tr标签是3个一循环
:param data:
:return:
"""
tree = etree.HTML(data)
table = tree.xpath('//tbody/tr')
# table为None,说明网页中不存在表格,不解析
if not table:
raise ValueError("网页中没有表格")
# flag用来记录循环,相当于一个哨兵
flag = 0
for each in table:
flag += 1
row = each.xpath('./td')
if flag == 1:
column_1 = row[0].xpath('.//text()')[0]
column_2 = ''.join(row[1].xpath('.//text()')).replace('\n', '').replace(' ', '')
column_3 = ''.join(row[2].xpath('.//text()')).replace('\n', '').replace(' ', '')
column_4 = ''.join(row[3].xpath('.//text()'))
column_5 = ''.join(row[4].xpath('.//text()'))
column_6 = ''.join(row[5].xpath('.//text()')).replace('\n', '').replace(' ', '').replace(' ', '')
column_7 = ''.join(row[6].xpath('.//text()')).replace('\n', '').replace(' ', '')
continue
if flag == 2:
column_2_2 = row[0].xpath('.//text()')[0]
column_2 = column_2 + column_2_2
yield [str(column_1), str(column_2), str(column_3), str(column_4), str(column_5), str(column_6),
str(column_7)]
# print([column_1, column_2 , column_3, column_4, column_5,column_6, column_7])
continue
if flag % 3 == 0:
column_1 = row[0].xpath('.//text()')[0]
column_2 = ''.join(row[1].xpath('.//text()')).replace('\n', '').replace(' ', '')
column_3 = ''.join(row[2].xpath('.//text()')).replace('\n', '').replace(' ', '')
column_4 = ''.join(row[3].xpath('.//text()'))
column_5 = ''.join(row[4].xpath('.//text()'))
column_6 = ''.join(row[5].xpath('.//text()')).replace('\n', '').replace(' ', '')
column_7 = ''.join(row[6].xpath('.//text()')).replace('\n', '').replace(' ', '')
continue
if flag % 3 == 1:
column_2_1 = ''.join(row[0].xpath('.//text()')).replace('\n', '').replace(' ', '')
column_2 = column_2 + column_2_1
continue
if flag % 3 == 2:
column_2_2 = ''.join(row[0].xpath('.//text()')).replace('\n', '').replace(' ', '')
column_2 = column_2 + column_2_2
yield [str(column_1), str(column_2), str(column_3), str(column_4), str(column_5), str(column_6),
str(column_7)]
# print([column_1, column_2 , column_3, column_4, column_5,column_6, column_7])
def save(self):
"""
保存数据为CSV
:return:
"""
# 创建excel
xls = openpyxl.Workbook()
# 激活sheet
sheet = xls.active
url_name_list = self.get_url_name_list()
# 记录抓取的网址数
url_count = 0
# 记录抓取的数据条数,不计算表头
total_item = 0
# 存储已经爬取的url,避免重复爬取
crawl_url_set = set()
# 记录失败的url和name
fail_url_name_list = list()
for item in url_name_list:
goal_url = 'http://www.most.gov.cn/bszn/new/rlyc/jgcx/{}'.format(item[0])
# 不爬取爬过的网页
if goal_url not in crawl_url_set:
crawl_url_set.add(goal_url)
url_count += 1
# print("开始抓取第{}个URL:{},批次是:{}".format(url_count, goal_url, item[1]))
data = self.get_html(goal_url)
try:
rows = self.parse_html_1(data)
for row in rows:
# 把每一行写入excel
sheet.append(row)
total_item += 1
print("抓取第{}个URL:{},成功".format(url_count, goal_url))
# print(result)
except:
try:
rows = self.parse_html_2(data)
for row in rows:
# 把每一行写入excel
sheet.append(row)
total_item += 1
print("抓取第{}个URL:{},成功".format(url_count, goal_url))
except:
try:
rows = self.parse_html_3(data)
for row in rows:
# 把每一行写入excel
sheet.append(row)
total_item += 1
print("抓取第{}个URL:{},成功".format(url_count, goal_url))
except:
try:
rows = self.parse_html_4(data)
for row in rows:
# 把每一行写入excel
sheet.append(row)
total_item += 1
print("抓取第{}个URL:{},成功".format(url_count, goal_url))
except:
print("抓取第{}个URL:{},失败!!!!".format(url_count, goal_url))
fail_url_name_list.append((goal_url,item[1]))
continue
print("累计抓取{}条数据".format(total_item - (url_count-len(fail_url_name_list))))
# 关闭Excel
xls.save('all_1.xlsx')
print("抓取成功{}个url,失败{}个url,失败的url和批次如下:".format(url_count-len(fail_url_name_list), len(fail_url_name_list)))
for each in fail_url_name_list:
print("网址:{},批次:{}".format(each[0], each[1]))
if __name__ == '__main__':
spider = YiChuanSpider()
spider.save()
"""
# 测试用例
# 测试spider.get_url_name_list()
url_name_list = spider.get_url_name_list()
for each in url_name_list:
print(each)
# 测试spider.get_html(url),spider.parse_html_1(data)
url_1 = 'http://www.most.gov.cn/bszn/new/rlyc/jgcx/201907/t20190709_147573.htm'
data_1 = spider.get_html(url_1)
print(data_1)
result = spider.parse_html_1(data_1)
for i in result:
print(i)
# 测试spider.get_html(url),spider.parse_html_2(data)
url_2 = 'http://www.most.gov.cn/bszn/new/rlyc/jgcx//201507/t20150703_120511.htm'
data_2 = spider.get_html(url_2)
result = spider.parse_html_2(data_2)
for i in result:
print(i)
"""
输出结果如下:
看到数据乖乖到了Excel里面。出于对于数据的准确性,我把每张表的数据都对了一遍,避免数据不完整。
三、总结
这算是第一次用爬虫解决了工作上的任务。总结了下,爬虫在这里虽然好用,不过爬虫很可能抓取的数据不完整或者出错,所以在程序中一定要考虑充分数据的完整性,在关键的地方进行监测,输出信息,避免漏掉网页。拿到数据后,还要人工进行干预,校验。所以爬虫也是会花很大时间和精力的。具体的情况还是要具体分析,灵活选择解决方案。