一次不太成功的搬砖(上):爬取法定传染病疫情月报数据
文章目录
带怀旧色彩的源起
清明节跑去一个休闲浴场鬼混,在电影厅懒散地看掉了《生化危机6》。场地很豪华(但我就是不透露门牌地址),然而剧情不怎么样——女主光环实在太亮了。倒是病毒-丧尸-疫苗的急性传染病建模设定引起了我的一些职业回忆。
毕业后,我曾在基层疾控中心干过一年多,主要做疫苗接种规划和传染病控制。除了定期不定期地出外勤下现场,就是统计数字、写报告、汇编材料。这些数字沿着行政金字塔的梯级层层上卷,最终汇入国家卫生部疾控局官方报表的大海中。
说是大海,视觉上其实就是类似这样的一张表格:
一晃很多年过去了。籍着这个由头,我又登上了卫生部(现在叫卫计委了,早晚改回卫生部)的官网,那感觉就像——拜会一个久寓故居,新近敲了墙、刷了房门的老派的朋友。那些月报还原封不动,化石一样静静地躺在信息动态里。
这种格式报告,行文和结构都很固定,特别适合用机器人来自动生成。比如最新这期,正文就包括了发病、死亡合计总数,以及甲乙丙类各自的发病、死亡数。明细数据放在附表里。掐指一数,从2004年到现在,卫计委也积攒了140多份月报,不少了。何不爬下来看看?
所以,尽管当时身还在浴场,但心在砖场了,已经!
搬砖设想
搬砖虽然是个贱活儿,但也要讲技巧。好在从技术角度,爬这些页面是再简单不过的事。只需要两步就完了:
- 把所有月报页的链接抓到
- 顺着这些链接把所有页面源码都爬下来
更好的消息是所有页面都是静态的。所以只要用rvest
就够了,整页爬下来,所有信息就包含在html里面(事实上并不是)。
爬到所有月报页后,解析内容又是两步走:
- 把正文里的发病/死亡总数抽出来,跑个时序图看看周期性
- 把附表里的内容抽出来,分病种跑些分析
Easy as a pie!等我一盏茶的功夫,我去去就来。(结果茶馊了都没能回来)
爬目录
“传染病预防控制”这个分类的URL是很有规律的。第一页是http://www.nhfpc.gov.cn/jkj/s2907/new_list.shtml,第二页就是http://www.nhfpc.gov.cn/jkj/s2907/new_list_2.shtml。也就是说23个目录页拼一下就出来了:
|
|
rvest
有了URL,就可以爬源码了。当然可以把网页当文本,直接readLines
,然后拿XML
包写解析规则。但我们学R图什么?还不就是免费+有很多包方便偷懒?对于静态网页,当然毫不犹豫rvest
。
rvest
的核心函数是read_html
、html_nodes
、html_text
和html_table
。
read_html
很好理解,把页面读进来。这个页面会被封装为一个xml_nodes
对象。html_nodes
则负责从xml_nodes
对象中提取某个节点的内容,封装成xml_nodeset
对象。- 进一步,如果要把里面的内容都当做文本提出来,用
html_text
。 - 有表格(<table>…</table>)的话,用
html_table
,直接输出梦寐以求的data.frame。
唯一费解的概念也就是“节点”。但只要对html和xml略有了解,就很容易理解。一个html文件的典型结构是
|
|
从缩进结构可以看出,html
是根节点,下一级是两个元素子节点head
和body
,head
的子节点是title
,body
的子节点是h1
,它们还可以有文本、属性或注释子节点。以此类推。
怎么看页面节点结构呢?用Chrome访问目录页,F12查看文档结构,或者右键“查看页面源代码”。
每一篇信息动态都在一个列表节点<li>里,最终都被包进一个无序列表父节点<ul>里。这个无序列表元素的类型是“zxxx_list”(果然还是万能的拼音首字母命名法,“资讯信息”)。所以拿到这个节点后,提取目录信息就很简单了:<a>节点内有标题和链接,<span ml>节点内有发布日期。
|
|
当然,这些动态不都是疫情报告。所以还要利用正则表达式过滤一下,并把链接格式补完。
- 翻了几页,所有月度疫情公报都有“xx月全国法定”或“xx月份全国法定”的字样。
- 所有链接都是相对路径,形如"../../xxx",需要替换为完整路径。
|
|
除了链接,我还想知道每份月报讲的是何年何月,将来解析了数据,直接可以对上日期。这时候标题就派上用场了,因为里面直接包含有年月信息。但这里就有一个坑:不是所有标题都完整。最早的一些月报是不含年份的,得结合发布日期来补全。
|
|
toc
数据集长这样:
万里长征踏出了第一步。
爬网页
这一步就比较简单了,干脆就先把网页代码先弄下来。在这里就不多考虑反爬虫问题了(因为试下来卫计委官网好像没有反爬虫机制,再说才爬它一百多个页面,有啥好反的)。电脑虽然配置不济,好歹有4个核。为了加点速,充分利用CPU(R默认只用单核)算力,不妨拿doParallel
包做点并行计算处理。
|
|
pages是一个149个文本元素构成的大向量,用日期作为向量命名。
|
|
|
|
解析发病和死亡总数
第二个大坑正在缓缓靠近:不是所有月报都有发病和死亡总数。
- 2005年以前,压根不报告丙类传染病
- 2010年以前,不汇报发病和死亡总数,且甲乙类合并计数,丙类另报
- 甲类汇总的文本多样化很强,一般鼠疫和霍乱还分开来报,正则写起来会死。
所以最后决定:
- 抽得到总数的,直接用总数
- 没有报告总数的,会向后抽到甲乙类合计,或甲类,或乙类。无论哪种情况,乙类+丙类基本等于总数
好机(偷)智(懒)!
|
|
历尽艰苦,终于得到了这个该死的数据集。把历月发病和死亡数做个时序图。
|
|
年周期性还是很显著的。
抽取各明细病种数据
接下来进行明细分病种数据的爬取。
理论上,把表格抽取出来就完了嘛。但是尝试了一下发现竟然有问题。随机看了一下,发现自己被坑惨了。附表有三类:
- 网页表格,这种最好利用
- 附件,如doc、xls
- 图片(!),如png,jpg
竟然还有直接传个截图当公报的,怎么不去爆炸?
本来打算html_table
跑一遍就愉快地合并数据框了,却不料跑进了一个马里亚纳深坑里。只能改变策略,先把这些附件都下载下来。
|
|
看到这些文件齐齐整整码在硬盘里,心下暂时宽慰了一点。
然而让我们回头看看疫情月报附表们可恨的多样性吧。
|
|
|
|
140多篇动态报道,只有67篇用了网页表格附件,有53篇是MS office文档,还有26篇直接贴图了事。
真是一叶落而知天下秋。卫生系统的数据化水准,跟金融系统比起来真是天上地下,一个站着,一个躺着。