电价数据获取:爬取某省电网峰谷电价表
做储能收益测算,第一步就是搞到电价数据。
很多人上来就套公式,结果算出来的收益跟实际差一大截。为什么?因为电价数据本身就有问题。我见过太多项目,方案做得漂漂亮亮,一落地发现电价表都是错的——要么是过时的,要么是格式不对。
今天我们就来解决这个最基础、也最要命的问题。
电价数据长什么样?
先看看我们要拿什么数据。以某省电网的峰谷电价为例,典型的电价表长这样:
| 时段 | 起始时间 | 结束时间 | 电价(元/kWh) | 类型 |
|---|---|---|---|---|
| 高峰 | 08:00 | 12:00 | 1.0254 | 峰 |
| 平段 | 12:00 | 17:00 | 0.6836 | 平 |
| 高峰 | 17:00 | 21:00 | 1.0254 | 峰 |
| 低谷 | 21:00 | 08:00 | 0.3418 | 谷 |
嗯,看起来挺规整的。但实际从电网官网扒下来的数据,往往是一团乱麻。有的藏在PDF里,有的嵌在表格里还带合并单元格,有的干脆就是一张图片。
爬取策略:别硬来
我个人习惯是,先看网页结构,再决定怎么爬。某省电网的电价表,通常放在「信息公开」或「电价政策」栏目下。
我建议用 requests + BeautifulSoup 的组合。为什么不用 Selenium?因为电价表大多是静态页面,没必要上浏览器驱动,太重了。
import requests
from bs4 import BeautifulSoup
import pandas as pd
url = "http://www.xxx-grid.com/electricity_price"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
resp = requests.get(url, headers=headers, timeout=10)
soup = BeautifulSoup(resp.text, "html.parser")
# 找到电价表格——这里要看实际页面的class或id
table = soup.find("table", class_="price-table")
rows = table.find_all("tr")
data = []
for row in rows[1:]: # 跳过表头
cols = row.find_all("td")
data.append([col.get_text(strip=True) for col in cols])
df = pd.DataFrame(data, columns=["时段", "起始", "结束", "电价", "类型"])
print(df.head())
⚠️ 注意: 有些电网网站会反爬。我遇到过返回200但内容是空的情况。这时候检查一下headers,加个Referer试试。实在不行,就用
time.sleep(2) 慢点爬。
数据清洗:把脏数据洗干净
爬下来的数据,十有八九是脏的。我总结了几种常见情况:
- 空格和换行符:
" 08:00 "这种,直接.strip()搞定 - 电价带单位:比如
"1.0254元/kWh",需要提取数字部分 - 时间格式不统一:有的写
"8:00",有的写"08:00" - 合并单元格残留:比如高峰时段跨两行,但只有第一行有数据
我曾经遇到过一个项目,电价表里居然混入了中文全角字符的冒号「:」,跟半角":"混在一起。排查了半天才发现。所以清洗时一定要做字符规范化。
# 清洗函数
def clean_price_data(df):
# 去除空格
df = df.applymap(lambda x: x.strip() if isinstance(x, str) else x)
# 提取电价数字
df["电价"] = df["电价"].str.extract(r"(\d+\.?\d*)").astype(float)
# 统一时间格式
df["起始"] = df["起始"].str.replace(":", ":").str.zfill(5)
df["结束"] = df["结束"].str.replace(":", ":").str.zfill(5)
# 填充合并单元格的空值
df = df.ffill()
return df
df_clean = clean_price_data(df)
print(df_clean)
时间序列索引构建
数据洗干净了,接下来要把它变成能用的时间序列。说白了,就是把「几点到几点是什么电价」映射到每一分钟或每一小时上。
为什么要做这个?因为储能充放电策略是按时间走的。你总得知道每个时刻的电价,才能决定什么时候充、什么时候放。
import pandas as pd
import numpy as np
# 假设我们要生成2024年1月1日全天的电价序列
start_date = "2024-01-01"
end_date = "2024-01-02"
# 生成每分钟的时间索引
time_index = pd.date_range(start=start_date, end=end_date, freq="1min", closed="left")
# 构建电价映射表
price_map = {}
for _, row in df_clean.iterrows():
start_time = pd.Timestamp(f"{start_date} {row['起始']}")
end_time = pd.Timestamp(f"{start_date} {row['结束']}")
if end_time <= start_time: # 跨天的情况,比如21:00到08:00
end_time += pd.Timedelta(days=1)
mask = (time_index >= start_time) & (time_index < end_time)
price_map[row["类型"]] = (mask, row["电价"])
# 生成电价序列
price_series = pd.Series(index=time_index, dtype=float)
for ptype, (mask, price) in price_map.items():
price_series[mask] = price
print(price_series.head(10))
💡 小技巧: 跨天时段(比如21:00到次日08:00)是常见的坑。我习惯用
if end_time <= start_time: end_time += timedelta(days=1) 来处理。简单粗暴,但有效。
核心逻辑流程图
下面这张图,是我做电价数据处理时一直用的框架。你照着走,基本不会出错。
避坑指南
做电价数据获取,有几个坑我踩过,你注意一下:
- 电价更新频率:不是每年都变。有的省一年调一次,有的省好几年不动。别爬完就以为永远有效。
- 节假日电价:很多省份节假日执行特殊电价。我做过一个项目,没考虑节假日,结果收益算多了20%。
- 两部制电价:大工业用户还有容量电价和需量电价。峰谷套利只算电度电价,别搞混了。
📌 核心要点:
- 爬取时注意反爬机制,加headers和延时
- 清洗时重点处理跨天时段和合并单元格
- 时间序列索引要精确到分钟级,才能做精细化的充放电策略
好了,电价数据拿到手,下一步就是用它来算收益了。不过那是后面的事。先把数据搞对,后面的模型才有意义。
公众号:蓝海资料掘金营,微信deep3321