专栏首页数据科学人工智能使用Python爬取COVID-19疫情数据
原创

使用Python爬取COVID-19疫情数据

视频内容

新型冠状病毒感染的肺炎疫情爆发后,对人们的生活产生很大的影响。当前感染人数依然在不断变化。每天国家卫健委和各大新闻媒体都会公布疫情的数据,包括累计确诊人数、现有确诊人数等。

本案例使用Python开发网络爬虫,对新冠肺炎的疫情数据(包括当日的实时数据和历史数据)进行采集。

1. 选择数据源

自新冠肺炎(covid-19)疫情爆发以来,这场疫情几乎影响了每个人的生活,为了对疫情做数据分析,需要采集疫情的数据,本篇案例就基于python爬虫进行数据采集。

首先我们需要找到合适的数据源,卫健委和各媒体每天都会报道新冠肺炎的疫情数据,如下图所示,因此我们可以考虑将这些网页作为数据源。

以北京市和湖南省为例查看卫健委的数据,结果发现,两地卫健委的数据各式并不统一,北京市卫健委数据为文本,而湖南省卫健委数据为图片,并且由于各卫健委的网络地址还需要再花时间去寻找,因此卫健委的数据并不便于采集。

  • 北京市卫健委疫情通报地址:http://wjw.beijing.gov.cn/wjwh/ztzl/xxgzbd/gzbdyqtb/index_1.html
  • 湖南省卫健委疫情通报地址:http://wjw.hunan.gov.cn/wjw/qwfb/yqfkgz_list.html

我们考虑将新闻媒体的播报平台作为数据源,以网易的疫情播报平台为例,如下图所示可以看到它的数据内容非常丰富,不仅包括国内的数据还包括国外的数据,且作为大平台,公信度也比较高。因此我们选择网易的疫情实时动态播报平台作为数据源,其地址如下:

https://wp.m.163.com/163/page/news/virus_report/index.html?_nw_=1&_anw_=1

我们基于网易的实时播报平台寻找数据,由于它是一个实时的动态平台,因此数据一般在Network标签下可以找到,以Chrome浏览器为例展示寻找数据步骤:

  • 访问网易实时疫情播报平台https://wp.m.163.com/163/page/news/virus_report/index.html?_nw_=1&_anw_=1
  • 在页面任意位置右键点击检查
  • 进入Network标签下的XHR,此时可能会提示刷新,按下“Ctrl+R”即可

在网页的检查页面内,左下角红框所示的部分是我们找到的数据源,基于这些地址进行爬取,我们获取数据的目标如下:

2. 初步探索

通过比对,我们发现在第二个地址中存放着关于疫情的数据,因此我们先对这个地址进行爬虫。

接下来找到其地址,点击headers后进行查看,在url中?后边为参数,可以不用设置,因此我们需要请求的地址为:https://c.m.163.com/ug/api/wuhan/app/data/list-total,并且可以看到请求方法为get,同时查看自己浏览器的user-agent,使用requests请求时,设置user-agent伪装浏览器。

下来开始请求,首先导入使用的包,使用request进行网页请求,使用pandas保存数据。

import requests
import pandas as pd
import time 
pd.set_option('max_rows',500)

设置请求头,伪装为浏览器

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'
}

发起请求,将找到的第一个数据源作为请求目标。

url = 'https://c.m.163.com/ug/api/wuhan/app/data/list-total'   # 定义要访问的地址
r = requests.get(url, headers=headers)  # 使用requests发起请求
print(r.status_code)  # 查看请求状态

200

print(type(r.text))
print(len(r.text))

<class 'str'> 301443

可以看到返回后的内容是一个几十万长度的字符串,由于字符串格式不方便进行分析,并且在网页预览中发现数据为类似字典的json格式,所以我们将其转为json格式。

import json
data_json = json.loads(r.text)
data_json.keys()

dict_keys(['reqId', 'code', 'msg', 'data', 'timestamp'])

可以看出在data中存放着我们需要的数据,因此我们取出数据。

data = data_json['data'] # 取出json中的数据
data.keys()

dict_keys(['chinaTotal', 'chinaDayList', 'lastUpdateTime', 'areaTree'])

数据中总共有四个键,每个键存储着不同的内容:

接下来我们分别获取实时数据和历史数据。

键名称

数据内容

chinaTotal

全国当日数据

chinaDayList

全国历史数据

lastUpdateTime

更新时间

areaTree

世界各地实时数据

3. 实时数据爬取

3.1 全国各省实时数据爬取

首先我们抓取全国各省的实时数据,在areaTree键值对中,存放着世界各地的实时数据,areaTree是一个列表,每一个元素都是一个国家的数据,每一个元素的children是各国家省份的数据。 我们首先找到中国各省的实时数据,如下图所示。

data_province = data['areaTree'][2]['children']  # 取出中国各省的实时数据
type(data_province)

list

data_province是列表格式,每个元素是一个省的实时数据,并且为字典格式,每个省的键名称全部相同。

data_province[0].keys() # 查看每个省键名称

dict_keys(['today', 'total', 'extData', 'name', 'id', 'lastUpdateTime', 'children'])

各省数据键值含义如下:

键名称

数据内容

today

各省当日数据

total

各省当日累计数据

extData

无任何数据

name

各省名称

id

各省行政编号

lastUpdateTime

更新时间

children

各省下一级数据

for i in range(len(data_province)): # 遍历查看各省名称、更新时间
    print(data_province[i]['name'],data_province[i]['lastUpdateTime'])
    if i == 5:
        break

湖北 2020-03-25 08:15:00 广东 2020-03-25 08:44:58 河南 2020-03-25 08:23:28 浙江 2020-03-25 09:01:13 湖南 2020-03-25 08:25:33 安徽 2020-03-25 08:38:12

用DataFrame以字典格式生成数据的例子,传入一个列表,列表每一个元素都是字典。

test_dict = [{'a':1,
              'b':2,
              'c':3,},
             
             {'a':111,
              'b':222}]
pd.DataFrame(test_dict)
pd.DataFrame(data_province).head() # 直接生成数据效果并不理想

不能直接生成DataFrame是因为数据中嵌套着字典,例如湖北省数据如下:标红线表示带有嵌套字典,篮筐内没有嵌套字典。

逐个拆解数据,我们已经了解children为下一地级数据,我们只分析到省单位,因此各省的children数据不采集;extData为空值,也不采集,最后具体采集方法如下:

不需要采集的数据:children、extData。 需要采集的数据:由于数据中today和total嵌套着字典,因此不能直接获取,对于id、lastUpdateTime、name、可以直接取出为一个数据,today为一个数据,total为一个数据,最后三个数据合并为一个数据。

# 获取id、lastUpdateTime、name
info = pd.DataFrame(data_province)[['id','lastUpdateTime','name']]
info.head()

列表推导式例子

l1 = [1,1,1,2,2,2]
[i+1 for i in l1 ]

[2, 2, 2, 3, 3, 3]

# 获取today中的数据
today_data = pd.DataFrame([province['today'] for province in data_province ]) 
today_data.head()
['today_'+i for i in today_data.columns]

['today_confirm', 'today_dead', 'today_heal', 'today_severe', 'today_storeConfirm', 'today_suspect']

today_data.columns = ['today_'+i for i in today_data.columns] # 由于today中键名和total键名相同,因此需要修改列名称
today_data.head()
# 获取total中的数据
total_data = pd.DataFrame([province['total'] for province in data_province ])
total_data.columns = ['total_'+i for i in total_data.columns]
total_data.head()
pd.concat([info,total_data,today_data],axis=1).head() # 将三个数据合并
# 将提取数据的方法封装为函数
def get_data(data,info_list):
    info = pd.DataFrame(data)[info_list] # 主要信息
    
    today_data = pd.DataFrame([i['today'] for i in data ]) # 生成today的数据
    today_data.columns = ['today_'+i for i in today_data.columns] # 修改列名
    
    total_data = pd.DataFrame([i['total'] for i in data ]) # 生成total的数据
    total_data.columns = ['total_'+i for i in total_data.columns] # 修改列名
    
    return pd.concat([info,total_data,today_data],axis=1) # info、today和total横向合并最终得到汇总的数据
today_province = get_data(data_province,['id','lastUpdateTime','name'])
today_province.head()
def save_data(data,name): # 定义保存数据方法
    file_name = name+'_'+time.strftime('%Y_%m_%d',time.localtime(time.time()))+'.csv'
    data.to_csv(file_name,index=None,encoding='utf_8_sig')
    print(file_name+' 保存成功!')
time.strftime('%Y_%m_%d',time.localtime(time.time()))

'2020_03_25'

save_data(today_province,'today_province')

today_province_2020_03_25.csv 保存成功!

3.2 世界各国实时数据爬取

之前已经了解到在json数据data中的areaTree是列表格式,每个元素都是一个国家的实时数据,每个元素的children是各国家省份的数据,现在我们提取世界各国实时数据。

areaTree = data['areaTree'] # 取出areaTree
areaTree[0] # 查看第一个国家的数据

{'children': [], 'extData': {'confirmNote': None, 'deadNote': None, 'healNote': None, 'incrConfirmNote': None, 'incrSevereNote': None, 'suspectNote': None}, 'id': '9577772', 'lastUpdateTime': '2020-03-25 00:00:31', 'name': '突尼斯', 'today': {'confirm': None, 'dead': None, 'heal': None, 'severe': None, 'storeConfirm': None, 'suspect': None}, 'total': {'confirm': 114, 'dead': 3, 'heal': 0, 'severe': 0, 'suspect': 0}}

areaTre中每个键值的含义:

for i in range(len(areaTree)):  # 查看各国家名称和更新时间
    print(areaTree[i]['name'],areaTree[i]['lastUpdateTime'])
    if i == 5:
        break

突尼斯 2020-03-25 00:00:31 塞尔维亚 2020-03-25 03:57:40 中国 2020-03-25 09:52:07 日本 2020-03-24 15:26:25 泰国 2020-03-25 00:00:30 新加坡 2020-03-25 00:00:30

areaTree中提取每个国家的实时数据。

today_world = get_data(areaTree,['id','lastUpdateTime','name'])
today_world.head()
save_data(today_world,'today_world')

today_world_2020_03_25.csv 保存成功!

4.历史数据爬取

4.1 全国历史数据爬取

在数据data中,总共有四个键,其中chinaDayList存放着中国的历史数据,我们将其取出。

data.keys()

dict_keys(['chinaTotal', 'chinaDayList', 'lastUpdateTime', 'areaTree'])

chinaDayList = data['chinaDayList'] # 取出chinaDayList

chinaDayList中键值含义:

键名称

数据内容

date

日期

today

当日数据

total

累计数据

lastUpdateTime

更新时间

type(chinaDayList) # 查看chinaDayList的格式

list

chinaDayList[0]

{'date': '2020-01-20', 'lastUpdateTime': None, 'today': {'confirm': 291, 'dead': 6, 'heal': 25, 'severe': 0, 'storeConfirm': None, 'suspect': 27}, 'total': {'confirm': 291, 'dead': 6, 'heal': 25, 'severe': 0, 'suspect': 54}}

可以看到todaytotal中也嵌套着字典,因此直接使用定义好的方法从chinaDayList中提取全国历史数据。

alltime_China = get_data(chinaDayList,['date','lastUpdateTime'])
alltime_China.head()
save_data(alltime_China,'alltime_China')

alltime_China_2020_03_25.csv 保存成功!

4.2 全国各省历史数据爬取

找到第二个数据地址https://c.m.163.com/ug/api/wuhan/app/data/list-by-area-code?areaCode=420000 ,在页面对比发现是湖北省的历史数据。

先以其中一个省为例,先尝试获取其历史数据,其他的省可以使用同样的方法。

url = 'https://c.m.163.com/ug/api/wuhan/app/data/list-by-area-code?areaCode=420000' # 定义数据地址
r = requests.get(url, headers=headers) # 进行请求
data_json = json.loads(r.text) # 获取json数据
data_json.keys()

dict_keys(['reqId', 'code', 'msg', 'data', 'timestamp'])

list中存放着数据。

data_json['data']['list'][0]

{'date': '2020-01-20', 'lastUpdateTime': None, 'today': {'confirm': 270, 'dead': 6, 'heal': 25, 'severe': None, 'storeConfirm': None, 'suspect': 0}, 'total': {'confirm': 270, 'dead': 6, 'heal': 25, 'severe': 0, 'suspect': 0}}

可以看出一个省一天的的数据格式如上,这和之前的数据结构一样,因此可以使用之前的方法得到数据。

data_test = get_data(data_json['data']['list'],['date'])
data_test['name'] = '湖北省'
data_test.head()

通过上述方法得到了湖北省的历史数据,想要得到每个省的历史数据怎么做呢?

在湖北省历史数据的地址中,我们发现参数aeraCode=420000,而这刚好和全国各省实时数据today_province中的id对应.

today_province[['id','name']].head()

为了进一步确认,在百度上查找全国各省的行政代码,结果发现和数据today_province中的id这一列一致,因此id这一列就是各省的行政代码。

因此为了得到每个省的历史数据,我们只需要将各省的行政代码作为参数传入这个地址即可,如下所示: https://c.m.163.com/ug/api/wuhan/app/data/list-by-area-code?areaCode=各省行政代码 例如广东省历史数据的地址为:https://c.m.163.com/ug/api/wuhan/app/data/list-by-area-code?areaCode=440000 湖南省历史数据的地址为:https://c.m.163.com/ug/api/wuhan/app/data/list-by-area-code?areaCode=430000

但是数据中并没有显示省的名称,因此需要写入每个省的名称。

为了便于写入各省的名称,我们需要生成一个各省行政代码和省名称对应的字典。

以一个简单的数据为例,展示生成字典的方法。

a = ['1','2','3','4']
b = ['q','w','e','r']

for i,j in zip(a, b):
    print(i,j)

1 q 2 w 3 e 4 r

{ i:j  for i,j in zip(a, b)}

{'1': 'q', '2': 'w', '3': 'e', '4': 'r'}

province_dict = {num:name for num,name in zip(today_province['id'],today_province['name'])}
# 查看前五个内容
count = 0
for i in province_dict:
    print(i,province_dict[i])
    count += 1
    if count == 5:
        break

420000 湖北 440000 广东 410000 河南 330000 浙江 430000 湖南

每一个省的列名是相同的,因此多个省的数据合并起来就可以存入一个数据中,数据合并演示的例子如下:

df1 = pd.DataFrame([{'a':1,'b':2,'c':3,},{'a':111,'b':222}])
df1
df2 = pd.DataFrame([{'a':9,'b':8,'c':7,},{'a':345,'c':789}])
df2
df1 = pd.concat([df1,df2],axis=0)
df1

按照上述方法,得到每一个省的数据后,进行合并。

start = time.time()
for province_id in province_dict: # 遍历各省编号
    
    try:
        # 按照省编号访问每个省的数据地址,并获取json数据
        url = 'https://c.m.163.com/ug/api/wuhan/app/data/list-by-area-code?areaCode='+province_id
        r = requests.get(url, headers=headers)
        data_json = json.loads(r.text)
        
        # 提取各省数据,然后写入各省名称
        province_data = get_data(data_json['data']['list'],['date'])
        province_data['name'] = province_dict[province_id]
        
        # 合并数据
        if province_id == '420000':
            alltime_province = province_data
        else:
            alltime_province = pd.concat([alltime_province,province_data])
            
        print('-'*20,province_dict[province_id],'成功',
              province_data.shape,alltime_province.shape,
              ',累计耗时:',round(time.time()-start),'-'*20)
        
        # 设置延迟等待
        time.sleep(10)
        
    except:
        print('-'*20,province_dict[province_id],'wrong','-'*20)
save_data(alltime_province,'alltime_province')

alltime_province_2020_03_25.csv 保存成功!

4.3 世界各国历史数据爬取

接着找到下一个数据地址,对比后发现是意大利数据,首先尝试爬取意大利的历史数据,其他国家的数据可以用同样的方法爬取。

url_italy = 'https://c.m.163.com/ug/api/wuhan/app/data/list-by-area-code?areaCode=15'  # 意大利的数据地址
r = requests.get(url_italy, headers=headers) # 进行访问
italy_json = json.loads(r.text) # 导出json数据
italy_json.keys()

dict_keys(['reqId', 'code', 'msg', 'data', 'timestamp'])

italy_json['data'].keys()

dict_keys(['list'])

结合页面观察,发现各国家的历史数据结构和各省的类似。

italy_json['data']['list'][0] # 查看数据内容

{'date': '2020-01-31', 'lastUpdateTime': None, 'today': {'confirm': 2, 'dead': 0, 'heal': 0, 'severe': None, 'storeConfirm': None, 'suspect': 0}, 'total': {'confirm': 2, 'dead': 0, 'heal': 0, 'severe': 0, 'suspect': 0}}

每个国家的数据格式和之前各省的几乎一样,因此使用定义好的方法生成数据,然后把国家名称写入数据。

data_italy = get_data(italy_json['data']['list'],['date']) # 生成数据
data_italy['name'] = '意大利' # 写入国家名称
data_italy.head()

因为原始数据中没有国家名称,为了得到每个国家的名称,需要生成国家编号和国家名称的键值对,这样就可以存储国家名称,在之前的世界各国实时数据today_world中有国家的编号和名称,可以用它来生成键值对。

today_world[['id','name']].head()
country_dict = {key:value for key,value in zip(today_world['id'], today_world['name'])}
# 查看前五个内容
count = 0
for i in country_dict:
    print(i,country_dict[i])
    count += 1
    if count == 5:
        break

9577772 突尼斯 9507896 塞尔维亚 0 中国 1 日本 2 泰国

通过每个国家的编号访问每个国家历史数据的地址,然后获取每一个国家的历史数据。

start = time.time()
for country_id in country_dict: # 遍历每个国家的编号
    
    try:
        # 按照编号访问每个国家的数据地址,并获取json数据
        url = 'https://c.m.163.com/ug/api/wuhan/app/data/list-by-area-code?areaCode='+country_id
        r = requests.get(url, headers=headers)
        json_data = json.loads(r.text)
        
        # 生成每个国家的数据
        country_data = get_data(json_data['data']['list'],['date'])
        country_data['name'] = country_dict[country_id]

        # 数据叠加
        if country_id == '9577772':
            alltime_world = country_data
        else:
            alltime_world = pd.concat([alltime_world,country_data])
            
        print('-'*20,country_dict[country_id],'成功',country_data.shape,alltime_world.shape,
              ',累计耗时:',round(time.time()-start),'-'*20)
        
        time.sleep(10)

    except:
        print('-'*20,country_dict[country_id],'wrong','-'*20)
save_data(alltime_world,'alltime_world')

5.总结

本篇案例的主要内容是新冠肺炎疫情的数据采集,首先我们寻找合适的数据源,最终选择网易的实时疫情播报平台作为数据源,然后逐步地解析找到我们需要的数据,最后通过python爬虫获得我们得到了疫情的数据,包括中国各省和世界各国的实时数据,还包括中国整体、中国各省、世界各国的历史数据。通过这样一篇数据采集的案例让同学们学习到爬虫的基本方法,掌握这些方法能够提升数据采集的能力,并且可以使用在以后的学习和工作中。

注意:因为爬虫的对象是网页上的数据,如果这些网页的数据内容有所变动可能会导致采集无效。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Pandas疫情探索性分析

    新型冠状病毒感染的肺炎疫情爆发后,对人们的生活产生很大的影响。当前感染人数依然在不断变化。每天国家卫健委和各大新闻媒体都会公布疫情的数据,包括累计确诊人数、现有...

    数据酷客
  • 基于PyEcharts的COVID-19疫情可视化分析

    将国家或地区的数值信息映射到地图上,通过颜色变化来表示数值的大小或范围。颜色地图适合带有地理位置信息的数据的展现,将颜色和地图相结合,直观显示数据的地理分布,通...

    数据酷客
  • 数据科学通识第八讲:数据可视化

    下面这四组数据是由统计学家Francis Anscombe在1973年精心构建的。大家直观地看这四组数据,能否看出什么规律呢?

    数据酷客
  • Vue 组件中的 data 为什么必须是函数

    在 new Vue() 中,data 是可以作为一个对象进行操作的,然而在 component 中,data 只能以函数的形式存在,不能直接将对象赋值给它

    Nian糕
  • R语言与生信系列①(R入门与临床三线表绘制)

    首次分享课讲的是TCGA数据分析,探究某一因素与肿瘤临床数据之间的关系,并自动生成可以用于SCI发表的三线表,如下图所示:

    用户1359560
  • Python中的相关分析correlation analysis

    相关分析(correlation analysis) 研究两个或两个以上随机变量之间相互依存关系的方向和密切程度的方法。 线性相关关系主要采用皮尔逊(Pears...

    Erin
  • Python中如何进行数据分组

    数据分组 根据数据分析对象的特征,按照一定的数值指标,把数据分析对象划分为不同的区间进行研究,以揭示其内在联系和规律性。 cut 函数: cut(series,...

    Erin
  • python中pandas库中DataFrame对行和列的操作使用方法示例

    最近处理数据时发现当pd.read_csv()数据时有时候会有读取到未命名的列,且该列也用不到,一般是索引列被换掉后导致的,有强迫症的看着难受,这时候dataf...

    砸漏
  • TP数据避免重复和去重处理

    alter table gift_doc add unique index(num_id);

    php007
  • Python中的数据标准化

    数据标准化 数据标准化是指将数据按比例缩放,使之落入到特定区间。 为了消除量纲的影响,方便进行不同变量间的比较分析。 0-1标准化: x=(x-min)/(ma...

    Erin

扫码关注云+社区

领取腾讯云代金券