qiuxiang 13a9697447 店铺收款账户账单新增「总账备注」筛选;每日账单脚本支持胜算异步分片导出
- 所有账户页签在「店铺」右侧新增「总账备注」下拉:按(平台+账单项目)翻译成账单项目集合过滤,页面/列设置/导出同口径;淘系支付宝、淘系微信同页签混多平台时按店铺名前缀细分
- 每日账单脚本:胜算超出同步导出上限(url=null+taskId)时自动从「异步导出下载管理」取回本次任务全部分片并合并成一份 CSV,导入前按页面笔数核对(不一致则跳过导入)
- 实测:京东 7 月异步导出两片 219158+235846=455004 笔 / 收入 17695094.84 / 支出 8035128.21,与已导入口径一致
2026-10-06 18:46:57 +08:00

114 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""把聚胜算导出的 xlsx 转成 UTF-8 CSV,并输出笔数/收入/支出核对数。
用法:
python xlsx-to-csv.py <输入.xlsx|输入.csv> [输出.csv]
python xlsx-to-csv.py --merge <输出.csv> <输入1> <输入2> ... # 多分片合并(表头取第一个文件)
输出最后一行固定为:csv=<路径> rows=<n> income=<x> expense=<y> net=<z>[ parts=<k>]
合并模式(多个输入)下,每个分片额外输出一行:part=<文件> rows=<n> income=<x> expense=<y>
"""
import csv
import os
import sys
def load(src):
"""读取一个 xlsx/csv 文件,返回 (header, rows)。"""
if src.lower().endswith(('.xlsx', '.xlsm')):
import openpyxl
wb = openpyxl.load_workbook(src, read_only=True, data_only=True)
ws = wb.worksheets[0]
iterator = ws.iter_rows(values_only=True)
header = ['' if c is None else str(c).strip() for c in next(iterator)]
rows = [['' if c is None else c for c in row] for row in iterator if row is not None]
wb.close()
return header, rows
with open(src, 'r', encoding='utf-8-sig', newline='') as fh:
reader = csv.reader(fh)
header = [h.strip() for h in next(reader)]
rows = [row for row in reader if row]
return header, rows
def amount_index(header, token, exact):
"""定位金额列:先精确匹配(收入/支出),再退化为包含匹配(收入金额/支出金额)。"""
if exact in header:
return header.index(exact)
for i, name in enumerate(header):
if token in name:
return i
return None
def tally(header, rows):
"""统计非空行数、收入合计、支出合计。"""
income_idx = amount_index(header, '收入', '收入')
expense_idx = amount_index(header, '支出', '支出')
n = 0
income = 0.0
expense = 0.0
for values in rows:
if not any(str(v).strip() for v in values):
continue
n += 1
if income_idx is not None and income_idx < len(values):
try:
income += float(values[income_idx] or 0)
except (TypeError, ValueError):
pass
if expense_idx is not None and expense_idx < len(values):
try:
expense += float(values[expense_idx] or 0)
except (TypeError, ValueError):
pass
return n, income, expense
def run(sources, dst):
total_rows = 0
total_income = 0.0
total_expense = 0.0
header = None
with open(dst, 'w', encoding='utf-8', newline='') as fh:
writer = csv.writer(fh)
for src in sources:
part_header, rows = load(src)
n, income, expense = tally(part_header, rows)
total_rows += n
total_income += income
total_expense += expense
if header is None:
header = part_header
writer.writerow(header)
elif part_header != header:
print('warning: 分片表头与首个文件不一致:%s' % src, file=sys.stderr)
for values in rows:
if not any(str(v).strip() for v in values):
continue
writer.writerow(values)
if len(sources) > 1:
print('part=%s rows=%d income=%.2f expense=%.2f' % (src, n, income, expense))
suffix = ' parts=%d' % len(sources) if len(sources) > 1 else ''
print('csv=%s rows=%d income=%.2f expense=%.2f net=%.2f%s' % (dst, total_rows, total_income, total_expense, total_income - total_expense, suffix))
def main():
args = sys.argv[1:]
if args and args[0] == '--merge':
if len(args) < 3:
print('用法:xlsx-to-csv.py --merge <输出.csv> <输入1> [输入2 ...]', file=sys.stderr)
return 2
run(args[2:], args[1])
return 0
if not args:
print('用法:xlsx-to-csv.py <输入.xlsx|输入.csv> [输出.csv]', file=sys.stderr)
return 2
dst = args[1] if len(args) > 1 else os.path.splitext(args[0])[0] + '.csv'
run([args[0]], dst)
return 0
if __name__ == '__main__':
sys.exit(main())