如何将双空格分隔的分块日历数据转换为pandas日期时间数据框
分块日历文本转结构化长表实现方案
核心解析思路
常规印刷版分块日历的文本结构一般符合以下特征,也是我们解析的适配基础:
- 按月份独立分块,每个块的开头是月份标识行(比如
2021年1月这类文本) - 块内第二行是固定7列的星期表头,对应周一到周日
- 块内后续行是每周的日期单元格,每个有效单元格的内容格式是「日期数字 + 对应时间值y」(比如
1 17:26),非当月的占位单元格无有效y值 - 所有日期、数值没有连续排列,完全分散在不同块、不同行列位置,不适合直接用固定列结构的CSV读取方式处理
可落地实现代码
1. 基础依赖导入与全量文本读取
跳过固定列结构的CSV读取,直接加载所有非空文本行,避免列错位问题:
import pandas as pd import re # 读取本地文本文件的所有非空行,去除前后冗余空白 with open("2021.txt", "r", encoding="utf-8") as f: raw_lines = [line.strip() for line in f.readlines() if line.strip()]
2. 逐行解析块结构,提取有效记录
遍历所有文本行,匹配月份边界、拆分单元格内容、过滤无效占位值:
extracted_records = [] current_month = None # 匹配月份标识的正则:识别类似"2021年X月"的行 month_matcher = re.compile(r"2021年(\d{1,2})月") # 匹配有效单元格的正则:识别"日期 时间"格式的单元格内容 cell_matcher = re.compile(r"^\s*(\d{1,2})\s+(\d{1,2}:\d{2})\s*$") for line in raw_lines: # 检测当前行是否为月份标题,更新当前解析的月份 month_match = month_matcher.search(line) if month_match: current_month = int(month_match.group(1)) continue # 跳过星期表头行 if re.search(r"[一二三四五六日]\s+[一二三四五六日]", line): continue # 按2个以上连续空白拆分当前行的所有单元格,兼容不同缩进的排版 line_cells = re.split(r"\s{2,}", line) for cell in line_cells: cell_match = cell_matcher.search(cell) if not cell_match: continue # 提取单元格内的日、y值,做基础合法性校验 day = int(cell_match.group(1)) y_value = cell_match.group(2) if 1 <= day <= 31 and current_month is not None: extracted_records.append({ "month": current_month, "day": day, "y": y_value })
3. 生成标准日期列,整理最终长表
使用pandas.to_datetime拼接生成符合要求的标准日期格式,整理为目标两列结构:
# 转成临时数据框 temp_df = pd.DataFrame(extracted_records) # 拼接完整日期字符串,转成标准datetime格式 temp_df["Date"] = pd.to_datetime( "2021-" + temp_df["month"].astype(str) + "-" + temp_df["day"].astype(str), format="%Y-%m-%d" ) # 按日期排序,取目标列得到最终结果 result_df = temp_df[["Date", "y"]].sort_values("Date").reset_index(drop=True)
避坑提示
- 直接用
pd.read_csv(sep=" ")的方案容错性很差:因为日历排版的缩进不固定,相邻单元格的分隔空格数经常变化,很容易出现值串行、列错位的问题,逐块解析的方式能适配绝大多数排版差异 - 一定要做单元格有效性校验:印刷日历通常会在月初、月末的单元格打印相邻月份的日期占位,这类单元格没有对应y值,通过正则匹配可以自动过滤,不会混进最终结果
- 日期拆分逻辑不需要额外处理补零:
pd.to_datetime会自动识别个位数的月、日格式,不用手动给月、日补0
内容的提问来源于stack exchange,提问作者Numbermind
相关产品推荐
相关产品推荐

