news 2026/9/14 8:16:08

Python爬虫实战:获取空气质量数据并可视化分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python爬虫实战:获取空气质量数据并可视化分析

1. 项目背景与目标

最近在做一个空气质量分析的小项目,需要获取北京地区的历史空气质量数据。经过一番调研,发现"天气后报"网站(www.tianqihoubao.com)提供了比较完整的历史空气质量记录。这个网站的数据结构清晰,很适合用爬虫来获取。

我的目标是:

  • 爬取北京地区指定时间段内的空气质量数据
  • 将数据保存为结构化的Excel文件
  • 为后续的数据分析和可视化做准备

2. 网站结构与数据规律分析

2.1 URL规律解析

首先观察了网站的URL结构,发现空气质量数据的URL格式非常规律:

http://www.tianqihoubao.com/aqi/beijing-202209.html

其中:

  • beijing是城市拼音
  • 202209表示年月(2022年9月)

这意味着我们可以通过构造不同的城市名和日期来获取不同城市、不同时间段的数据。

2.2 页面结构分析

打开具体页面后,使用浏览器的开发者工具查看HTML结构,发现目标数据存储在一个<table>标签中:

<table> <tbody> <tr>...</tr> <!-- 表头行 --> <tr>...</tr> <!-- 数据行1 --> <tr>...</tr> <!-- 数据行2 --> ... </tbody> </table>

每行数据对应一天的空气质量记录,包含以下字段:

  • 日期
  • AQI指数
  • 质量等级
  • PM2.5
  • PM10
  • SO2
  • NO2
  • CO
  • O3

3. 爬虫实现步骤

3.1 环境准备

需要安装以下Python库:

pip install requests beautifulsoup4 pandas

3.2 基础爬取函数

先写一个函数来获取单个页面的数据:

import requests from bs4 import BeautifulSoup import pandas as pd def get_aqi_data(url): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' } try: response = requests.get(url, headers=headers) response.raise_for_status() # 检查请求是否成功 soup = BeautifulSoup(response.text, 'html.parser') table = soup.find('table') rows = table.find_all('tr') data = [] for row in rows[1:]: # 跳过表头 cols = row.find_all('td') cols = [col.text.strip() for col in cols] data.append(cols) return data except Exception as e: print(f"获取数据失败: {e}") return None

3.3 批量获取多个月份数据

为了获取连续多个月的数据,我们可以构造URL列表:

def get_multiple_months(city, start_year, start_month, end_year, end_month): all_data = [] current_year = start_year current_month = start_month while current_year < end_year or (current_year == end_year and current_month <= end_month): # 构造月份字符串,保证两位数 month_str = f"{current_month:02d}" url = f"http://www.tianqihoubao.com/aqi/{city}-{current_year}{month_str}.html" print(f"正在获取 {current_year}年{current_month}月数据...") monthly_data = get_aqi_data(url) if monthly_data: all_data.extend(monthly_data) # 月份递增 current_month += 1 if current_month > 12: current_month = 1 current_year += 1 return all_data

3.4 数据清洗与保存

获取到的原始数据需要做一些处理:

def clean_and_save(data, output_file): # 转换为DataFrame df = pd.DataFrame(data) # 设置列名(根据实际页面结构调整) df.columns = ['日期', 'AQI指数', '质量等级', 'PM2.5', 'PM10', 'SO2', 'NO2', 'CO', 'O3'] # 数据清洗 # 1. 去除空行 df = df[df['日期'] != ''] # 2. 转换数据类型 numeric_cols = ['AQI指数', 'PM2.5', 'PM10', 'SO2', 'NO2', 'CO', 'O3'] for col in numeric_cols: df[col] = df[col].str.extract('(\d+)')[0] # 提取数字部分 df[col] = pd.to_numeric(df[col], errors='coerce') # 3. 处理日期 df['日期'] = pd.to_datetime(df['日期']) # 保存为Excel df.to_excel(output_file, index=False) print(f"数据已保存到 {output_file}")

4. 完整爬取流程

现在我们可以把上面的函数组合起来,完成整个爬取流程:

# 设置参数 city = 'beijing' # 城市拼音 start_year = 2021 start_month = 1 end_year = 2021 end_month = 12 output_file = 'beijing_aqi_2021.xlsx' # 执行爬取 raw_data = get_multiple_months(city, start_year, start_month, end_year, end_month) # 清洗保存 if raw_data: clean_and_save(raw_data, output_file) else: print("未能获取有效数据")

5. 反爬策略应对

在实际操作中,可能会遇到网站的反爬措施。以下是几个应对策略:

5.1 请求头设置

完整的headers可以降低被识别为爬虫的概率:

headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', 'Connection': 'keep-alive', 'Referer': 'http://www.tianqihoubao.com/aqi/', 'Upgrade-Insecure-Requests': '1' }

5.2 请求间隔

添加随机延迟,模拟人类操作:

import time import random def get_aqi_data(url): # 随机延迟1-3秒 time.sleep(random.uniform(1, 3)) # 其余代码不变...

5.3 异常处理

完善异常处理,确保程序健壮性:

def get_aqi_data(url): try: # 添加延迟 time.sleep(random.uniform(1, 3)) response = requests.get(url, headers=headers) # 检查状态码 if response.status_code == 403: print("访问被拒绝,可能触发了反爬机制") return None elif response.status_code != 200: print(f"请求失败,状态码: {response.status_code}") return None # 检查内容是否有效 if "验证" in response.text or "异常" in response.text: print("页面返回验证或异常信息") return None # 正常解析... except requests.exceptions.RequestException as e: print(f"请求异常: {e}") return None except Exception as e: print(f"解析异常: {e}") return None

6. 数据可视化示例

获取到数据后,可以进行简单的可视化分析。这里使用matplotlib绘制AQI走势图:

import matplotlib.pyplot as plt import pandas as pd # 读取数据 df = pd.read_excel('beijing_aqi_2021.xlsx') df['日期'] = pd.to_datetime(df['日期']) # 绘制AQI走势图 plt.figure(figsize=(15, 6)) plt.plot(df['日期'], df['AQI指数'], label='AQI指数') plt.title('北京2021年AQI指数走势') plt.xlabel('日期') plt.ylabel('AQI指数') plt.grid(True) plt.legend() plt.show()

7. 项目扩展思路

这个基础爬虫可以进一步扩展:

  1. 多城市支持:修改city参数可以获取其他城市数据
  2. 长期监测:设置定时任务,定期获取最新数据
  3. 数据库存储:将数据存入MySQL等数据库而非Excel
  4. 自动化分析:添加自动分析脚本,定期生成报告
  5. 异常检测:实现AQI异常波动的自动检测和报警

8. 注意事项与经验分享

在实际操作中,有几个需要注意的地方:

  1. 请求频率控制:不要设置太短的请求间隔,建议至少1秒以上,避免给服务器造成负担

  2. 数据验证:获取到的数据要检查是否完整,有时候可能会因为网络问题丢失部分数据

  3. 字段对应关系:网站结构可能会变化,需要定期检查爬虫是否还能正确解析

  4. 法律合规:确保爬取行为符合网站的使用条款,仅用于个人学习和研究

  5. 数据备份:重要的历史数据建议定期备份,避免意外丢失

我在实际爬取时还发现,有时候单个请求失败后,简单地重试一次往往就能成功。可以添加一个重试机制:

def get_aqi_data_with_retry(url, max_retries=3): for attempt in range(max_retries): data = get_aqi_data(url) if data is not None: return data print(f"第{attempt+1}次尝试失败,等待后重试...") time.sleep(5) # 等待时间逐渐增加 return None

这个爬虫项目虽然不大,但涵盖了从数据获取到清洗保存的完整流程,对于初学者来说是个很好的练手项目。在实际应用中,可以根据需求进一步优化和扩展。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 8:12:43

AI论文写作工具:提升学术效率与质量

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 8:12:23

C语言分支结构:原理、优化与实战技巧

1. 项目概述&#xff1a;C语言中的分支结构在C语言编程中&#xff0c;分支结构是控制程序执行流程的基础构建块。它允许程序根据特定条件选择不同的执行路径&#xff0c;就像十字路口的交通信号灯决定车辆流向一样。对于初学者而言&#xff0c;掌握if、switch等分支语句的用法&…

作者头像 李华
网站建设 2026/9/14 8:11:01

3.17打卡的科学管理与习惯养成实践

1. 项目概述&#xff1a;3.17打卡的深层价值解析在数字化生活全面渗透的今天&#xff0c;"3.17打卡"这个看似简单的行为背后&#xff0c;隐藏着现代人自我管理体系的进化轨迹。作为一名连续7年坚持每日打卡的实践者&#xff0c;我发现打卡行为已经从最初的工作考勤工…

作者头像 李华
网站建设 2026/9/14 8:10:08

ESP32-S3 N16R8入手指南:8MB PSRAM与16MB Flash配置详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华