Python SAX Parser解析多XML存CSV仅保留最后文件数据问题
问题原因及解决办法
核心问题1:CSV写入时的覆盖
你每次调用df.to_csv(fp2, index=False)都是用默认的覆盖模式(mode='w')写入文件,所以每解析一个XML,就会把之前的CSV内容清空,最后只留下最后一个文件的数据。
核心问题2:SAX解析器状态未重置
你用同一个parser实例解析所有XML文件,之前解析的属性值(比如self.rc、self.dd)不会自动清空,可能导致后面的文件数据混入之前的旧值。
核心问题3:characters方法的误用
SAX的characters()方法会被多次触发(比如元素内容有换行、被其他节点分割时),直接在这里构建行数据并写入,很可能收集到不完整的内容,而且会重复写入数据。
修正后的代码示例
第一步:重构SAX Handler类
把数据收集逻辑移到endElement(),并在每次解析新文件前重置Handler的状态:
import os import pandas as pd from xml.sax import make_parser, handler class CaseHandler(handler.ContentHandler): def __init__(self): # 初始化所有字段 self.reset_state() # 存储所有案例数据的列表 self.all_rows = [] self.current_tag = "" def reset_state(self): # 重置所有属性,避免旧数据干扰 self.rc = "" self.dd = "" self.can = "" self.fn = "" self.loc = "" self.cn = "" self.ca = "" self.j = "" self.cl = "" self.cc = "" self.pc = "" self.cd = "" self.rn = "" self.current_tag = "" def startElement(self, name, attrs): # 记录当前正在解析的标签 self.current_tag = name def characters(self, content): # 只在对应标签下收集内容,注意去空格 content = content.strip() if not content: return if self.current_tag == "ReporterCite": self.rc += content elif self.current_tag == "DecisionDate": self.dd += content elif self.current_tag == "CaseName": self.can += content # 其他字段依次类推,对应你的XML标签名 def endElement(self, name): # 当遇到整个案例的结束标签时(比如<Case>标签结束),收集数据 if name == "Case": # 这里替换成你XML中每个案例的根标签 row = { "ReporterCite": self.rc, "DecisionDate": self.dd, "CaseName": self.can, "FileNum": self.fn, "CourtLocation": self.loc, "CourtName": self.cn, "CourtAbbrv": self.ca, "Judge": self.j, "CaseLength": self.cl, "CourtCite": self.cc, "ParallelCite": self.pc, "CitedCount": self.cd, "UCN": self.rn } self.all_rows.append(row) # 重置状态,准备解析下一个案例(如果一个XML中有多个案例) self.reset_state() def get_all_data(self): return self.all_rows
第二步:遍历文件并解析
每次解析新文件时,重置Handler状态,最后一次性写入CSV:
fp1 = "你的文件夹路径" fp2 = "输出的CSV路径" # 创建Handler实例 handler = CaseHandler() parser = make_parser() parser.setContentHandler(handler) # 遍历所有XML文件 for dirpath, _, files in os.walk(fp1): for filename in files: fname = os.path.join(dirpath, filename) if fname.endswith('.xml'): print(f"正在解析: {fname}") # 重置Handler状态,避免旧文件数据干扰 handler.reset_state() parser.parse(fname) # 所有文件解析完成后,一次性写入CSV cols = ["ReporterCite","DecisionDate","CaseName","FileNum","CourtLocation","CourtName","CourtAbbrv","Judge","CaseLength","CourtCite","ParallelCite","CitedCount","UCN"] df = pd.DataFrame(handler.get_all_data(), columns=cols) df.to_csv(fp2, index=False)
替代方案:追加写入CSV
如果数据量很大,不想全部存在内存里,可以每次解析完一个文件就追加写入CSV,但要注意只在第一次写入表头:
fp1 = "你的文件夹路径" fp2 = "输出的CSV路径" cols = ["ReporterCite","DecisionDate","CaseName","FileNum","CourtLocation","CourtName","CourtAbbrv","Judge","CaseLength","CourtCite","ParallelCite","CitedCount","UCN"] # 判断文件是否存在,决定是否写入表头 file_exists = os.path.isfile(fp2) for dirpath, _, files in os.walk(fp1): for filename in files: fname = os.path.join(dirpath, filename) if fname.endswith('.xml'): print(f"正在解析: {fname}") # 每次解析新文件都创建新的Handler实例 handler = CaseHandler() parser = make_parser() parser.setContentHandler(handler) parser.parse(fname) # 追加写入CSV df = pd.DataFrame(handler.get_all_data(), columns=cols) df.to_csv(fp2, index=False, mode='a', header=not file_exists) # 第一次写入后,标记文件已存在,后续不再写表头 file_exists = True
内容的提问来源于stack exchange,提问作者PythonKS
相关产品推荐
相关产品推荐

