简单易行的空气质量指数日历史数据爬取
效果
目录
空气质量历史数据官网,戳它直达!
前言自雾霾深度调查纪录片《柴静雾霾调查:穹顶之下》的出现后,有关环境问题的各种报道不断涌出,也使得各个学科领域针对环境问题发表诸多文献,如计量经济学中的对某某市的空气质量影响因素的计量经济学分析等等。
但在这其中,有关数据的获取永远是报道、分析的第一步!
承接上篇月数据,日数据的爬取大同小异,下面就让我们手把手from scratch地爬虫收集齐全国384个地区城市的各个城市的各个月份的空气质量指数日统计历史数据
与上篇空气质量指数月统计历史数据爬取的方法大同小异,其实本篇不过是增加了许多技巧和细节(且在这些技巧和细节中与爬虫本身无关,只是实践中会遇到的一些注意事项)
注!!!:如果你已经完全掌握了上篇的方法,请自行亲手实验,爬取更为细致的日数据,掌握这类方法可以完全解决大部分的爬虫问题。
安装:
!pip install lxml -i https://pypi.douban.com/simple
!pip install selenium -i https://pypi.douban.com/simple
!pip install urllib # 这里没加国内镜像是因为没有urllib
!pip install tqdm # 非必须,用于显示迭代时间
!pip install pickle # 非必须,用于保存字典
使用:(如果你的环境还缺少下述包,请按上述方式进行pip)
import os
import re
import time
import pandas as pd
from tqdm import tqdm
import requests
from urllib import parse
from selenium import webdriver
除了在环境中安装上述包,只需再下载一个phantomjs,根据你的Windows、Mac、Linux等选对应系统的一路安装,记住最终的本地安装地址即可。
II Citys 获取网站内的城市名和对应链接同样,这里使用更简单上手的爬取网站内容的方法,requests + re:
(1) 通过requsets获取网页内容
(2) 通过re正则抽取信息
2.1 requsets 网页内容
注意,此时我们是要获取目标网站内的384个城市的名称,此时仅需两行代码便可得到目标网站链接的内容信息,如下图
url = "https://www.aqistudy.cn/historydata/" # 目标网站链接
url_text = requests.get(url).text # 目标网站的内容信息
请仔细观察我们的目标网站链接,有没有发现目标城市名称都是隐含在诸如此类的
<a href=…> XXX</a>HTML语言中,两个<a>间XXX便是"我们想要提取的目标城市名称"
2.2 re 正则提取
在上面我们已经获得了目标网站内的内容信息,但是我们并不需要诸如<a href = > 、<li>等内容。在此,让我们用一种有一定普适性的简单方法,正则表达式。
请不要畏惧正则表达式re,这只是个可以很好帮你提取信息的工具包,且不需要刻意去记.*?等含义内容,这类工具向的忘记了查一下即可。你甚至可以考虑喜欢re,它能做的既简单又优美。
# (2) 通过re正则抽取信息
pattern = re.compile(r'<a href="monthdata.php\?city=\w+">(\w+)</a>\r\n') # 要在?前加\转义符
citys = pattern.findall(url_text) # 获取384个城市名
print("已获取{}个城市名,第一个城市是{},最后一个城市是{}".format(len(citys),citys[0],citys[-1]))
在此,你仅需记住一件事,找到你想要从文本中提取的内容(如词汇、数字等),和该内容的模式(规律)。
模式(规律)可以经过观察得到,其实目标城市名称都是隐含在诸如此类的<a href=…> </a>HTML语言中。
提取内容便交给正则表达式来解决,需要提取内容在模式中用()代替即可,即:
<a href=“monthdata.php?city=\w+”>(\w+)\r\n,在此还需用\w+来代替不断改变的城市名
2.3 url 需爬取的城市月数据网站链接
获得所有城市名称后,也就获得了需要爬去网站的url网站链接:
# 如北京,只需要在base_url + '北京'即可
base_url = 'https://www.aqistudy.cn/historydata/monthdata.php?city='
# 即北京:
beijing_url = base_url + '北京'
在此,值得注意的是 爬虫可并不能识别中文字符,要转成它的语言,将中文字符转成URL字符也很简单,仅需一行代码:
```python
# from urllib import parse
# 第一步已经导入包了 urllib中的parse可以做到将中文字符转成URL字符:
beijing_url = base_url + parse.quote('北京')
看,这就是北京的空气质量历史数据网站链接了:
https://www.aqistudy.cn/historydata/monthdata.php?city=%E5%8C%97%E4%BA%AC
有了城市网页url网页链接,我们可以提取网站表格内的数据了,注意,我们此时要收集的是日数据,而上述链接是每个城市的月数据表,还需要通过月数据表进入对应城市的各个月的日数据网站。
2.4 City_day_url 城市的日数据网站链接
在上一步中,我们已经得到了如上页面的城市月数据网站链接**(左图),我们只需进入上图箭头处的网站(右图)**便可得到对应城市的日数据了。
那我们该怎么通过作图的月数据网站得到日数据网站链接呢?承接上文的方法。
III Webdriver 爬取网站表格数据3.1 修改phantomjs的地址
配置你刚刚下载的phantomjs的地址
driver = webdriver.PhantomJS(r'E:\\Tools\\phantomjs-2.1.1-windows\\bin\\phantomjs.exe')
在配置好phantomjs后,driver.get(city_url) 获取网页,再只需一步 driver.page_source 即可获取网页内的数据,此时的数据会是一个列表,取[0]即可获取所需DataFrame的内容。
3.2 DataFrame 获得月份
DataFrame的处理便更为简单方便,仅需一行代码即可得到每个城市的所有不同月份:
month = dataframe['月份'].to_list()
3.3 url 日数据网站链接
根据 DataFrame得到的是城市的所有月份,如北京的2013-12、2014-01、…、2020-09。
在此需要强调一点,各个城市的日数据网站链接与月数据网站链接并不只是后缀不同,中间.php?也有变化,应该将之前月数据网站前缀链接的monthdata.php?改成daydata.php?。这也是笔者第一次实验失败的原因,如果不改这个地方将只会得到图像,而没有表格数据供我们爬取。
month = dataframe['月份'].to_list()
# 注意,这里要将base_url中的monthdata改成daydata,否则访问的url是图表版日数据
day_base_url = 'https://www.aqistudy.cn/historydata/daydata.php?city='
for m in month:
url = day_base_url + parse.quote(city) + '&month=' + str(m)
citys_month_url[city].append(url)
实现代码如下
citys_month_url = {}
# 月数据网站前缀链接
base_url = 'https://www.aqistudy.cn/historydata/monthdata.php?city='
# 改成自己的本地路径
driver = webdriver.PhantomJS(r'E:\\Tools\\phantomjs-2.1.1-windows\\bin\\phantomjs.exe')
# 便利所有城市,根据城市建立字典,保存每个城市的所有月份下的日数据网站链接
for city in tqdm(citys):
citys_month_url[city] = []
city_url = base_url + parse.quote(city)
driver.get(city_url)
time.sleep(2)
dataframe = pd.read_html(driver.page_source)[0]
time.sleep(1)
month = dataframe['月份'].to_list()
# 注意,这里要将base_url中的monthdata改成daydata,否则访问的url是图表版日数据
day_base_url = 'https://www.aqistudy.cn/historydata/daydata.php?city='
for m in month:
url = day_base_url + parse.quote(city) + '&month=' + str(m)
citys_month_url[city].append(url)
print('城市月份url,收集完成')
至此,我们已经收集到了城市的所有日数据网站链接,有了url剩下只是时间问题了。
3.4 pickle保存
获取日数据网站链接还是需要一定时间的,如上图需要22分19秒。安全起见,让我们保存以下这个city_month_url字典吧,该怎么保存呢?
pickle永远的神!
import pickle
# 通过pickle保存字典
with open("citys_month_url.file", "wb") as f:
pickle.dump(citys_month_url, f)
# 通过pickle读取字典
with open("citys_month_url.file", "rb") as f:
citys_month_url = pickle.load(f)
for i in citys_month_url['阿里地区']:
print(i)
四行代码即可实现字典的保存,还是很方便实用的,当然这只是为了安全起见。
如何通过网站链接爬取表格数据呢?请看本篇2.3,同样的方法
for city in tqdm(citys_month_url.keys()):
for month_url in citys_month_url[city]:
month = month_url[-7:] # ’2016-17‘ 倒数七个字符
driver.get(month_url)
time.sleep(2)
dataframe = pd.read_html(driver.page_source)[0]
time.sleep(1)
path = r'E:\\Data\\{}'.format(str(city))
folder = os.path.exists(path)
if not folder:
os.makedirs(r'E:\\Data\\{}'.format(str(city)))
dataframe.to_csv(r'E:\\Data\\{}\\{}.csv'.format(str(city),str(month)),mode='a+',encoding='utf_8_sig')
print('城市:\t',city,'日数据收集完毕')
print('所有城市日数据,已收集完成!')
仅需大约22个小时即可…当然这只是tqdm的预估
其实也还很正常的,384个城市 * 70(以阿坝州为例,有70个月份,即70组月数据) = 26880 个日数据.csv
22 h = 1320 min = 79200 s
79200 / 26880 = 2.94 个/秒
其实平均每个表格仅需大约3秒,提问! 为什么是大约3秒?(tip:代码睡觉了)
答案:time.sleep(2) + time.sleep(1) = 3秒,程序还是很乖的,tqdm估计的也是蛮准的了。
V 完整代码import os
import re
import time
import pandas as pd
from tqdm import tqdm
import requests
from urllib import parse
from selenium import webdriver
url = "https://www.aqistudy.cn/historydata/" # 目标网站链接
url_text = requests.get(url).text # 目标网站的内容信息
# (2)模式提取信息,仅需(\w+)替换目标字符即可
pattern = re.compile(r'<a href="monthdata.php\?city=\w+">(\w+)</a>\r\n') # 要在?前加\转义符
# 正则提取
citys = pattern.findall(url_text) # 获取384个城市名
citys_month_url = {}
# 月数据网站前缀链接
base_url = 'https://www.aqistudy.cn/historydata/monthdata.php?city='
driver = webdriver.PhantomJS(r'E:\\Tools\\phantomjs-2.1.1-windows\\bin\\phantomjs.exe')
# 日数据网站链接
for city in tqdm(citys):
citys_month_url[city] = []
city_url = base_url + parse.quote(city)
driver.get(city_url)
time.sleep(2)
dataframe = pd.read_html(driver.page_source)[0]
time.sleep(1)
month = dataframe['月份'].to_list()
# 注意,这里要将base_url中的monthdata改成daydata,否则访问的url是图表版日数据
day_base_url = 'https://www.aqistudy.cn/historydata/daydata.php?city='
for m in month:
url = day_base_url + parse.quote(city) + '&month=' + str(m)
citys_month_url[city].append(url)
print('城市月份url,收集完成')
# 日数据爬取
for city in tqdm(citys_month_url.keys()):
for month_url in citys_month_url[city]:
month = month_url[-7:]
driver.get(month_url)
time.sleep(2)
dataframe = pd.read_html(driver.page_source)[0]
time.sleep(1)
path = r'E:\\Data\\{}'.format(str(city))
folder = os.path.exists(path)
if not folder:
os.makedirs(r'E:\\Data\\{}'.format(str(city)))
dataframe.to_csv(r'E:\\Data\\{}\\{}.csv'.format(str(city),str(month)),mode='a+',encoding='utf_8_sig')
print('城市:\t',city,'日数据收集完毕')
print('所有城市日数据,已收集完成!')
日数据爬取的代码可优化为下面这段,这样我们就可以通过一个csv文件保存所有的日数据了,而非26880个不同城市不同月份的表格数据。
# 日数据爬取
df = pd.DataFrame(columns = ['日期', 'AQI', '质量等级', 'PM2.5', 'PM10', 'SO2', 'CO', 'NO2', 'O3_8h','城市'])
for city in tqdm(citys_month_url.keys()):
for month_url in citys_month_url[city]:
month = month_url[-7:]
driver.get(month_url)
time.sleep(1)
dataframe = pd.read_html(driver.page_source)[0]
time.sleep(1)
if dataframe.shape[0] == 0:
dataframe = pd.DataFrame(columns = ['日期', 'AQI', '质量等级', 'PM2.5', 'PM10', 'SO2', 'CO', 'NO2', 'O3_8h','城市'])
dataframe.loc[0,:] = [None,None,None,None,None,None,None,None,None,city]
else:
dataframe['城市'] = city
df = df.append(dataframe)
print('城市:\t',city,'日数据收集完毕')
print('所有城市日数据,已收集完成!')
df.to_csv('E:\\Data\\day.csv',mode='a+',encoding='utf_8_sig')
其实这里的完整代码可以优化哦,从整体的代码考虑,这里有两个依次进行的迭代,可以改成一个迭代的实现。但是我们在实验的时候是一步一步跑的,因此上述完整代码也是每一步的综合,更易于调试和阅读。
补充有好多伙伴询问怎么爬取指定城市的,下面补充有关指定城市爬取的完整代码,只是把最后的城市迭代改成指定城市而已。
import os
import re
import time
import pandas as pd
from tqdm import tqdm
import requests
from urllib import parse
from selenium import webdriver
# (2)模式提取信息,仅需(\w+)替换目标字符即可
pattern = re.compile(r'<a href="monthdata.php\?city=\w+">(\w+)</a>\r\n') # 要在?前加\转义符
url = "https://www.aqistudy.cn/historydata/" # 目标网站链接
url_text = requests.get(url).text # 目标网站的内容信息
# 正则提取
citys = pattern.findall(url_text) # 获取384个城市名
citys_month_url = {}
# 月数据网站前缀链接
base_url = 'https://www.aqistudy.cn/historydata/monthdata.php?city='
driver = webdriver.PhantomJS(r'E:\\Tools\\phantomjs-2.1.1-windows\\bin\\phantomjs.exe')
# 日数据网站链接
city ='青岛'
citys_month_url[city] = []
city_url = base_url + parse.quote(city)
driver.get(city_url)
time.sleep(2)
dataframe = pd.read_html(driver.page_source)[0]
time.sleep(1)
month = dataframe['月份'].to_list()
# 注意,这里要将base_url中的monthdata改成daydata,否则访问的url是图表版日数据
day_base_url = 'https://www.aqistudy.cn/historydata/daydata.php?city='
for m in month:
url = day_base_url + parse.quote(city) + '&month=' + str(m)
citys_month_url[city].append(url)
print('城市月份url,收集完成')
# 日数据爬取
for month_url in citys_month_url[city]:
month = month_url[-7:]
driver.get(month_url)
time.sleep(2)
dataframe = pd.read_html(driver.page_source)[0]
time.sleep(1)
path = r'E:\\Data\\{}'.format(str(city))
folder = os.path.exists(path)
if not folder:
os.makedirs(r'E:\\Data\\{}'.format(str(city)))
dataframe.to_csv(r'E:\\Data\\{}\\{}.csv'.format(str(city), str(month)), mode='a+', encoding='utf_8_sig')
print('城市:\t', city, '日数据收集完毕')