爬虫案例-2345天气王历史天气获取
1. 项目简介
本项目的目的是利用网络爬虫技术,在2345天气王网站中,获取重庆从2011年至2023年的历史天气数据,包括每日温度、降雨量等信息。通过数据的获取与清洗,我们能够更好地研究重庆的气候变化趋势,为相关分析提供基础数据支持。
2. 进入网站并初步分析
2.1 访问网站
访问目标网站(2345天气王),找到重庆历史天气页面
2.2 页面结构分析
使用浏览器的开发者工具(通常可以通过按 F12
键打开)来分析页面结构。重点观察页面上展示历史天气数据的部分,确定其展示形式。常见的展示形式包括:
- 表格:历史天气数据可能以表格的形式展现,每行或每列对应不同日期的天气信息,如温度、降雨量等。
- 文本块:部分数据可能以段落或分散的文本块形式存在,需要通过爬虫代码解析具体标签内容。
- 图表:如果有数据可视化图表,可能需要进一步确认是否存在隐藏数据源或通过额外请求获取。
通过分析可知,历史天气数据以表格形式展示,对应HTML标签为<table class="history-table">
。
2.3 数据加载方式判断
在分析过程中,需要判断页面数据是否通过JavaScript动态加载。
(1)如果是动态加载的数据,则通常需要使用抓包工具来分析数据请求的API地址和参数。通过观察网络请求(Network 请求)面板中的XHR请求,可以获取数据来源的接口地址及其请求头信息,为后续的抓包步骤提供帮助。
(2)如果数据是静态页面加载的,则可以直接使用爬虫解析HTML来获取目标数据。
例如,选择2024年10月时,可以在控制台的网络请求中发现XHR
请求,点击查看传参内容。
3. 代码实现步骤
3.1 模拟访问和数据抓取
首先,我们使用requests
库模拟对目标网页的访问,并通过伪装HTTP请求头来模拟真实用户的访问行为,以避免触发反爬虫机制。
import requests
import random
# 模拟多个 User-Agent 以模拟不同的浏览器访问
# 这里定义了一个包含常见浏览器标识的列表,用于伪装成不同的浏览器访问网站
user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0',
# 其他 User-Agent 字符串可以继续添加以模拟更多浏览器
...
]
# 获取随机 User-Agent 头部信息的函数
# 该函数随机从 user_agents 列表中选择一个 User-Agent 字符串,并构造一个请求头部
def get_random_headers():
return {
'User-Agent': random.choice(user_agents), # 随机选择一个 User-Agent 字符串
'Accept': 'application/json, text/javascript, */*; q=0.01', # 设置请求头部的 Accept 字段,指定期望接受的响应类型
'Referer': 'https://tianqi.2345.com/wea_history/57516.htm', # 设置请求头部的 Referer 字段,伪装请求来源页面
}
3.2 URL构建与请求发送
我们构建请求URL和参数,用于指定获取的年份和月份,并通过发送HTTP请求获取响应数据。
def build_request_params(year, month):
# 构建请求的 URL 和参数
url = "https://tianqi.2345.com/Pc/GetHistory" # 目标网站的历史天气数据接口
params = {
'areaInfo[areaId]': 57516, # 重庆市的地区 ID,用于获取指定地区的数据
'areaInfo[areaType]': 2, # 地区类型,1 表示国外城市,2 表示国内城市
'date[year]': year, # 查询的年份
'date[month]': month, # 查询的月份
}
return url, params # 返回构建好的 URL 和参数
def fetch_weather_data(url, params):
# 获取天气数据
headers = get_random_headers() # 使用随机的请求头部信息以模拟不同浏览器
response = requests.get(url, headers=headers, params=params) # 发送 HTTP GET 请求
if response.status_code == 200: # 判断请求是否成功(状态码 200 表示成功)
return response.json() # 如果请求成功,则返回 JSON 格式的数据
else:
# 请求失败时,打印状态码并返回 None
print(f"请求失败,状态码: {response.status_code}")
return None
3.3 数据解析和处理
我们使用BeautifulSoup
解析HTML数据,并将其转换为pandas
的DataFrame
格式,便于数据操作和清洗。
from bs4 import BeautifulSoup # 导入 BeautifulSoup 库用于解析 HTML
import pandas as pd # 导入 Pandas 库用于数据操作
def parse_html_to_dataframe(html_string):
# 将 HTML 字符串解析为数据框
soup = BeautifulSoup(html_string, 'html.parser') # 使用 BeautifulSoup 解析 HTML 字符串
table = soup.find('table', class_='history-table') # 查找包含历史数据的表格元素
if table: # 如果找到表格
table_str = str(table) # 将表格转为字符串
df = pd.read_html(StringIO(table_str))[0] # 使用 Pandas 读取表格数据并转换为 DataFrame
return df # 返回 DataFrame
else:
print("未找到表格数据") # 如果未找到表格,输出提示信息
return None # 返回 None
3.4 数据合并与保存
在循环爬取每年的每月数据时,我们逐步将数据合并,并最终保存到CSV文件中。
def merge_dataframes(final_df, new_df):
# 合并数据框的函数
# 如果 new_df 不为 None,则将其与 final_df 合并
if new_df is not None:
return pd.concat([final_df, new_df], ignore_index=True) # 使用 pd.concat 进行合并,并重置索引
return final_df # 如果 new_df 为空,返回原来的 final_df
# 将最终的数据框保存为 CSV 文件
final_df.to_csv('weather_data.csv', index=False) # 保存数据到 'weather_data.csv' 文件,不保留索引列
3.5 爬虫优化
为了避免频繁请求被检测,我们模拟了人类访问的行为,随机延迟请求时间。同时,我们设置了多种User-Agent来防止被识别为爬虫请求。
import time # 导入 time 模块用于延时功能
def simulate_human_behavior(min_delay=0.5, max_delay=3.5):
"""
模拟人类行为的函数,主要通过延时来模仿人类浏览网页的时间间隔,以避免被目标网站识别为爬虫程序。
参数:
min_delay (float): 最小延时(单位为秒),默认为 0.5 秒
max_delay (float): 最大延时(单位为秒),默认为 3.5 秒
"""
# 在 min_delay 和 max_delay 之间随机生成一个延时,并暂停程序运行
time.sleep(random.uniform(min_delay, max_delay))
4. 完整代码
# 导入必要的库
import requests
import pandas as pd
from io import StringIO # 用于将 HTML 转换为可读的字符串流
from bs4 import BeautifulSoup # 用于解析 HTML 内容
import itertools # 用于生成年份和月份的组合
import time # 用于引入时间延迟
import random # 用于生成随机数模拟不同的请求头和延迟
# 准备多个 User-Agent 模拟不同的浏览器,防止爬虫被检测
user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Mobile/15E148 Safari/604.1'
]
# 获取随机 User-Agent 的请求头,模拟不同的浏览器请求
def get_random_headers():
return {
'User-Agent': random.choice(user_agents), # 随机选择一个 User-Agent
'Accept': 'application/json, text/javascript, */*; q=0.01', # 接受的返回数据类型
'Accept-Encoding': 'gzip, deflate, br, zstd', # 压缩编码
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', # 支持的语言
'X-Requested-With': 'XMLHttpRequest', # 表示通过 Ajax 发出的请求
'Connection': 'keep-alive', # 保持连接
'Referer': 'https://tianqi.2345.com/wea_history/57516.htm', # 来源网址,用于表明请求的来源页面
'Origin': 'https://tianqi.2345.com' # 请求来源,防止跨域请求问题
}
# 构建请求 URL 和参数,用于指定具体年份和月份的天气数据
def build_request_params(year, month):
url = "https://tianqi.2345.com/Pc/GetHistory" # 请求的基础 URL
params = {
'areaInfo[areaId]': 57516, # 地区 ID,代表重庆市
'areaInfo[areaType]': 2, # 地区类型,2 代表城市
'date[year]': year, # 请求的年份
'date[month]': month, # 请求的月份
}
return url, params # 返回 URL 和请求参数
# 发送请求并获取响应数据,检查状态码,确保请求成功
def fetch_weather_data(url, params):
headers = get_random_headers() # 获取随机请求头
response = requests.get(url, headers=headers, params=params) # 发送 GET 请求
if response.status_code == 200: # 检查是否成功响应
return response.json() # 返回响应数据(JSON 格式)
else:
print(f"请求失败,状态码: {response.status_code}") # 输出错误信息
return None # 请求失败时返回 None
# 解析 HTML 数据并将其转换为 Pandas DataFrame 以便后续数据处理
def parse_html_to_dataframe(html_string):
soup = BeautifulSoup(html_string, 'html.parser') # 使用 BeautifulSoup 解析 HTML
table = soup.find('table', class_='history-table') # 查找具有特定 class 的表格
if table: # 如果找到表格
table_str = str(table) # 将表格转换为字符串形式
table_io = StringIO(table_str) # 使用 StringIO 将其转换为可读的字符串流
df = pd.read_html(table_io)[0] # 使用 pandas 读取 HTML 表格,转换为 DataFrame
return df # 返回 DataFrame
else:
print("未找到表格数据") # 如果未找到表格,输出提示信息
return None # 返回 None
# 将新的 DataFrame 与之前的数据合并
def merge_dataframes(final_df, new_df):
if new_df is not None: # 如果新数据存在
return pd.concat([final_df, new_df], ignore_index=True) # 合并 DataFrame 并重置索引
return final_df # 如果没有新数据,则返回原始的 final_df
# 模拟人类行为,随机延迟一定时间,防止请求过于频繁
def simulate_human_behavior(min_delay=0.5, max_delay=3.5):
time.sleep(random.uniform(min_delay, max_delay)) # 随机延迟 0.5 到 3.5 秒
# 显示当前爬取进度,年份和月份的进度显示
def display_progress(year, month, current_year):
if year == current_year: # 如果当前年份与上次相同
print('\t#', end='') # 输出进度标识,不换行
else:
print(f'\n{year}年\t#', end='') # 输出年份并换行,表示新的年份开始
return year # 返回当前年份
# 主函数,负责执行天气数据的爬取任务
def main():
print('重庆市2011-2023年历史天气数据爬取开始:')
print('-' * 100) # 打印分割线,表示爬取开始
# 初始化空 DataFrame,用于存储爬取到的所有数据
final_df = pd.DataFrame()
# 定义年份和月份的范围
years = range(2011, 2024) # 从 2011 年到 2023 年
months = range(1, 13) # 1 月到 12 月
year_month_combinations = itertools.product(years, months) # 生成所有年份和月份的组合
# 输出月份表头
header = '\t' + '\t'.join([f'{x}月' for x in months]) # 生成月份表头字符串
print(header, end='') # 输出月份表头
# 跟踪当前年份,初始化为 0
current_year = 0
# 遍历所有年份和月份
for year, month in year_month_combinations:
# 构建请求的 URL 和参数
url, params = build_request_params(year, month)
# 模拟人类行为,避免频繁请求,实际使用时可以启用这行代码
simulate_human_behavior()
# 获取天气数据
response_data = fetch_weather_data(url, params) # 获取 JSON 响应
if response_data:
html_string = response_data.get('data', '') # 提取 HTML 数据
# 解析 HTML 并转换为 DataFrame
new_df = parse_html_to_dataframe(html_string)
# 合并新数据到最终 DataFrame
final_df = merge_dataframes(final_df, new_df)
# 更新并显示进度
current_year = display_progress(year, month, current_year)
# 最终输出爬取完成信息
print('\n' + '-' * 100, end='') # 打印分割线,表示爬取结束
print("\n数据爬取完成。") # 输出提示信息
return final_df # 返回最终的 DataFrame
# 调用主函数
if __name__ == "__main__":
result_df = main() # 执行爬取任务
result_df.to_csv('weather_data.csv', index=False) # 保存数据到 CSV 文件
5. 总结
本项目通过爬虫获取历史天气数据,利用数据分析工具进行清洗和合并,为重庆气候趋势分析提供了可靠的数据支持。通过优化爬虫策略和代码结构,提高了爬取效率和稳定性。
标签:2345,return,请求,df,天气,爬虫,year,table,数据 From: https://blog.csdn.net/MoRanzhi1203/article/details/143721939