电价数据获取:爬取某省电网峰谷电价表

做储能收益测算,第一步就是搞到电价数据。

很多人上来就套公式,结果算出来的收益跟实际差一大截。为什么?因为电价数据本身就有问题。我见过太多项目,方案做得漂漂亮亮,一落地发现电价表都是错的——要么是过时的,要么是格式不对。

今天我们就来解决这个最基础、也最要命的问题。

电价数据长什么样?

先看看我们要拿什么数据。以某省电网的峰谷电价为例,典型的电价表长这样:

时段 起始时间 结束时间 电价(元/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) 来处理。简单粗暴,但有效。

核心逻辑流程图

下面这张图,是我做电价数据处理时一直用的框架。你照着走,基本不会出错。

电价数据处理流程 ① 网页爬取 ② 数据清洗 ③ 格式化处理 ④ 时间序列索引构建 requests + BeautifulSoup 去空格、提取数字、统一格式 时间标准化、跨天处理 生成分钟级电价序列

避坑指南

做电价数据获取,有几个坑我踩过,你注意一下:

  • 电价更新频率:不是每年都变。有的省一年调一次,有的省好几年不动。别爬完就以为永远有效。
  • 节假日电价:很多省份节假日执行特殊电价。我做过一个项目,没考虑节假日,结果收益算多了20%。
  • 两部制电价:大工业用户还有容量电价和需量电价。峰谷套利只算电度电价,别搞混了。

📌 核心要点:

  • 爬取时注意反爬机制,加headers和延时
  • 清洗时重点处理跨天时段和合并单元格
  • 时间序列索引要精确到分钟级,才能做精细化的充放电策略

好了,电价数据拿到手,下一步就是用它来算收益了。不过那是后面的事。先把数据搞对,后面的模型才有意义。


公众号:蓝海资料掘金营,微信deep3321