一个利用API自动下载空气污染数据的脚本

Posted by rogerclarkgc on 周三 21 十二月 2016

怎么来的

最近北京的天气简直和活在寂静岭里一样,天天pm2.5爆炸,我已经关注北京空气质量有一定时间了,非常神秘的是,不仅pm2.5爆炸,北京大气中No2的水平也达到了黄色污染水平,基本超过了100微克每立方米,按照平常的情况,北京一二环内才有可能达到这个数值,并且仅仅在一天中的晚间出现,而现在北京市基本所有的空气检测点都超过了100微克每立方米。看看北京市环境监测中心的数据点:

no2points

可以看到,北京城区除了海淀区植物园是绿色的外,其他都是黄色的。

讲道理,虽然空气污染这么严重,但我还是有点开心的,因为我的研究和空气中的No2息息相关,如果空气很干净,No2天天在20个ppb的本底值附近徘徊,那我的研究恐怕进行不下去了。。。

怎么做的

以上的都算是题外话,为了方便我对No2浓度数据的持续获取,我写了个小脚本用来持续爬取网上的监测数据来保存到本地硬盘。

这里要感谢PM25.in这个网站,这个网站免费提供了一个封装过的API,可以很容易的通过构造一个json请求来获取他们收集的全国各地的No2监测站的数据,关键是免费的!

我把这个API的帮助文档看了一下,它接受一个json请求,返回的对象也是一个json列表,而python的基础库json能够完成python对象和json对象的转换,因此两者理论上是完美兼容的。

解决了json对象的解析,最关键也最烦人的就是编码问题,由于返回的json对象中含有中文,而中文在各个编码集中编码并不通用,所以需要弄清楚两个问题:

  • 我获取到的原生json列表是什么编码的?
  • 在python环境下我采用什么编码来显示、读写这些数据?

第一个问题我在PM25.in的api帮助文档中找到了答案,返回的原始数据是由utf-8编码集编码,这样,知道了这一个信息,我就能够在python环境中用decode()方法将编码转化为unicode。

实际上,后续发现,json库中的json.load()方法能够直接转化基于ascii的编码集,这包括了utf-8,因此可以直接用这个方法读入返回的json对象。

第二个问题在经过几次尝试后找到了解决办法,先将json列表转化为python中的list对象,然后统一用decode('utf-8')方法转化为unicode编码,最后由于含有中文,在写入文件时,采用了encode('gbk')方法,将所有字符转化为国标扩展字符集。

这样,解决了最关键的机制问题,这个业务的流程变成这样:

  1. 构造、发起、读取请求:利用urllib2库的urllib2.Request()方法构造一个GET类请求,用urllib2.urlopen()发起和读取请求
  2. 利用json库解析返回结果,编码转换:utf-8 → unicode
  3. 利用sys库写入txt文件:编码转换 unicode → gbk
  4. 定时的运行上面三步,每小时请求一次,获取No2的小时变化数据

做的什么样子

老样子,为了防止自己忘记怎么用这个脚本,我把它写成一个类,并且在这里记下这个类的一些情况:

No2API类方法属性表:

这个类比较简单,包括如下东西:

['__doc__', '__init__', '__module__', 'city',

'pullData', 'runspy', 'saveDataLog', 'saveNO2Data',

'startRequest', 'timeformat', 'token', 'url', 'url_all']

前三个没什么好说...

citytoken是构造json请求的两个元素,前者是要获取的城市名,后者是API的token相当于身份标示,由于没有申请到一个token,只好用PM25.in官方的公共token了,好在运行了一周,没有发现因为提交次数过多而被封禁的问题。

self.urlself.url_all用来构造完整的json请求,其实就是一个url

self.timeformat是规定的时间格式,在后面写入文件时要给每次爬取打上时间戳

self.pullData是一个空的列表,用于存储爬取数据

self.startReques()用来爬取数据的方法,把数据转换为列表对象存贮在self.pullData

self.saveDataLog()是用来存储数据的方法,是按行写入txt文件的

self.NO2Data()是用来存储No2数据的,前面的self.saveDataLog()其实是返回的原始数据,内容太繁复,因此这个方法只挑选感兴趣的No2浓度数据来存贮。

self.runspy()这个方法就是一次业务流程写在了一起,方便在其他脚本中调用的。

在No2API模块中,我还单独写了一个runtask()函数,用来定时运行程序,其实就是一个不断进行的while循环,不断判断时间是否到达下一个任务点,如果是,就执行任务。

例子

要使用它很简单

import time
from NO2pull import No2API
from NO2pull import runtask

pull = No2API()
runtask(pull.runspy, day = 0, hour = 0, minute = 30, 
second = 0)

这样,每三十分钟就会发起一次请求,数据能自动存储,真是方便不少啊

后记

在连续运行了一周后,确实收获了大量数据,但由于我的运行间隔设置成了每30分钟运行一次,所以出现了许多重复观测,给我在R里分析数据造成了小小的麻烦,在去除了重复观测后,我用R做出了13个监测点的周变化和日变化图,并进行了多重比较,感觉这个脚本有长期使用的价值(只要pm25.in还开放api接口,并且这个公共token一直可以用,不然我只能老老实实分析网页写正则表达式了),对这个API,以及带来的更多的需求和问题,我有如下展望:

  • 完善功能,加入自动生成每日浓度变化的脚本(这个基本完成,将在后续blog中记录上)
  • 学习一下R和python中的多重比较,基本掌握操作,熟悉背后的数学原理。
  • 继续懒无止境,用python写一个自动分析脚本,自动分析每日爬取的数据,做显著检验,多重比较,岂不是美滋滋?
  • 我把代码同步到了我的github上,方便后续的开发。

tags: python