You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python对比每日CSV差异并生成周变更统计新CSV的实现方法

Python实现日更CSV变更追踪+周度更新天数统计方案

核心实现思路

  • 第一步:按日期命名规则加载所有历史CSV文件,先通过文件哈希做粗粒度校验,内容完全一致的无更新日期直接跳过,减少无效比对
  • 第二步:逐行逐单元格比对相邻有效版本,记录每个单元格(行唯一标识+列名组合)发生值变更的具体日期
  • 第三步:按自然周规则对所有变更日期做分组,统计每个单元格在对应自然周内发生值更新的累计天数
  • 第四步:将统计结果导出为汇总CSV文件

前置依赖

安装数据处理库pandas,执行命令:
pip install pandas

完整实现代码

import os
import hashlib
import pandas as pd
from datetime import datetime

# -------------------------- 可配置参数 --------------------------
CSV_DIR = "./csv_versions"  # 所有日更CSV存放的文件夹路径
FILE_NAME_PATTERN = "data_%Y%m%d.csv"  # 文件名日期格式,例:data_20220714.csv对应2022年7月14日
UNIQUE_KEY = "id"  # CSV中唯一标识一行的列名(如用户ID、商品ID),无唯一列则填None
OUTPUT_PATH = "./weekly_update_stats.csv"  # 统计结果导出路径
WEEK_FIRST_DAY = "monday"  # 周起始日,可选值monday/sunday
# ----------------------------------------------------------------

def get_file_md5(file_path):
    """计算文件MD5值,快速判断两个文件内容是否完全一致"""
    with open(file_path, 'rb') as f:
        return hashlib.md5(f.read()).hexdigest()

def load_all_csv_versions():
    """加载所有有效版本CSV,跳过内容完全重复的无更新日期"""
    version_list = []
    last_file_md5 = None
    for file_name in sorted(os.listdir(CSV_DIR)):
        try:
            file_date = datetime.strptime(file_name, FILE_NAME_PATTERN).date()
        except ValueError:
            continue  # 跳过不符合命名规则的无关文件
        file_path = os.path.join(CSV_DIR, file_name)
        current_md5 = get_file_md5(file_path)
        if current_md5 == last_file_md5:
            continue  # 和上一版本内容完全一致,无更新直接跳过
        df = pd.read_csv(file_path, dtype=str).fillna("")
        version_list.append({"date": file_date, "df": df})
        last_file_md5 = current_md5
    return version_list

def track_all_changes(version_list):
    """追踪所有单元格的变更日期"""
    if not version_list:
        return pd.DataFrame()
    change_records = {}
    # 第一个版本作为基准,所有单元格记录首次上线日期
    first_df = version_list[0]["df"]
    first_date = version_list[0]["date"]
    for _, row in first_df.iterrows():
        row_key = row[UNIQUE_KEY] if UNIQUE_KEY else row.name
        for col in first_df.columns:
            change_records[(row_key, col)] = [first_date]
    # 逐版本比对差异
    for i in range(1, len(version_list)):
        current_ver = version_list[i]
        current_date = current_ver["date"]
        current_df = current_ver["df"]
        last_df = version_list[i-1]["df"]
        # 对齐两个版本的行和列,缺失值补空
        if UNIQUE_KEY:
            merged_index = pd.concat([last_df[UNIQUE_KEY], current_df[UNIQUE_KEY]]).drop_duplicates()
            last_aligned = last_df.set_index(UNIQUE_KEY).reindex(merged_index).fillna("")
            current_aligned = current_df.set_index(UNIQUE_KEY).reindex(merged_index).fillna("")
        else:
            max_row = max(len(last_df), len(current_df))
            last_aligned = last_df.reindex(range(max_row)).fillna("")
            current_aligned = current_df.reindex(range(max_row)).fillna("")
        # 逐单元格标记差异
        diff_mask = (last_aligned != current_aligned)
        for row_key in diff_mask.index:
            for col in diff_mask.columns:
                if diff_mask.loc[row_key, col]:
                    if (row_key, col) not in change_records:
                        change_records[(row_key, col)] = []
                    change_records[(row_key, col)].append(current_date)
    return change_records

def calc_weekly_stats(change_records):
    """按自然周统计每个单元格的更新天数"""
    all_rows = set()
    all_cols = set()
    week_range_map = {}
    # 收集所有行、列、周维度信息
    for (row_key, col), date_list in change_records.items():
        all_rows.add(row_key)
        all_cols.add(col)
        for d in date_list:
            if WEEK_FIRST_DAY == "monday":
                year, week_num = d.isocalendar()[0], d.isocalendar()[1]
                week_start = d - pd.Timedelta(days=d.weekday())
            else:
                year, week_num = d.year, (d.day + d.replace(day=1).weekday()) // 7 + 1
                week_start = d - pd.Timedelta(days=(d.weekday() + 1) % 7)
            week_end = week_start + pd.Timedelta(days=6)
            week_tag = f"{year}年第{week_num:02d}周"
            week_range_map[week_tag] = (week_start, week_end)
    # 生成统计结果
    result_rows = []
    for row_key in sorted(all_rows):
        row_data = {UNIQUE_KEY if UNIQUE_KEY else "row_id": row_key}
        for col in all_cols:
            date_list = change_records.get((row_key, col), [])
            for week_tag in sorted(week_range_map.keys()):
                col_name = f"{col}_{week_tag}"
                week_start, week_end = week_range_map[week_tag]
                update_count = sum([1 for d in date_list if week_start <= d <= week_end])
                row_data[col_name] = update_count
        result_rows.append(row_data)
    return pd.DataFrame(result_rows)

if __name__ == "__main__":
    versions = load_all_csv_versions()
    records = track_all_changes(versions)
    stats_df = calc_weekly_stats(records)
    stats_df.to_csv(OUTPUT_PATH, index=False, encoding="utf-8-sig")
    print(f"统计完成,结果已导出到{OUTPUT_PATH}")

关键逻辑说明

  • 性能优化:优先通过文件MD5做粗粒度校验,内容完全相同的日期版本直接跳过逐格比对,大幅降低长周期文件的处理耗时
  • 变更计数规则:同一单元格同一自然日内无论值修改几次,只要和上一版本存在差异就只计1天更新,不会重复计数
  • 兼容结构变更:如果CSV中途新增列、新增行,代码会自动识别,首次出现的日期会被记为第一次更新
  • 周度规则可配置:支持切换周一/周日作为周起始日,符合不同场景的周统计要求

注意:如果你的CSV没有唯一标识每行的主键列,请保证所有版本CSV的行顺序固定,不要随意调整行排序,否则会导致行匹配错位,统计结果出错。

内容的提问来源于stack exchange,提问作者vidathri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 19:15:38