compare_student_headers.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. 比较两个学生 Excel 文件的表头(列名)。
  5. 用法:
  6. python compare_student_headers.py \
  7. --file-a 学校历史数据迁移/student1222.xls \
  8. --file-b 学校历史数据迁移/student1127.xls
  9. 脚本会打印:
  10. - 是否完全相同(顺序及名称)
  11. - 仅在 A 中存在的列
  12. - 仅在 B 中存在的列
  13. - 顺序不同但内容相同的列(名称相同但位置不同)
  14. """
  15. from __future__ import annotations
  16. import os
  17. import sys
  18. import argparse
  19. from typing import List
  20. import pandas as pd
  21. def read_headers(path: str) -> List[str]:
  22. if not os.path.exists(path):
  23. raise FileNotFoundError(path)
  24. ext = os.path.splitext(path)[1].lower()
  25. if ext == ".xls":
  26. df = pd.read_excel(path, nrows=0, engine="xlrd")
  27. else:
  28. # .xlsx or others
  29. df = pd.read_excel(path, nrows=0, engine="openpyxl")
  30. return [str(c).strip() for c in list(df.columns)]
  31. def main():
  32. parser = argparse.ArgumentParser()
  33. parser.add_argument("--file-a", type=str, required=True)
  34. parser.add_argument("--file-b", type=str, required=True)
  35. args = parser.parse_args()
  36. try:
  37. headers_a = read_headers(args.file_a)
  38. except Exception as e:
  39. print(f"读取 {args.file_a} 表头失败: {e}")
  40. sys.exit(2)
  41. try:
  42. headers_b = read_headers(args.file_b)
  43. except Exception as e:
  44. print(f"读取 {args.file_b} 表头失败: {e}")
  45. sys.exit(2)
  46. print(f"{args.file_a} 列数: {len(headers_a)}")
  47. print(f"{args.file_b} 列数: {len(headers_b)}")
  48. if headers_a == headers_b:
  49. print("表头完全相同(顺序及名称一致)。")
  50. return
  51. set_a = set(headers_a)
  52. set_b = set(headers_b)
  53. only_a = sorted(list(set_a - set_b))
  54. only_b = sorted(list(set_b - set_a))
  55. if not only_a and not only_b:
  56. print("列集合相同,但顺序不同。")
  57. # show first few mismatches by position
  58. diffs = []
  59. for i, (ca, cb) in enumerate(zip(headers_a, headers_b)):
  60. if ca != cb:
  61. diffs.append((i + 1, ca, cb))
  62. if diffs:
  63. print("位置不一致的前若干列(位置, A列名, B列名):")
  64. for d in diffs[:20]:
  65. print(f" {d[0]:>3}: {d[1]} | {d[2]}")
  66. else:
  67. # maybe lengths differ; list remainder
  68. if len(headers_a) != len(headers_b):
  69. print("两表长度不同,下面列出多余部分:")
  70. if len(headers_a) > len(headers_b):
  71. print("A 多余:", headers_a[len(headers_b):len(headers_b)+20])
  72. else:
  73. print("B 多余:", headers_b[len(headers_a):len(headers_a)+20])
  74. return
  75. # print differences
  76. if only_a:
  77. print(f"仅在 A 中存在的列 ({len(only_a)}):")
  78. for c in only_a[:100]:
  79. print(" -", c)
  80. if only_b:
  81. print(f"仅在 B 中存在的列 ({len(only_b)}):")
  82. for c in only_b[:100]:
  83. print(" -", c)
  84. # also list positional mismatches up to 20
  85. diffs = []
  86. for i in range(min(len(headers_a), len(headers_b))):
  87. if headers_a[i] != headers_b[i]:
  88. diffs.append((i + 1, headers_a[i], headers_b[i]))
  89. if diffs:
  90. print("位置不一致的前若干列(位置, A列名, B列名):")
  91. for d in diffs[:20]:
  92. print(f" {d[0]:>3}: {d[1]} | {d[2]}")
  93. if __name__ == "__main__":
  94. main()