怎么来的
最近北京的天气简直和活在寂静岭里一样,天天pm2.5爆炸,我已经关注北京空气质量有一定时间了,非常神秘的是,不仅pm2.5爆炸,北京大气中No2的水平也达到了黄色污染水平,基本超过了100微克每立方米,按照平常的情况,北京一二环内才有可能达到这个数值,并且仅仅在一天中的晚间出现,而现在北京市基本所有的空气检测点都超过了100微克每立方米。看看北京市环境监测中心的数据点:
可以看到,北京城区除了海淀区植物园是绿色的外,其他都是黄色的。
讲道理,虽然空气污染这么严重,但我还是有点开心的,因为我的研究和空气中的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')
方法,将所有字符转化为国标扩展字符集。
这样,解决了最关键的机制问题,这个业务的流程变成这样:
- 构造、发起、读取请求:利用urllib2库的
urllib2.Request()
方法构造一个GET类请求,用urllib2.urlopen()
发起和读取请求 - 利用json库解析返回结果,编码转换:utf-8 → unicode
- 利用sys库写入txt文件:编码转换 unicode → gbk
- 定时的运行上面三步,每小时请求一次,获取No2的小时变化数据
做的什么样子
老样子,为了防止自己忘记怎么用这个脚本,我把它写成一个类,并且在这里记下这个类的一些情况:
No2API类方法属性表:
这个类比较简单,包括如下东西:
['__doc__', '__init__', '__module__', 'city',
'pullData', 'runspy', 'saveDataLog', 'saveNO2Data',
'startRequest', 'timeformat', 'token', 'url', 'url_all']
前三个没什么好说...
city
、token
是构造json请求的两个元素,前者是要获取的城市名,后者是API的token相当于身份标示,由于没有申请到一个token,只好用PM25.in官方的公共token了,好在运行了一周,没有发现因为提交次数过多而被封禁的问题。
self.url
、self.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上,方便后续的开发。