利用Python爬取中国科学技术部人类遗传资源管理7000+数据

23 篇文章 4 订阅
2 篇文章 0 订阅

一、需求

1. 目的

把中国科学技术部网站下,科技部门户 > 办事服务 > 行政许可 > 人类遗传资源管理 > 结果查询 网址,里面所有的结果保存到一张Excel里面。如下图所示:
在这里插入图片描述

在这里插入图片描述

2. 需求分析

总共有95个链接,共计93张表,其中有两个链接没有表格。

方法一

直接打开一个新的Excel,一个一个链接去打开,然后复制、粘贴表格内容。
优点:简单粗暴、不用动脑,数据的准确性可靠
缺点:机械化操作、比较枯燥,而且遇到一些有换行符的表格,粘贴会分行,处理耗时巨大。
一开始我想着爬虫应该比较合适做这样的事,结果leader说爬虫同事说这个表格复制粘贴比较好,爬虫写起来会麻烦。我想数据量不算大,先按leader的建议,直接复制粘贴,等完成任务,回头再试试爬虫代码去爬,结果到了第20个,出现了换行符的情况,并且后面很多张表都是这样。这样每张表都要一行一行去重新复制、粘贴、删除,工作量将大大增加。所以只好先暂停,试试爬虫的处理方式。

方法二

利用爬虫爬取。接下来主要就是讲解爬虫处理的思路。

二、爬虫实现

1. 整体思路

  1. 拿到每张表格的网址
  2. 依次爬取每张表格的网页
  3. 解析爬取到的网页
  4. 保存到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里面。出于对于数据的准确性,我把每张表的数据都对了一遍,避免数据不完整。

三、总结

这算是第一次用爬虫解决了工作上的任务。总结了下,爬虫在这里虽然好用,不过爬虫很可能抓取的数据不完整或者出错,所以在程序中一定要考虑充分数据的完整性,在关键的地方进行监测,输出信息,避免漏掉网页。拿到数据后,还要人工进行干预,校验。所以爬虫也是会花很大时间和精力的。具体的情况还是要具体分析,灵活选择解决方案。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值