写了个自动化脚本,每日更新疫情数据

2020,努力做一个无可替代的人!

写在前面的话

先说明一下,这是一篇爬虫+分析+自动化的文章,并不是上节说到的 NumPy 系列文章,NumPy 系列请期待下节内容。

这篇实战文章也属于心血来潮吧,简单说一下:

小一我自从疫情发生了之后,每天早上第一件事就是关注微博热搜里面关于各地确诊人数的新闻,不得不说,确实很牵动人心,前几天的突增1w+,有点害怕,还好这几天降下来了。

最近几天和往常一样去看热搜的时候,却发现好像确诊人数的新闻并不在热搜里面,有时候还需要折腾一会才能搜到相关数据。

好吧,既然这样,那咱们就自己写一个程序,自己更新数据。

大概这篇文章的起源就是这样,就一个心血来潮的冲动,就有了。

ok,该介绍的背景都说完了,再来说下这篇文章:

技术方面:会用到 爬虫+数据库+数据处理+绘图+邮件 相关技术

咋一看,发现技术点还挺多,如果你经常读公众号的文章,会发现大部分知识点都有专门写过。

我都一一列出来,文章哪一块看不明白了回来查一下再继续

爬虫:动态获取数据BeautifulSoup详解

数据库:数据库存储

邮件:邮件发送

正文

我们要做一个自动化的程序,当然就不只是爬虫那么简单了。

先明确一下需求:

  • 爬虫获取最新疫情数据
  • 数据简单清洗,保存数据库
  • 绘制热力地图,与前一日数据进行比较
  • 将结果以邮件形式发送
  • 每日定时执行程序

大概就上面五个步骤,也不是很难嘛。画热力地图是个新知识,可能需要花一些时间

准备好了,我们就开始吧!

爬取数据

首先我们需要确定数据源,这个简单。

说个题外话,这次疫情期间,我感觉官方媒体还是很给力的,数据都能在第一时间公开公布,让大众知道,还是很给力的

其中包括卫健委、人民日报、丁香园、百度地图等,都有最新数据。

就不一一列举了,网上都能搜得到。

本次爬虫我用的是丁香园的数据。

再说个题外话,别整那些恶意爬虫去搞这些网站,特别是最近一段时间。慎之慎之

看一下丁香园的疫情官网,可以看到有这样一些(国内)数据

一个是地区累积确诊人数的热力分布图,一个是当前的最新数据,当然还有很多折线图,我没有截

我们需要的是每日的各个省、地市的相关数据。

检查源代码,可以看到:

其中有三个 div 需要注意:

  • class=’fold___xVOZX‘ 的 div:每个省的所有数据(总)
  • class=’areaBlock1___3V3UU‘ 的 div:每个省的汇总数据(分)
  • class=’areaBlock2___27vn7‘ 的 div:每个省下的所有地市数据(分)

我们需要的数据就在这三个 div 里面,再看看 div 里面有什么:

红色的是省份汇总数据,黄色的是地市的数据,黑色的是具体数据标签。

省份汇总数据的 div 和地市的数据的 div 下面都有5个 p 标签存放数据,基本一致

5个 p 标签分别是:

  • class=’subBlock1___j0DGa‘ 的 p 标签:表示省份/城市名称
  • class=’subBlock2___E7-fW‘ 的 p 标签:表示现存确诊人数
  • class=’subBlock4___ANk6l‘ 的 p 标签:表示累计确诊人数
  • class=’subBlock3___3mcDz‘ 的 p 标签:表示死亡人数
  • class=’subBlock5___2EkOU‘ 的 p 标签:表示治愈人数

数据就这些了,选择一种爬虫方式爬下来吧

打开页面,我第一感觉就是动态数据,不信你也可以试试

选用 selenium 进行数据爬取,我尽量贴一下核心代码,文末也有源码获取方式

# 初始化 seleniumexecutable_path = "你本机的chromedriver.exe路径"# 设置不弹窗显示chrome_options = Options()chrome_options.add_argument('--headless')chrome_options.add_argument('--disable-gpu')browser = webdriver.Chrome(chrome_options=chrome_options,executable_path=executable_path)

你也可以选择 selenium 的弹窗显示,源码里面也有写。

browser.get(url)# 输出网页源码content = browser.page_sourcesoup = BeautifulSoup(content, 'html.parser')# 获取中国城市疫情人数soup_city_class = soup.find('div', class_='areaBox___3jZkr').find_all('div',class_='areaBlock2___27vn7')# 获取每一个地市的数据# 循环省略resolve_info(per_city, 'city')# 获取中国省份疫情人数soup_province_class = soup.find('div', class_='areaBox___3jZkr').find_all('div',class_='areaBlock1___3V3UU')# 获取每一个省的数据# 循环省略resolve_info(per_province, 'province')    

循环拿到每一个省份和每一个城市的代码我没写,你知道这里面的 per_city 和 per_province 代表每一个城市和省份就行了。

解析函数里面,直接获取我们需要的几个数据

# 解析省份和地市详细数据if tag == 'city':    # 城市    data_name = data.find('p', class_='subBlock1___j0DGa').find('span').stringelse:    # 省份    data_name = [string for string in data.find('p', class_='subBlock1___j0DGa').strings][0]# 现存确诊人数data_curr_diagnose = data.find('p', class_='subBlock2___E7-fW').string# 累计确诊人数data_sum_diagnose = data.find('p', class_='subBlock4___ANk6l').string# 死亡人数data_death = data.find('p', class_='subBlock3___3mcDz').string# 治愈人数data_cure = data.find('p', class_='subBlock5___2EkOU').string

当然会存在一些特殊情况

比如:有的省份最下面有特殊注释,有的数据是空缺的等等,合理处理就行了

[图片上传失败...(image-958b84-1581743457799)]

好了,数据已经全部拿到了,爬虫就算结束了。

数据清洗

拿到数据以后,大致看了一眼,还算比较规整的。

在数据中,我发现了两处需要处理的地方

  • 数据存在空值
  • 部分地市名称其实并不是地市名称

就拿北京来说,看一下数据:

黄颜色标出的是缺失数据,红颜色的是非正常名称

我是这样处理的:

第一处地方:官网的数据并没有0,所有这个空值就是0,直接填充就可

第二处地方:部分数据名称不对,根据需求剔除或者合并到省会城市都可

看一下源代码:

# 删除地市的不明确数据if tag == 'city':    df_data.drop(index=df_data[df_data['city'] == '待明确地区'].index, axis=1,inplace=True)    # df_data.drop(df_data['city'] == '外地来京人员', axis=1, inplace=True)    # df_data.drop(df_data['city'] == '外地来沪人员', axis=1, inplace=True)    # df_data.drop(df_data['city'] == '外地来津人员', axis=1, inplace=True)# 填充空记录为0df_data.fillna(0, inplace=True)# 增加日期字段df_data['date'] = time_str

代码应该都能看懂,就不解释了,日期字段是为了方便取出近两天的数据进行比较

接下来就是导数据到数据库了,一共两种表,省份数据表和地市数据表。

看一下数据库表结构:

省份表类似,只是把城市名换成了省份名。

当然,你要觉得两张表麻烦,一张表也可以存这些数据,看你自己。

对于我们的 DataFrame 类型的数据,是可以直接导入数据库的

一行代码就行,看好了

# 连接数据库connect = create_engine('mysql+pymysql://username:passwd@localhost:3306/db_name?charset=utf8')# 保存数据到数据库中df_data.to_sql(name=table_name, con=connect, index=False, if_exists='append')

你不会觉得连接数据库也算一行吧?那就两行,给大哥跪下

数据搞定了,下面开始绘图

数据绘图

我们要画的是热力地图,直接用 pyecharts,上手简单

用 echarts 的原因是我曾经写过一段时间前端代码,echarts研究过一段时间,比较容易上手

这里需要安装两个模块 pyecharts 和 ,用来画图和输出成图片保存

安装也很简单, cmd 下直接输入 pip install 模块名称

模块包安装没有问题的话就可以画图了

# 导入相应模块from pyecharts import options as optsfrom pyecharts.charts import Mapfrom pyecharts.render import make_snapshotfrom snapshot_selenium import snapshot"""绘制热力地图"""# 获取数据list_data = df_data.iloc[:, [1, 3]].values.tolist()# 绘制地图ncp_map = (    Map(init_opts=opts.InitOpts('1000px', '600px'))    .add('', list_data, 'china')    .set_global_opts(        title_opts=opts.TitleOpts(            title=title,            pos_left='center'        ),        visualmap_opts=opts.VisualMapOpts(            # 设置为分段形数据显示            is_piecewise=True,            # 设置拖拽用的手柄            is_calculable=True,            # 设置数据最大值            max_=df_data['sum_diagnose'].max(),            # 自定义的每一段的范围,以及每一段的文字,以及每一段的特别的样式。            pieces=[                {'min': 10001, 'label': '>10000', 'color': '#4F040A'},                {'min': 1000, 'max': 10000, 'label': '1000 - 10000', 'color': '#811C24'},                {'min': 500, 'max': 999, 'label': '500 - 999', 'color': '#CB2A2F'},                {'min': 100, 'max': 499, 'label': '100 - 499', 'color': '#E55A4E'},                {'min': 10, 'max': 99, 'label': '10 - 99', 'color': '#F59E83'},                {'min': 1, 'max': 9, 'label': '1 - 9', 'color': '#FDEBCF'},                {'min': 0, 'max': 0, 'label': '0', 'color': '#F7F7F7'}            ]        ),    ))# 保存图片到本地make_snapshot(snapshot, ncp_map.render(), filepath_save)

看着效果还不错。

需要提到的是,我们需要的是省份/地市名称+累积确诊人数两列数据

它们对应的是第二列和第四列,所以上面代码是这样写的

df_data.iloc[:, [1, 3]]

还有一些地图的控件设置,看懂是什么意思就行了,不会了再去查API文档

我有挨个行写注释,你可别说你看不懂

图片生成了,看看张什么样子

根据每日的数据更新,我们比较最近两天的增长情况,做一个表格出来

获取到最近两天的数据库数据

# 设置日期data_time = datetime.now() + timedelta(-2)data_time_str = data_time.strftime('%Y-%m-%d')# 获取数据库近两天的数据sql_province = 'select * from t_ncp_province_info where date>={0}'.format(data_time_str)df_province_data = pd.read_sql_query(sql_province, connect)

将数据按天分成两部分,做差即可,直接贴代码

# 获取数据日期date_list = df_data['date'].drop_duplicates().values.tolist()# 根据日期拆分dataframedf_data_1 = df_data[df_data['date'] == date_list[0]]df_data_2 = df_data[df_data['date'] == date_list[1]]# 昨天-前天 比较新增数据df_data_result = df_data_2[['curr_diagnose', 'sum_diagnose', 'death', 'cure']] - df_data_1[['curr_diagnose', 'sum_diagnose', 'death', 'cure']]

更进一步的,计算数据的环比增长率

# 新增较上一日环比列df_data_result['curr_diagnose_ratio'] = (df_data_result['curr_diagnose']/df_data_1['curr_diagnose']).apply(lambda x: format(x, '.2%'))df_data_result['sum_diagnose_ratio'] = (df_data_result['sum_diagnose']/df_data_1['sum_diagnose']).apply(lambda x: format(x, '.2%'))df_data_result['death_ratio'] = (df_data_result['death']/df_data_1['death']).apply(lambda x: format(x, '.2%'))df_data_result['cure_ratio'] = (df_data_result['cure']/df_data_1['cure']).apply(lambda x: format(x, '.2%'))

如果要在邮件中显示表格内容,我们还需要对列名进行排序和更改

并且根据相应的数据进行降序排序,这样增长变化看起来更明显

if tag == 'city':    name = '城市'else:    name = '省份'df_data = df_data[[tag, 'sum_diagnose', 'sum_diagnose_ratio', 'curr_diagnose','curr_diagnose_ratio', 'death', 'death_ratio', 'cure', 'cure_ratio']]df_data.rename(    columns={        tag: name,        'sum_diagnose': '累计确诊人数',        'sum_diagnose_ratio': '累计确诊环比增长率',        'curr_diagnose': '现存确诊人数',        'curr_diagnose_ratio': '现存确诊环比增长率',        'death': '死亡人数',        'death_ratio': '死亡环比增长率',        'cure': '治愈人数',        'cure_ratio': '治愈环比增长率'    }, inplace=True)# 数据排序df_data.sort_values(['累计确诊人数', '累计确诊环比增长率'], inplace=True, ascending=False)df_data.reset_index(inplace=True)

ok,以上的数据,包括生成的图片都是我们需要在邮件中显示的。

邮件发送

邮件中,需要加入上一步的图片和表格数据,添加到正文中发送

因此,邮件正文需要设置成 html 格式发送。

并且我们在正文中需要插入近两天的数据,所以 html 中需要这样设置

# 部分 html 内容'<p><img src="cid:image1" alt="最新数据地图" width="1200" height="600"></a></p>''<p><img src="cid:image2" alt="最新数据地图" width="1200" height="600"></a></p>'

根据 cid 区分不同的照片,同样的,需要在邮件中这样设置

# 读取图片并创建MIMEImagefor i, imag_filepath in enumerate(img_path_list):    with open(imag_filepath, 'rb') as fp:        msg_image = fp.read()    msg_image = MIMEImage(msg_image)    # 定义图片 ID,在 HTML 文本中引用    msg_image.add_header('Content-ID', '<image{0}>'.format(i + 1))    message.attach(msg_image)

另外,邮件中设置 html 格式正文也需要设置

# 设置主题subject = '截止 ' + date_str + ' 疫情最新数据(自动推送)'# 设置发送内容:1:发送html表格数据message = MIMEMultipart()# 生成邮件正文内容emain_content = get_email_content(df_data_1, df_data_2)send_text = MIMEText(emain_content, 'html', 'utf-8')message.attach(send_text)

具体的邮件发送教程可以看最前面提到的,之前写的很详细

如果没有什么异常,你会收到这样的一封邮件

email_0.png

打开之后,你需要点击【显示图片】

邮件正文部分内容是这样的:

搞定!还有最后一部分

定时任务

程序基本上已经算是完成了,自动化这一步提供一个方法,大家参考即可:

  • Linux下:可以使用 crontab 设置定时任务
  • Win下:可以使用(控制面板搜)任务计划程序设置定时任务

另外,我已经部署好了自己的定时任务,如果有需要的同学可以在评论区留言自己的邮箱 ,每天早上定时更新

总结一下:

先列好需求,再把需求一个个实现了,其实今天的项目就比较清晰明了了。

一个五个需求,我们再回顾一下:

  • 爬虫获取最新疫情数据
  • 数据简单清洗,保存数据库
  • 绘制热力地图,与前一日数据进行比较
  • 将结果以邮件形式发送
  • 每日定时执行程序

最后一步大家可以先百度,以后我会专门拎出来写一节,可以自动化的任务它不香吗?

源码获取

公众号后台回复 武汉加油 获取文章源码

有需要交流学习的同学可以加我们的交流群。(后台回复加群

写在后面的话

疫情还没过去,下周大家伙应该都要上班了

我已经窝了两星期,虽然特别想出来,但是一想到上下班的人,我就有点怂。

不多说了,下周上班,我们都要保护好自己。

碎碎念一下

对了,需要每天定时邮件更新疫情数据的同学评论区留自己的邮箱

我们评论区见

原创不易,欢迎点赞噢

文章首发:公众号【知秋小一】

文章同步:掘金,简书

原文链接 :写了个自动化脚本,每日更新疫情数据

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341