继续创造,加速成长!这是我参与「掘金日新方案 10 月更文应战」的第1天
摘要:
针对某地的天气预报数据爬取,选择了爬取天气状况,主要有气温、降水量、相对湿度、空气质量AQI四类数据,并对其进行图画复原。
遇到的问题:
首先,直接用PyQuery来直接获取html源代码会呈现很多乱码问题,无法得到咱们想要的数据
其次,在获取详细城市天气预报网页的超链接时,咱们能够选用正则表达式或其他解析库进行解析来获取网址。
接着,在详细城市的天气预报网页中,假如运用PyQuery来解析获取咱们想要的数据,会呈现解析错误的状况(解析出来的数据并非咱们想要的数据),究其原因是因为在同一级标签中的标签名与类名都一致,且当中都有script标签来贮存动态json数据,然后就直接回来了第一个符合咱们设置的条件的json字符串。运用PyQuery对全文搜索无法达到咱们的目标,且还有个json数据里边都是以字典类型来贮存相应的数据,需求对json数据进行json解析.
json解析之后,怎么得到相应的json的值,并进行暂时贮存
画图时,因为每个城市都有四个图要画,假如每个城市分隔四个图片来画的画,那么关于查询多个城市的适合,会很费事,怎么处理这一问题?而且在数据贮存中,原数据是字符串类型,导致数据需求进行类型转化,不然最终画图时会呈现一些坐标的误差
数据的保存,每次咱们只能得到一个城市的数据,假如直接进行保存,会呈现后一个城市将前一个城市的数据给覆盖掉的状况
处理办法:
关于第一个问题,咱们采取了不直接运用PyQuery库来获取源代码,而是选用了最直接的request.get来得到一个服务器相应response目标。
为什么用request库呢?
这是因为request.get得到的服务器呼应的目标中,它的网页源代码text函数中,咱们能够运用decode办法来设置咱们需求的编码方式进行解析,这样,咱们就能够处理网页源代码是乱码的问题了。
代码如下:
def get_html(self, url):
# 设置标头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36'
}
# 得到get恳求的回应
response = requests.get(url, headers=headers)
# 进行文本编码,去除乱码状况
html = response.text.encode('iso-8859-1').decode('utf-8')
# 回来源代码
return html
关于第二个问题,正则表达式需求逐个匹配,有点过于繁琐,所以咱们选用了PyQuery库来进行解析,快速且易读,而且,因为咱们的链接在多个相同结构的标签里边,那么用正则匹配出来的是字符串,可能是连在一起,欠好别离。而PyQuery库中,咱们能够运用items()函数生成一个生成器,经过遍历生成器里的内容,能够单独地获取咱们需求的每一个城市的网址。 代码如下:
def get_new_url(self, html):
# 将网页源代码转化成pq目标
html = pq(html)
# 获取详细网址的生成器,其间每个生成器内的子器代表着一个城市的超链接
items = html('div.lqcontentBoxH>div.contentboxTab>div.contentboxTab1>div.contentboxTab2>div.hanml>div.conMidtab>div.conMidtab2>table>tr>td.last>a').items()
# 贮存网址
new_url = []
# 获取href特点中的内容
for item in items:
new_url.append(item.attr('href'))
# 回来列表类型的超链接
return new_url
关于第三个问题,因为运用PyQuery库进行解析不太便利,咱们就不再运用pq库来解析了,而咱们以处理1中的代码能够得到一个response.text进行过编码后的网页源代码,其类型如text所言,是一个字符串类型。咱们知道,字符串类型用有一个find办法能够帮咱们快速找到咱们指定的字符串内容是否在其之中,存在时会回来第一个字符的索引值。由此,咱们能够运用find办法来帮咱们快速定位所找json字符串的方位;然后关于script标签的贮存的动态数据,其内容是多层字典的形式,咱们能够运用json.loads办法对其进行解析,将字符串类型转化成咱们想要的字典类型,代码如下:
# 找到od2后的下一层的详细数据
text = js_['od']['od2']
# 找到od1中贮存的城市名
city = js_['od']['od1']
# 暂时贮存天气数据(创建数表),检查了一下数据,除了od23不知道是什么数据,其他的都能够知道,因而在设置列索引时,咱们就直接进行了修改,便利咱们后续检查
temp_weather = pd.DataFrame([], columns=['时刻', '温度', 'od23', '风向', '风力', '降水量', '相对湿度', '空气质量'],index=[x+1 for x in range(len(text)-1)])
# 写入数据
for i in range(len(text)):
# 为什么需求越过这个呢?这是因为od2中贮存的其实有25组数据,即25个小时的数据,即会有一个是两天的同一个时辰的数据,后边画图会有些费事,这儿为了避归这一费事,且第二天同一时辰的数据对后边也没有太多的影响,就选择越过这一问题了
if i == 0:
continue
# 写入时刻,没有进行类型改变,因而时刻是字符串类型,而这儿的横作为有23开端是因为其字典的次序自身便是反过来的
temp_weather.iloc[23-i, 0] = text[i]['od21']
# 写入温度,转化成float类型
temp_weather.iloc[23-i, 1] = float(text[i]['od22'])
# od23不清楚指的是什么内容,但对本爬虫的要求无影响,故不做处理,之后会进行删去处理
temp_weather.iloc[23-i, 2] = text[i]['od23']
# 写入风向,字符串类型
temp_weather.iloc[23-i, 3] = text[i]['od24']
# 写入风力,因为不做要求,因而不做任何处理,后续会进行删去处理
temp_weather.iloc[23-i, 4] = text[i]['od25']
# 写入降水量,转化成浮点型
temp_weather.iloc[23-i, 5] = float(text[i]['od26'])
# 写入相对湿度,转化成浮点型
temp_weather.iloc[23-i, 6] = float(text[i]['od27'])
# 写入空气质量,因为最终一处呈现缺失(空值),现在还转化不了浮点型,后边会进行处理
temp_weather.iloc[23-i, 7] = text[i]['od28']
# 得到数表后,将od23和风向风力的数据进行删去
temp_weather.drop(labels=['od23', '风向', '风力'], axis=1, inplace=True)
# 将那个字符串空值赋予缺失值处理
temp_weather.iloc[23, 4] = np.nan
# 将空气质量的类型由字符串转化成浮点型
temp_weather['空气质量'] = temp_weather['空气质量'].astype('float')
# 然后将刚刚那个缺失值进行处理,这儿运用的是前填充,使其与上一个小时的AQI值坚持一致
temp_weather.fillna(method='ffill', inplace=True)
关于多画图问题,咱们运用多图绘画函数即可,即matplotlib.pyplot.subplot,能够完成多个图片放在一个图纸上的操作,减少了费事。而关于详细的数据的类型是字符串的问题,咱们能够在上述贮存数据入数表时进行强制类型转化。
为什么当咱们的横轴坐标列表的数据是字符串时,会呈现一个坐标轴刻度散乱散布的状况呢?
其实,这是因为咱们在画图的时分,函数内部会帮咱们把数字型的数据在刻度中按次序排列(没有用yticks/xticks之前),而当数据是字符串时,字符串无法进行巨细排序,因而,函数只能老老实实地将列表中字符串的次序写入,然后导致了刻度散乱的问题。可是呢,因为假如把时刻也给强制类型转化成整型或许浮点型的话,那么时刻在横坐标上就不再按正常的时刻次序从前到后写入了,而是被排序成0~23来排列,这样又不符合咱们的要求,所以上面在写入时刻的数据时,咱们能够看到我并不运用类型转化。代码如下:
def printing(self, df):
# 坚持时刻(为字符串类型,这样子就不会在后续的图片中呈现matplotlib主动将数值进行排序从0开端的状况,使之从今时开端)
x = list(df.iloc[:, 0])
# 顺次贮存温度、降水量、相对湿度、空气质量AOI的值,转化成列表类型
temperature = list(df.iloc[:, 1])
rainfall = list(df.iloc[:, 2])
humidity = list(df.iloc[:, 3])
air_quality = list(df.iloc[:, 4])
plt.figure(figsize=(20, 10), dpi=80)
plt.subplot(221)
# AQI图
# 贮存AQI最大值
max_air = max(air_quality)
# 画出柱形图
plt.bar(x, air_quality, color='pink', label='空气质量(AQI)')
# 设置横纵坐标轴的标签--对应显现的内容的意义
plt.ylabel('数值')
plt.xlabel('时刻/h')
# 设置图片标题
plt.title(f'{city}市24h内空气质量改变示意图')
# 设置文本框
plt.text(x=float(min(x)), y=max_air + 0.8, s=f'24h内AQI最高值为{max_air}', color='white',
bbox={
'color': 'gray',
'alpha': 0.5
})
# 显现出每条柱对应的值
for i in range(len(air_quality)):
plt.text(x[i], air_quality[i], '%.0f' % air_quality[i], ha='center', va='bottom', color='#74C476')
# 设置网格,仅设置y轴上的横线
plt.grid(alpha=1, axis='y', linestyle='--')
# 设置图例
plt.legend(loc='upper right')
plt.subplot(222)
# 相对湿度图
# 贮存最大相对湿度
max_humidty = max(humidity)
# 画出折线图以显现其改变
plt.plot(x, humidity, color='#74C476', label='相对湿度')
# 标出各点
plt.scatter(x, humidity, color='r')
# 设置横纵坐标标签,横轴表明时刻,纵轴表明相对湿度的巨细
plt.ylabel('相对湿度/%')
plt.xlabel('时刻/h')
# 设置图片标题
plt.title(f'{city}市24h内空气相对湿度改变图')
# 设置文本框,对图片进行恰当阐明
plt.text(x=int(min(x)), y=max_humidty - 0.4, s=f'24h内最高相对湿度为{max_humidty}%', color='white',
bbox={
'color': 'gray',
'alpha': 0.5
})
# 设置图例方位
plt.legend(loc='upper center')
# 降水量图
plt.subplot(223)
# 顺次贮存总降水量、最小/大降水量
sum_rain = round(sum(rainfall), 2)
min_rain = min(rainfall)
max_rain = max(rainfall)
# 用柱形图画出降水量状况
plt.bar(x, rainfall, color='pink', label='降水量')
# 设置横纵坐标轴的意义,横轴表明时刻,纵轴表明降水量
plt.xlabel('时刻/h')
plt.ylabel('降水量/mm')
# 设置图片标题
plt.title(f'{city}市24h内降水量示意图')
# 显现出每条柱对应的值
for i in range(len(rainfall)):
plt.text(x[i], rainfall[i], '%.1f' % rainfall[i], ha='center', va='bottom', color='#74C476')
# 在图片中设置文本,内容包括降水极值的阐明
# annotate可在样本点邻近进行阐明,可是不太漂亮
# text自己设置方位放置,统一阐明,能够加布景框,显得好亮点,芜湖,x、y为横、纵坐标,s输入所需显现的文字,调色,bbox是设置布景框用的
plt.text(x=int(min(x)), y=max_rain - 0.4, s=f'最高降水量为{max_rain}mm\n最低降水量为{min_rain}mm\n总降水量为{sum_rain}mm',
color='white',
bbox={
'color': 'gray',
'alpha': 0.5
})
# 设置网格线
plt.grid(alpha=1, axis='y')
# 设置图例内容
plt.legend(loc='upper right')
# 气温图
# 贮存最高/低气温值
plt.subplot(224)
max_tem = max(temperature)
min_tem = min(temperature)
# 画出温度改变图
plt.plot(x, temperature, color='#74C476', label='温度')
# 标出各时刻段的气温点方位
plt.scatter(x, temperature, color='blue')
# 设置横纵坐标轴的意义,横轴表明时刻,纵轴表明气温
plt.xlabel('时刻/h')
plt.ylabel('温度/℃')
# 设置图片标题
plt.title(f'{city}市24h温度改变示意图')
# 设置文本框,显现恰当的文字阐明以复原图画
plt.text(x=x[5], y=max_tem - 0.3, s=f'最高气温为{max_tem}ml\n最低气温为{min_tem}ml', color='white',
bbox={
'color': 'gray',
'alpha': 0.5
})
# 设置图例方位
plt.legend(loc='upper right')
plt.show()
关于第五个问题,现在还没有处理,而暂时不计划对数据进行贮存
最终便是将以上的办法进行一个类的包装啦。
全部代码如下:
import json
import pandas as pd
import requests
from pyquery import PyQuery as pq
from matplotlib import pyplot as plt
import matplotlib
import numpy as np
# 在图片中显现中文
matplotlib.rc("font", family="MicroSoft YaHei", weight='bold', size=13)
class Weather(object):
def get_html(self, url):
# 设置标头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36'
}
# 得到get恳求的回应
response = requests.get(url, headers=headers)
# 进行文本编码,去除乱码状况
html = response.text.encode('iso-8859-1').decode('utf-8')
# 回来源代码
return html
def get_new_url(self, html):
# 将网页源代码转化成pq目标
html = pq(html)
# 获取详细网址的生成器,其间每个生成器内的子器代表着一个城市的超链接
items = html('div.lqcontentBoxH>div.contentboxTab>div.contentboxTab1>div.contentboxTab2>div.hanml>div.conMidtab>div.conMidtab2>table>tr>td.last>a').items()
# 贮存网址
new_url = []
# 获取href特点中的内容
for item in items:
new_url.append(item.attr('href'))
# 回来列表类型的超链接
return new_url
def gain_data(self, new_html):
# 寻觅数据最初方位
n = new_html.find('observe24h_data')
# 末尾方位
m = new_html.find('}]}}')
# 挑选
text = new_html[n+18:m+4]
# 回来挑选后的代码数据(字符串类型)
return text
def printing(self, df):
# 坚持时刻(为字符串类型,这样子就不会在后续的图片中呈现matplotlib主动将数值进行排序从0开端的状况,使之从今时开端)
x = list(df.iloc[:, 0])
# 顺次贮存温度、降水量、相对湿度、空气质量AOI的值,转化成列表类型
temperature = list(df.iloc[:, 1])
rainfall = list(df.iloc[:, 2])
humidity = list(df.iloc[:, 3])
air_quality = list(df.iloc[:, 4])
# 设置整个的图纸巨细
plt.figure(figsize=(20, 10), dpi=80)
plt.subplot(221)
# AQI图
# 贮存AQI的最大值
max_air = max(air_quality)
# 画出柱形图
plt.bar(x, air_quality, color='pink', label='空气质量(AQI)')
# 设置横纵坐标轴的标签--对应显现的内容的意义
plt.ylabel('数值')
plt.xlabel('时刻/h')
# 设置图片标题
plt.title(f'{city}市24h内空气质量改变示意图')
# 设置文本框
plt.text(x=float(min(x)), y=max_air + 0.8, s=f'24h内AQI最高值为{max_air}', color='white',
bbox={
'color': 'gray',
'alpha': 0.5
})
# 显现出每条柱对应的值
for i in range(len(air_quality)):
plt.text(x[i], air_quality[i], '%.0f' % air_quality[i], ha='center', va='bottom', color='#74C476')
# 设置网格,仅设置y轴上的横线
plt.grid(alpha=1, axis='y', linestyle='--')
# 设置图例
plt.legend(loc='upper right')
plt.subplot(222)
# 相对湿度图
# 贮存最大相对湿度
max_humidty = max(humidity)
# 画出折线图以显现其改变
plt.plot(x, humidity, color='#74C476', label='相对湿度')
# 标出各点
plt.scatter(x, humidity, color='r')
# 设置横纵坐标标签,横轴表明时刻,纵轴表明相对湿度的巨细
plt.ylabel('相对湿度/%')
plt.xlabel('时刻/h')
# 设置图片标题
plt.title(f'{city}市24h内空气相对湿度改变图')
# 设置文本框,对图片进行恰当阐明
plt.text(x=int(min(x)), y=max_humidty - 0.4, s=f'24h内最高相对湿度为{max_humidty}%', color='white',
bbox={
'color': 'gray',
'alpha': 0.5
})
# 设置图例方位
plt.legend(loc='upper center')
# 降水量图
plt.subplot(223)
# 顺次贮存总降水量、最小/大降水量
sum_rain = round(sum(rainfall), 2)
min_rain = min(rainfall)
max_rain = max(rainfall)
# 用柱形图画出降水量状况
plt.bar(x, rainfall, color='pink', label='降水量')
# 设置横纵坐标轴的意义,横轴表明时刻,纵轴表明降水量
plt.xlabel('时刻/h')
plt.ylabel('降水量/mm')
# 设置图片标题
plt.title(f'{city}市24h内降水量示意图')
# 显现出每条柱对应的值
for i in range(len(rainfall)):
plt.text(x[i], rainfall[i], '%.1f' % rainfall[i], ha='center', va='bottom', color='#74C476')
# 在图片中设置文本,内容包括降水极值的阐明
# annotate可在样本点邻近进行阐明,可是不太漂亮
# text自己设置方位放置,统一阐明,能够加布景框,显得好亮点,芜湖,x、y为横、纵坐标,s输入所需显现的文字,调色,bbox是设置布景框用的
plt.text(x=int(min(x)), y=max_rain - 0.4, s=f'最高降水量为{max_rain}mm\n最低降水量为{min_rain}mm\n总降水量为{sum_rain}mm',
color='white',
bbox={
'color': 'gray',
'alpha': 0.5
})
# 设置网格线
plt.grid(alpha=1, axis='y')
# 设置图例内容
plt.legend(loc='upper right')
# 气温图
# 贮存最高/低气温值
plt.subplot(224)
max_tem = max(temperature)
min_tem = min(temperature)
# 画出温度改变图
plt.plot(x, temperature, color='#74C476', label='温度')
# 标出各时刻段的气温点方位
plt.scatter(x, temperature, color='blue')
# 设置横纵坐标轴的意义,横轴表明时刻,纵轴表明气温
plt.xlabel('时刻/h')
plt.ylabel('温度/℃')
# 设置图片标题
plt.title(f'{city}市24h温度改变示意图')
# 设置文本框,显现恰当的文字阐明以复原图画
plt.text(x=x[5], y=max_tem - 0.3, s=f'最高气温为{max_tem}ml\n最低气温为{min_tem}ml', color='white',
bbox={
'color': 'gray',
'alpha': 0.5
})
# 设置图例方位
plt.legend(loc='upper right')
plt.show()
if __name__ == '__main__':
# 爬取的网址
url = 'http://www.weather.com.cn/textFC/hn.shtml'
# 创建天气爬虫目标
national_w = Weather()
# 得到主页的html源代码
html = national_w.get_html(url)
# 获取该地区(华南、华北之类的)的每个城市的天气预报网址
new_url = national_w.get_new_url(html)
n = int(input(f'请输入要查询的城市数量(不大于{len(new_url)}的正整数):'))
for cnt in range(n):
# 获取详细城市的天气预报的源代码
new_html = national_w.get_html(new_url[cnt])
# 获取script标签里边的数据
datas = national_w.gain_data(new_html)
# 咱们所需求的数据在script标签里边的var变量里,内部是多层的字典类型,因而,咱们需求进行json解析转化成字典类型
js_ = json.loads(datas)
# 找到od2后的下一层的详细数据
text = js_['od']['od2']
# 找到od1中贮存的城市名
city = js_['od']['od1']
# 暂时贮存天气数据(创建数表),检查了一下数据,除了od23不知道是什么数据,其他的都能够知道,
# 因而在设置列索引时,咱们就直接进行了修改,便利咱们后续检查
temp_weather = pd.DataFrame([], columns=['时刻', '温度', 'od23', '风向', '风力', '降水量', '相对湿度', '空气质量'],
index=[x+1 for x in range(len(text)-1)])
# 写入数据
for i in range(len(text)):
# 为什么需求越过这个呢?这是因为od2中贮存的其实有25组数据,即25个小时的数据,
# 即会有一个是两天的同一个时辰的数据,后边画图会有些费事,这儿为了避归这一费事,
# 且第二天同一时辰的数据对后边也没有太多的影响,就选择越过这一问题了
if i == 0:
continue
# 写入时刻,没有进行类型改变,因而时刻是字符串类型,
# 而这儿的横作为有23开端是因为其字典的次序自身便是反过来的
temp_weather.iloc[23-i, 0] = text[i]['od21']
# 写入温度,转化成float类型
temp_weather.iloc[23-i, 1] = float(text[i]['od22'])
# od23不清楚指的是什么内容,但对本爬虫的要求无影响,故不做处理,之后会进行删去处理
temp_weather.iloc[23-i, 2] = text[i]['od23']
# 写入风向,字符串类型
temp_weather.iloc[23-i, 3] = text[i]['od24']
# 写入风力,因为不做要求,因而不做任何处理,后续会进行删去处理
temp_weather.iloc[23-i, 4] = text[i]['od25']
# 写入降水量,转化成浮点型
temp_weather.iloc[23-i, 5] = float(text[i]['od26'])
# 写入相对湿度,转化成浮点型
temp_weather.iloc[23-i, 6] = float(text[i]['od27'])
# 写入空气质量,因为最终一处呈现缺失(空值),现在还转化不了浮点型,后边会进行处理
temp_weather.iloc[23-i, 7] = text[i]['od28']
# 得到数表后,将od23和风向风力的数据进行删去
temp_weather.drop(labels=['od23', '风向', '风力'], axis=1, inplace=True)
# 将那个字符串空值赋予缺失值处理
temp_weather.iloc[23, 4] = np.nan
# 将空气质量的类型由字符串转化成浮点型
temp_weather['空气质量'] = temp_weather['空气质量'].astype('float')
# 然后将刚刚那个缺失值进行处理,这儿运用的是前填充,使其与上一个小时的AQI值坚持一致
temp_weather.fillna(method='ffill', inplace=True)
# 开端制作图画,顺次为温度、降水量、相对湿度、空气质量AOI的改变示意图
national_w.printing(temp_weather)
弥补:
爬虫偶尔来爬一爬也很不错,尽管我现在都还是有点难了解怎么调查详细是上面类型的数据。但在这儿,我学到的有关于json字符串怎么进行处理,怎么灵活地运用多个库进行解析,也包括了一个关于图片中文本阐明的弥补有:
plt.text:添加文本,能够自在的设置文本呈现的方位,参数s的作用是给咱们输入自己自定义的字符串进行显现。同时还有一个bbox参数,使咱们能够设置一个文本框,给文本大致划个规模且夺目。与此同时,咱们能够在画柱形图的时分选用text办法来对每个柱形上面添加其值的巨细的文本,使图片可观性、易读性更好
plt.text(x=0,#文本x轴坐标
y=0, #文本y轴坐标
s='basic unility of text', #文本内容
# 顺次设置的是字体巨细,颜色和字体种类
fontdict=dict(fontsize=12, color='r',family='monospace',),#字体特点字典
#添加文字布景色
bbox={'facecolor': 'red', #填充色
'edgecolor':'y',#外框色
'alpha': 0.5, #框透明度,值越大越不透明
'pad': 0.8,#本文与框周围间隔
'boxstyle':'sawtooth'
}
)
plt.annotate:也是添置文本,与text不同的是,咱们能够加入一个参数arrowprops使其能够显现一个箭头指向所需解说的点,完成了点与文本别离,但个人感觉有点不太漂亮啦
plt.annotate('basic unility of annotate',
xy=(x, y),#箭头结尾方位
xytext=(x1, y1),#文本开始方位
#箭头特点设置
arrowprops=dict(facecolor='red', # 箭头颜色
shrink=1,#箭头的收缩比
alpha=0.5,#透明度
width=7,#箭身宽
headwidth=40,#箭头宽
hatch='--',#填充形状
frac=0.8,#身与头比
#其它参阅matplotlib.patches.Polygon中任何参数
),
)
最终成果展示:
南宁市天气状况: 柳州市天气状况: