ixxmu / mp_duty

抓取网络文章到github issues保存
https://archives.duty-machine.now.sh/
115 stars 30 forks source link

[原创]中国保险行业协会 统计数据 [数据分享 数据爬虫] #1491

Closed ixxmu closed 2 years ago

ixxmu commented 2 years ago

https://mp.weixin.qq.com/s/WDdKs7lXjbYBPFDPrLkm_A

github-actions[bot] commented 2 years ago

[原创]中国保险行业协会 统计数据 [数据分享 数据爬虫] by world of statistics

介绍

文章介绍:

今天要分享的是 中国保险行业协会的统计数据分享,主要是分享数据爬取教程和数据。

免费分享的是数据爬虫的教程。如果有小白不会python代码的,可以找我要最终的数据结果。如果希望定制爬虫数据服务,也都可以私聊我。

节约时间

目前代码已经放在GitHub上了,如果觉得阅读太复杂了,可以直接翻到文末,查看代码链接。

数据介绍:

数据记录了从2009年到2018年的保险业经营报表,格式大概如下:

一共记录了107条数据,使用代码将多年的数据都整理成面板数据。大概的结果如下:

爬虫思路

爬虫步骤:就是获得所有文章的url,然后再提取每一篇的文章数据

获得所有的文章url

获得网站接口

先检查这个网站:http://www.iachina.cn/col/col41/index.html?uid=1508&pageNum=1

在chrome浏览器部分,右键打开【检查】,查看网络:

然后再刷新网页、不断的切换页面,查看下面的资源加载情况,直到看到这个资源:

然后再看这个资源的预览,就可以确定这个数据都是保存到这个接口里面:

然后复制这个资源的curl链接,步骤如下:

然后把这个复制到这个网站(https://curlconverter.com/)里面,即可将这个curl解析成python脚本。

可以看到网页的curl被转换成python脚本了。然后只要使用这个python脚本,就可以获得文章对应的url了。

但是要注意的是,因为上面那个网页的curl,提供的只是第4页的数据。而且文章一共就107个,所有,只要将参数修改一下,就可以一次性获得所有的文章url。也就是把上面的data部分修改一下:

  1. 修改前
params = (
    ('startrecord''49'),
    ('endrecord''96'),
    ('perpage''16'),
)
  1. 修改后
params = (
    ('startrecord''1'),#开始的文章位置,设置为1,代表从第1篇开始爬
    ('endrecord''140'),# 故意设置的,知道是107篇,设置大一点
    ('perpage''108'), # 故意设置的,知道是107篇,设置大一点,一次性返回所有的文章
)

解析接口数据

获得上面的网页接口,然后想办法将网页接口数据解析出来,这里直接使用bs4包就可以了。

完整的代码

话说回来,获得所有的文章的url,代码如下:

#%%
import numpy as np
import requests
from bs4 import BeautifulSoup
from tqdm import tqdm
from itertools import chain

import pandas as pd
import numpy as np
import time
import random


import re

# 这里cookie要换成你自己的
cookies = {
    'JSESSIONID''F53385626720xxxxFE646AFBC',
    '_pk_id.7.eda7''fbfbdec1aa1xxxxxxxx.',
}

headers = {
    'Connection''keep-alive',
    'Accept''application/xml, text/xml, */*; q=0.01',
    'X-Requested-With''XMLHttpRequest',
    'User-Agent''Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36',
    'Content-Type''application/x-www-form-urlencoded; charset=UTF-8',
    'Origin''http://www.iachina.cn',
    'Referer''http://www.iachina.cn/col/col41/index.html?uid=1508&pageNum=7',
    'Accept-Language''zh-CN,zh;q=0.9',
}

params = (
    ('startrecord''1'),
    ('endrecord''140'),
    ('perpage''108'),
)

data = {
  'col''1',
  'appid''1',
  'webid''1',
  'path''/',
  'columnid''41',
  'sourceContentType''8',
  'unitid''1508',
  'webname''\u4E2D\u56FD\u4FDD\u9669\u884C\u4E1A\u534F\u4F1A',
  'permissiontype''0'
}

response = requests.post('http://www.iachina.cn/module/web/jpage/dataproxy.jsp', headers=headers, params=params, cookies=cookies, data=data, verify=False)
soup = BeautifulSoup(response.text, 'lxml')
chapter_list = soup.find_all(name='a')
all_chapter_url = [f"http://www.iachina.cn{i.get('href')}" for i in chapter_list]
all_chapter_text = [i.text for i in chapter_list]

上面的代码,返回两个内容:

  1. all_chapter_url返回所有文章的url;
  2. all_chapter_text返回所有文章的标题;

解析文章

这个解析文章主要用到的是pandas.read_html函数,但是这只是第一步,后面还有三个麻烦的事情。

1. 最上面部分

最上面的那个部分,有的文章里面空了两行,有的文章空了一行,不太能确定如何剔除。后面采用的方法就是:直接剔除缺失值这个时候,不管是表格空1行还是空2行,都是可以被直接抹掉,我们只要记住后面的数据单位默认都是万元。

2. 最下面部分

最下面的注部分,也是很麻烦,因为有的备注是写到表格里面的,有的备注不在表格里面。在表格里面的备注,会导致数据变得混乱(尤其是会导致表格的列变多,生成很多乱七八糟的列)。不过这个也很好解决,因为这些被放到表格里面,每一条占用一行,这样在解析的时候,表的列名称和表的内容是一摸一样的,只要将表的列名称和内容相同的行剔除掉即可。

3. 中间部分

中间部分(绿框),这一部分有个大坑,就是一些条目都是二级详情向下钻的。比如原保险保费收入的1、财产险原保险赔付支出的1、财产险在表格解析的时候,是一样的列名称。导致在pandas.concat的时候,会报错,因为程序不知道1、财产险到底是在哪一个分支下。而且有时候每一期的分支下钻,都是不一样的,有的条目下钻到2级,有的下钻到1级,有的下钻的条目和数量也都是变化的。虽然人眼一眼就看出来,各个条目的层级关系,但是计算机就很难看出来,因此,我需要写一个脚本,将这些层级关系识别出来,并且适应各种下钻情况。

我后来琢磨了一下,写出了一个代码,可以自动将各级别转换成一个干净的完整级别逻辑的代码,效果如下:

也就是说只要传递进去一个表的列名称(也就是column_name列),然后这个函数,自动会将数据解析成干净的数据名称(也就是class_2_name列)。各个层级别中间使用__连接。

完整代码如下

从文章的url提取出这个文章的关键数据,完整代码如下:

def clean_column(columns_list):
    """
    这个代码负责将表格的列名称清洗干净(就是将一些二级标题、三级标题都转换成完整的名称:{一级标题}-{二级标题}-{三级标题})
    如果标题是一级标题 -> 一级标题
    如果标题是二级标题 -> {一级标题}-{二级标题}
    如果标题是三级标题 -> {一级标题}-{二级标题}-{三级标题}

    这么一做,每一个表的列名称就不会混淆

    """


    column_data = pd.DataFrame({'column_name':[i.replace(" "''for i in columns_list]})
    # 做一级填充
    column_data['class_1'] = column_data['column_name'].apply(lambda x: len(re.findall(pattern='[0-9]+、', string=x)) !=1)#[len(re.findall(pattern='[0-9]+、', string=temp_column)) !=1 for temp_column in columns_list]
    column_data['class_1_name'] = column_data['column_name']
    column_data.loc[column_data['class_1']== False'class_1_name'] = np.nan
    column_data['class_1_name'] = column_data['class_1_name'].ffill()
    # column_data.loc[column_data['column_name'] != column_data['class_1_name'],'class_1_name'] = \
    #     column_data.loc[column_data['column_name'] != column_data['class_1_name'],:].apply(lambda x: f"{x['class_1_name']}__{x['column_name']}", axis=1)
    column_data.loc[column_data['class_1'] == False,'class_1_name'] = \
        column_data.loc[column_data['class_1'] == False,:].apply(lambda x: f"{x['class_1_name']}__{x['column_name']}", axis=1)
    # 做二级填充

    column_data['class_2'] = column_data['column_name'].apply(lambda x: len(re.findall(pattern='([0-9]+)', string=x))!=1)

    column_data['class_2_name'] = column_data['class_1_name']
    column_data.loc[column_data['class_2'] == False'class_2_name'] = np.nan
    column_data['class_2_name'] = column_data['class_2_name'].ffill()
    
    column_data.loc[column_data['class_2'] == False'class_2_name'] = column_data.loc[column_data['class_2'] == False, :].apply(lambda x: f"{x['class_2_name']}__{x['column_name']}", axis=1)

    return column_data['class_2_name'].tolist()



def get_chapter_data(index):
    # time.sleep(random.randint(0, 1.5))

    temp_url = all_chapter_url[index]
    temp_info = all_chapter_text[index]
    data = pd.read_html(temp_url)[2].dropna()
    data = data.loc[data[0] != data[1], :]
    data = data.set_index(0).T.reset_index(drop=True)

    columns_list = data.columns.tolist()
    data.columns = clean_column(columns_list)

    # data.columns = [f"{index}_{columns_list[index]}" for index in range(len(columns_list))]


    data['from_url'] = temp_url
    data['info'] = temp_info
    return data

# test function
test_data = get_chapter_data(index=70)
test_data

将数据报错

结合上面的两步,最终只需要将所有的数据合并即可。并且保存数据,代码如下:

finaldata = pd.concat([get_chapter_data(index) for index in tqdm(range(len(all_chapter_url)))]).reset_index(drop=True)
finaldata.head(10)

finaldata.to_csv("保险业经营数据.csv", index=False)

写在最后

  1. 本文的完整代码🔗 https://github.com/yuanzhoulvpi2017/tiny_python/tree/main/insurance_association_china

  2. 只要运行上面的代码,就能获得全部数据。

  3. 如果代码不可用了,记得联系我(微信联系方式,就在公众号world of statistics的菜单栏的右下角),我再继续更新代码。

  4. 其实我的目的是分享数据,提取各种数据的,并不是为了做爬虫而做爬虫。后面数据都免费分享数据(有偿服务除外)

阅读更多


往期推荐



[原创]全球实时碳数据网站爬虫[全网首发 免费分享]

实时碳数据可视化[第二篇]

[转载]大学毕业三年的一些经历与思考

[原创]python计算中文文本相似度神器

[喜大普奔]dataV对getchinamap包没多大意见

[自制]创意的中国地图 3d版本(demo)

python获得百度指数脚本[免费分享]

[自制]python获得中国地图数据_介绍

python与GIS数据处理(风速数据处理 基于csv文件格式)

python处理nc数据【视频讲解】

一次痛苦且有趣的查看sklearn源码经历(一次有趣的复现经历)

重磅!最全中国地图数据分享!以及绘制方法~【数据分享系列1】


打赏我