前言

Python作为一种功能强大且灵活的编程语言,为我们提供了丰富的工具和库,使得从网上获取、处理和分析历史天气数据变得更加便捷和高效。接下来让我们一起探索如何利用Python爬虫技术,从网络上收集历史天气数据,并进行详尽的分析和可视化。

获取天气

我们将从天气+获取任意地区任意时间的历史天气。需要注意的是,本文分析的是一整个月的数据,所以需要选择已经结束的月份。

目标选择

首先选择好自己想要分析的地区和月份。这里我们采用两个月对比分析,所以选择了2月和3月天气作为目标网页。首先我们复制这两个月的网址,然后分析一下网页结构。

页面结构

显然我们需要获取的是每一天的数据。在这里按F12进入网页开发者工具,可以看到这一部分页面的结构。 可以看到整个表格框体包裹在一个类名为tian_threediv中,每行数据对应ul列表中的一个li。这里一定要注意第一个li是表头。除此之外还需要注意摄氏度符号一定要和网页保持一致。

获取网页

接下来我们在python程序中获取这两个月的天气页面。为了提高复用性,我们将这一部分封装为一个函数。

第一步:User-Agent设置

这里需要我们在浏览器开发者工具中获取一个UA。任意一个流量包都可以在请求头中找到,如下图位置: 将它的值复制出来保存为一个字典。

headers = {"User-Agent":
           'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0'
           }

这样就可以模拟一个浏览器请求,避免被服务器识别为爬虫或自动化程序而拒绝服务或返回不完整的数据。

第二步:使用requests库发送GET请求

直接调用requests包的get()即可发送带有设置好的headers的GET请求到指定的URL,获取网页的内容。

res = requests.get(url, headers=headers)
第三步:解析HTML内容

将获取到的网页内容解码为UTF-8格式,并使用BeautifulSoup库解析HTML内容。

html = res.content.decode('utf-8')
bs = BeautifulSoup(html, 'html.parser')
第四步:提取数据

根据前文的分析,我们需要按照以下步骤搜索元素:

  1. 类名为tian_threediv

  2. ul列表。

  3. li标签。

lis = bs.body.find('div', {'class': 'tian_three'}).find('ul').find_all('li')
第五步:处理每个li标签内的数据

由于每天的数据由div标签划分为不同的格子,所以需要继续搜索div中的内容。查找所有的div标签,然后将每个div标签内的文本提取出来,去除文本中的符号和空格,并存储到列表temp中,最后将temp添加到列表w中,返回处理后的结果w

w = []
for li in lis:
    temp = []
    divs = li.find_all('div')
    for d in divs:
        temps = d.string.replace("℃", "").replace(" ", "")
        temp.append(temps)
    w.append(temp)

返回处理后的结果w,其中包含了从HTML页面中提取的数据。

数据清洗

将两个页面放入上面的函数,即可获取到每日的天气数据。但这些数据并不能直接使用,它还存在以下几个问题:

  1. 日期中星期和日期混在一起。

  2. 各项数据的类型是字符串。

  3. 缺少一些统计量,如平均值等。 接下来我们将对这几个问题进行逐一处理。同样地,我们将这一过程封装为一个函数。

第一步:创建DataFrame

将传入的二维列表w转换为DataFrame对象df,指定列名为['日期星期', '最高气温', '最低气温', '天气', '风向']

df = pd.DataFrame(w, columns=['日期星期', '最高气温', '最低气温', '天气', '风向'])

第二步:去重处理

去除DataFrame中的重复行。

df = df.drop_duplicates()

第三步:日期处理

这一步我们用流处理的风格。首先使用assign()创建新列'日期',从'日期星期'列中取前10个字符(即日期部分);再次使用assign()创建新列'星期',从'日期星期'列中取第10到12个字符(即星期部分);最后删除原始的'日期星期'列。

df = df.assign(日期=df.日期星期.str.slice(0, 10)).assign(星期=df.日期星期.str.slice(10, 13)).drop('日期星期', axis=1)

第四步:风力处理

这里我们首先使用assign方法创建新列'风力',然后通过map()'风向'列中的每个元素应用lambda表达式,提取其中的数字部分,然后使用join()filter()来提取风力信息中的数字部分。

df = df.assign(风力=df.风向.map(lambda x: ''.join(list(filter(str.isdigit, x)))))

第五步:计算平均气温

由于每天气温有最高和最低两个值,所以需要计算平均气温来实现降维。直接计算每天的平均气温,将结果存储在新列'平均气温'中。

df['平均气温'] = (df.最高气温 + df.最低气温) / 2

数据类型转换

'最高气温''最低气温'列的数据类型转换为整数类型,将'风力'列的数据类型转换为整数类型,将'平均气温'列的数据类型转换为浮点数类型。最后返回经过所有处理步骤后的df

df.最高气温 = df.最高气温.astype(int)
df.最低气温 = df.最低气温.astype(int)
df.风力 = df.风力.astype(int)
df.平均气温 = df.平均气温.astype(float)

数据可视化

经过上面的步骤,我们就获得了一份干净的数据,可以直接进行数据分析与可视化。

天气走势

第一步:调整画布大小

创建一个图像对象,设置图像的大小为宽度12英寸,高度6英寸。

plt.figure(figsize=(12, 6))
第二步:准备横坐标

创建一个序列x,其长度与df的行数相同,用于作为折线图的横坐标。

x = range(0, len(df1))
第三部:绘制折线图

使用plt.plot()分别绘制最高气温和最低气温随时间变化的折线图。x是横坐标,df1['最高气温']df1['最低气温']是纵坐标数据,label参数设置图例标签。

plt.plot(x, df1['最高气温'], label='最高气温')
plt.plot(x, df1['最低气温'], label='最低气温')
第四步:设置横坐标刻度和标签

设置横坐标的刻度位置为x,刻度标签为df1['日期']中的日期数据,并将标签文字旋转30度,以避免文字重叠。

plt.xticks(x, df1['日期'], rotation=30)
第五步:添加图例

自动添加图例,图例显示每条折线对应的标签,即'最高气温''最低气温'

plt.legend()

结果如下:

宜居天数

宜居天气定义为气温在18~25摄氏度,风力在五级以下。

第一步:统计符合条件的行数

统计DataFrame中满足条件的行数。条件是平均气温在18到25之间,并且风力小于5。这里使用query()来筛选满足条件的行,然后通过len()获取符合条件的行数。

c1 = len(df1.query("(平均气温>=18) and (平均气温<=25) and (风力<5)"))
c2 = len(df2.query("(平均气温>=18) and (平均气温<=25) and (风力<5)"))
第二步:绘制柱状图

使用plt.bar()绘制柱状图。柱的横坐标为['2月', '3月'],分别代表两个月份。柱的高度数据为[c1, c2],即满足条件的行数。width=0.5设置柱的宽度为0.5个单位。

plt.bar(['2月', '3月'], [c1, c2], width=0.5)
第三步:设置轴标签

使用plt.xlabel('月份')设置横轴标签为 "月份";plt.ylabel('天数')设置纵轴标签为 "天数"。

plt.xlabel('月份')
plt.ylabel('天数')

结果如下:

各类天气对比

这里由于每个月的天数不同,天气种类也不同,所以需要先互相填充缺失的类型值为0。

第一步:数据分组和统计

首先对DataFrame按照'天气'列进行分组;之后将分组后的结果转换为字典形式;然后遍历字典,统计每个天气类型对应的行数,存储在res字典中。

res1 = {}
for k, v in dict([x for x in df1.groupby('天气')]).items():
    res1[k] = len(v)
第二步:处理缺失的天气类型

第一个循环检查res2中的每个天气类型,如果该类型在res1中不存在(即res1.get(k)返回None),则在res1中添加该天气类型并将其值设为0;第二个循环检查res1中的每个天气类型,如果该类型在res2中不存在,则在res2中添加该天气类型并将其值设为0。 这两个循环确保res1res2中的天气类型完全一致,避免柱状图中出现不匹配的情况。

for k in res2:
    if res1.get(k) is None:
        res1[k] = 0
for k in res1:
    if res2.get(k) is None:
        res2[k] = 0
第三步:绘制柱状图

首先设置绘图区域大小为 12x6;然后分别绘制两个柱状图,使用了偏移量 0.2 来使得两个月份的柱子错开显示;之后设置横轴刻度为天气类型,位置为 x;之后设置横轴标签为 "天气",设置纵轴标签为 "天数";最后添加图例,标明两组数据分别代表 "2月" 和 "3月"。

x = range(0, len(res1))
plt.figure(figsize=(12, 6))
plt.bar([i - 0.2 for i in x], res1.values(), width=0.4, label='2月')
plt.bar([i + 0.2 for i in x], res2.values(), width=0.4, label='3月')
plt.xticks(x, res1.keys())
plt.xlabel('天气')
plt.ylabel('天数')
plt.legend()

结果如下:

完整代码如下:

import requests
from bs4 import BeautifulSoup
import pandas as pd
import matplotlib.pyplot as plt
​
url1 = 'http://lishi.tianqi.com/shiyan/202302.html'
url2 = 'http://lishi.tianqi.com/shiyan/202303.html'
​
​
def get_data(url):
    headers = {"User-Agent":
               'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0'
               }
    res = requests.get(url, headers=headers)
    html = res.content.decode('utf-8')
​
    bs = BeautifulSoup(html, 'html.parser')
    lis = bs.body.find('div', {'class': 'tian_three'}
                       ).find('ul').find_all('li')
    w = []
    for li in lis:
        temp = []
        divs = li.find_all('div')
        for d in divs:
            temps = d.string.replace("℃", "").replace(" ", "")
            temp.append(temps)
        w.append(temp)
    return w
​
​
def data_proc(w):
    df = pd.DataFrame(w, columns=['日期星期', '最高气温', '最低气温', '天气', '风向'])
    df = df.drop_duplicates()
    df = df.assign(日期=df.日期星期.str.slice(0, 10)).assign(
        星期=df.日期星期.str.slice(10, 13)).drop('日期星期', axis=1)
    df.最高气温 = df.最高气温.astype(int)
    df.最低气温 = df.最低气温.astype(int)
    df = df.assign(风力=df.风向.map(
        lambda x: ''.join(list(filter(str.isdigit, x)))))
    df['平均气温'] = (df.最高气温 + df.最低气温) / 2
    df.风力 = df.风力.astype(int)
    df.平均气温 = df.平均气温.astype(float)
    return df
​
​
df1 = data_proc(get_data(url1))
df2 = data_proc(get_data(url2))
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.figure(figsize=(12, 6))
x = range(0, len(df1))
plt.plot(x, df1['最高气温'], label='最高气温')
plt.plot(x, df1['最低气温'], label='最低气温')
plt.xticks(x, df1['日期'], rotation=30)
plt.legend()
​
plt.show()
​
c1 = len(df1.query("(平均气温>=18) and (平均气温<=25) and (风力<5)"))
c2 = len(df2.query("(平均气温>=18) and (平均气温<=25) and (风力<5)"))
plt.bar(['2月', '3月'], [c1, c2], width=0.5)
plt.xlabel('月份')
plt.ylabel('天数')
plt.show()
​
​
res1 = {}
for k, v in dict([x for x in df1.groupby('天气')]).items():
    res1[k] = len(v)
res2 = {}
for k, v in dict([x for x in df2.groupby('天气')]).items():
    res2[k] = len(v)
for k in res2:
    if res1.get(k) is None:
        res1[k] = 0
for k in res1:
    if res2.get(k) is None:
        res2[k] = 0
x = range(0, len(res1))
plt.figure(figsize=(12, 6))
plt.bar([i - 0.2 for i in x], res1.values(), width=0.4, label='2月')
plt.bar([i + 0.2 for i in x], res2.values(), width=0.4, label='3月')
plt.xticks(x, res1.keys())
plt.xlabel('天气')
plt.ylabel('天数')
plt.legend()
plt.show()
​

总结

在本文中,我们一起探索了Python爬虫技术获取和分析历史天气数据的方法。首先,要通过分析网页结构和选择合适的解析器,有效地提取了所需的信息。随后,利用Python中的数据处理和清洗技术,对抓取到的数据进行预处理,使其适合后续的分析工作。这里使用了Pandas库来进行数据结构化处理,并通过示例展示了如何计算和分析各种天气指标。最后,利用Matplotlib库进行数据可视化,通过绘制折线图、柱状图等图表展示了历史天气数据的变化趋势和统计结果。这些可视化工具不仅帮助我们更直观地理解数据,还能够有效地传达数据分析的结论和见解。

Logo

为武汉地区的开发者提供学习、交流和合作的平台。社区聚集了众多技术爱好者和专业人士,涵盖了多个领域,包括人工智能、大数据、云计算、区块链等。社区定期举办技术分享、培训和活动,为开发者提供更多的学习和交流机会。

更多推荐