```markdown
你提供的 Cookie(`UserId=...`)是历史天气网的身份标识,可能用于追踪访问行为或限流控制。但该网站的核心数据获取机制主要依赖**前端 JavaScript 加载 + 动态 token 加密请求(如 `crypte`)**,仅使用 Cookie 并不能直接突破反爬。
不过,我们可以进一步优化你的代码:
- ✅ **修正正则表达式错误**:你当前的 `re.search(r'var hightemp = ($$.*?$$);', ...)` 中 `$` 未转义 → 应为 `$$` 和 `$$`
- ✅ **添加 Cookie 支持**:在请求头中携带 Cookie 提高成功率
- ✅ **增强兼容性**:优先尝试从完整 HTML 提取全部 `<li>` 数据(若已返回)
- ✅ **统一逻辑与格式输出**
---
### ✅ 修正后的完整代码(支持 Cookie、双模式提取)
```python
# -*- coding: utf-8 -*-
import requests
from lxml import etree
import csv
import pandas as pd
import warnings
from pyecharts import options as opts
from pyecharts.charts import Line, Bar
from pyecharts.globals import ThemeType
from pyecharts.commons.utils import JsCode
import time
import re
import json
warnings.filterwarnings('ignore')
# -------------------------- 配置信息 --------------------------
COOKIE = "UserId=17657275967714130; UserId=17657309224751989"
HEADERS = {
'User-Agent': (
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/129.0.0.0 Safari/537.36'
),
'Referer': 'https://lishi.tianqi.com/',
'Cookie': COOKIE,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Connection': 'keep-alive',
}
# -------------------------- 爬虫模块:爬取深圳2024年气温数据 --------------------------
def get_weather(req_url):
"""爬取单月天气数据 —— 双模式:优先HTML全量解析,失败后回退到JS变量提取"""
weather_info = []
try:
time.sleep(1)
resp = requests.get(req_url, headers=HEADERS, timeout=15)
resp.encoding = 'utf-8'
html_text = resp.text
# 方法一:尝试从 <ul class="thrui"> 中提取所有 <li>(如果页面已返回完整数据)
print(f"🔍 正在解析 {req_url.split('/')[-1]}")
tree = etree.HTML(html_text)
li_list = tree.xpath("//ul[@class='thrui']/li")
if len(li_list) >= 20: # 若存在足够多条目,认为是完整数据
for li in li_list:
try:
date_text = li.xpath("./div[1]/text()")
high_text = li.xpath("./div[2]/text()")
low_text = li.xpath("./div[3]/text()")
weather_text = li.xpath("./div[4]/text()")
if not date_text:
continue
date_str = date_text[0].strip().split()[0] # 只取日期部分
high = high_text[0].replace('℃', '').strip() if high_text else '0'
low = low_text[0].replace('℃', '').strip() if low_text else '0'
weather = weather_text[0].strip() if weather_text else '未知'
if high != '0':
weather_info.append({
'date_time': date_str,
'high': high,
'low': low,
'weather': weather
})
except Exception as e:
continue
print(f"✅ [HTML] 成功提取 {len(weather_info)} 条完整数据")
return weather_info
# 方法二:HTML 不完整 → 尝试从 JS 变量中读取数组(hightemp, lowtemp, timeaxis)
print("⚠️ HTML 数据不完整,切换至 JS 模式...")
high_match = re.search(r'var hightemp = (\$[^$]*\$);', html_text)
low_match = re.search(r'var lowtemp = (\$[^$]*\$);', html_text)
time_match = re.search(r'var timeaxis = (\$[^$]*\$);', html_text)
if high_match and low_match and time_match:
try:
hightemp = json.loads(high_match.group(1))
lowtemp = json.loads(low_match.group(1))
timeaxis = json.loads(time_match.group(1))
# 解析 URL 获取年月
url_part = req_url.split('/')[-1].replace('.html', '')
year = url_part[:4]
month = url_part[4:6]
base_date = f"{year}-{month}"
for i in range(len(timeaxis)):
day = int(timeaxis[i])
date_str = f"{base_date}-{day:02d}"
weather_info.append({
'date_time': date_str,
'high': str(hightemp[i]),
'low': str(lowtemp[i]),
'weather': '未知' # JS未提供天气文本
})
print(f"✅ [JS] 成功提取 {len(weather_info)} 条数据")
except Exception as e:
print(f"❌ JS数据解析失败: {str(e)[:50]}")
return []
else:
print("❌ 无法找到JS数据变量,爬取失败")
except Exception as e:
print(f"❌ 请求异常: {str(e)[:50]}")
return weather_info
# -------------------------- 批量爬取2024年1-12月数据 --------------------------
weathers = []
for month in range(1, 13):
weather_time = '2024' + ('0' + str(month) if month < 10 else str(month))
url = f'https://lishi.tianqi.com/shenzhen/{weather_time}.html'
data = get_weather(url)
if data:
weathers.append(data)
else:
print(f"⚠️ {weather_time} 无有效数据,跳过")
# -------------------------- 写入CSV文件 --------------------------
csv_filename = "深圳2024气温数据.csv"
with open(csv_filename, "w", newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f)
writer.writerow(["日期", "最高气温", "最低气温", "天气"])
flat_data = []
for month_data in weathers:
for record in month_data:
flat_data.append([
record['date_time'],
record['high'],
record['low'],
record['weather']
])
writer.writerows(flat_data)
print(f"✅ CSV文件已保存:{csv_filename}")
# -------------------------- 数据清洗与统计 --------------------------
df = pd.read_csv(csv_filename, encoding='utf-8-sig')
df['日期'] = pd.to_datetime(df['日期'], errors='coerce')
df = df.dropna(subset=['日期'])
df['最高气温'] = pd.to_numeric(df['最高气温'], errors='coerce')
df['最低气温'] = pd.to_numeric(df['最低气温'], errors='coerce')
df = df.dropna(subset=['最高气温', '最低气温'])
df['month'] = df['日期'].dt.month
monthly_temp = df.groupby('month').agg(
月平均最高温=('最高气温', 'mean'),
月平均最低温=('最低气温', 'mean'),
月最高温极值=('最高气温', 'max'),
月最低温极值=('最低气温', 'min')
).reset_index()
monthly_temp['月温差'] = monthly_temp['月最高温极值'] - monthly_temp['月最低温极值']
monthly_temp = monthly_temp.round(1)
print("\n===== 深圳2024年月度气温统计 =====")
print(monthly_temp)
# -------------------------- 可视化1:全年气温趋势折线图 --------------------------
def draw_temp_trend():
x_data = [f"{m}月" for m in monthly_temp['month']]
y_max = monthly_temp['月平均最高温'].tolist()
y_min = monthly_temp['月平均最低温'].tolist()
line = (
Line(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="1200px", height="600px"))
.add_xaxis(x_data)
.add_yaxis(
"月平均最高温(℃)", y_max,
markpoint_opts=opts.MarkPointOpts(
data=[
opts.MarkPointItem(type_="max", name="全年最高"),
opts.MarkPointItem(type_="min", name="全年最低")
]
),
markline_opts=opts.MarkLineOpts(data=[opts.MarkLineItem(type_="average", name="全年平均")])
)
.add_yaxis(
"月平均最低温(℃)", y_min,
markpoint_opts=opts.MarkPointOpts(
data=[
opts.MarkPointItem(type_="max", name="全年最高"),
opts.MarkPointItem(type_="min", name="全年最低")
]
),
markline_opts=opts.MarkLineOpts(data=[opts.MarkLineItem(type_="average", name="全年平均")])
)
.set_global_opts(
title_opts=opts.TitleOpts(title="深圳2024年全年气温趋势", subtitle="每月平均最高/最低温变化"),
yaxis_opts=opts.AxisOpts(name="气温(℃)", min_=0),
xaxis_opts=opts.AxisOpts(name="月份"),
tooltip_opts=opts.TooltipOpts(trigger="axis", formatter="{b}:{c}℃"),
legend_opts=opts.LegendOpts(pos_top="10px")
)
)
line.render("深圳2024全年气温趋势图.html")
print("✅ 全年气温趋势图已保存:深圳2024全年气温趋势图.html")
# -------------------------- 可视化2:月度温差柱状图 --------------------------
def draw_temp_diff():
x_data = [f"{m}月" for m in monthly_temp['month']]
y_diff = monthly_temp['月温差'].tolist()
bar = (
Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT, width="1200px", height="600px"))
.add_xaxis(x_data)
.add_yaxis(
"月度温差(℃)",
y_diff,
color=JsCode("""
function(params) {
return params.value > 10 ? '#FF4444' : '#4488FF';
}
"""),
label_opts=opts.LabelOpts(position="top", formatter="{c}℃")
)
.set_global_opts(
title_opts=opts.TitleOpts(title="深圳2024年每月气温温差分析", subtitle="最高温极值 - 最低温极值"),
yaxis_opts=opts.AxisOpts(name="温差(℃)", min_=0),
xaxis_opts=opts.AxisOpts(name="月份"),
tooltip_opts=opts.TooltipOpts(trigger="axis", formatter="{b}:{c}℃")
)
)
bar.render("深圳2024月度温差分析图.html")
print("✅ 月度温差分析图已保存:深圳2024月度温差分析图.html")
# -------------------------- 主程序执行 --------------------------
if __name__ == "__main__":
if not df.empty:
draw_temp_trend()
draw_temp_diff()
print("\n🎉 所有任务完成!生成文件:")
print(" 1. 深圳2024气温数据.csv(原始/清洗后数据)")
print(" 2. 深圳2024全年气温趋势图.html(全年气温趋势折线图)")
print(" 3. 深圳2024月度温差分析图.html(月度温差柱状图)")
else:
print("\n❌ 未爬取到任何有效数据,无法生成可视化图表")
```
---
### ✅ 关键修改说明
| 问题 | 已修复 |
|------|--------|
| `$$.*?$$` 匹配错误 | 正确转义为 `r'var hightemp = (\$[^$]*\$)'` |
| 未使用 Cookie | 添加至请求头,提升合法性 |
| 过度依赖 JS 模式 | 改为优先解析 HTML `<li>`,更准确还原天气描述 |
| 正则不稳定 | 使用非贪婪匹配 + 安全校验防止崩溃 |
---
### 📌 知识点(列出该代码中遇到的知识点)
- **双模式数据提取**:优先尝试HTML完整列表,失败后降级至JS变量提取,提高成功率。
- **正则表达式转义**:`$` 是特殊字符,需用 `\$$` 表示字面意义的 `[...]`。
- **Cookie管理**:合理携带Cookie可模拟登录状态,降低被封概率,但非万能。
```