
处理Pandas中带有内嵌双引号的制表符分隔文件时,标准解析常因字段内外双引号的冲突而导致数据损坏。本教程深入探讨了三种解决方案:一是利用Python `csv` 模块忽略引用并手动解析;二是自定义解码与编码函数,实现对特定“坏格式”的完美往返读写;三是结合正则表达式预处理和Pandas的`escapechar`参数。这些方法旨在确保数据完整性,并准确还原原始文件格式,帮助开发者有效应对复杂数据导入导出场景。
在数据处理过程中,我们经常会遇到需要导入和导出制表符分隔文件(TSV)的场景。然而,当这些文件中存在特殊情况,例如字段内容本身包含双引号,并且整个字段又被双引号包裹时,标准的解析库如Pandas的read_csv或Python内置的csv模块可能会因为默认的引用处理机制而导致数据被“破坏”。本教程将详细介绍如何有效应对这种挑战,确保数据在读写过程中保持其原始完整性。
考虑以下一个典型的制表符分隔文件 test.exp:
"HEADER" "ID" "Part Reference" "Value" "Part_Description" "PARTOCC:17306" "17306" "M1" "48SL-5S-50-C-LF" "Series 48SL–5 WEDGE–LOK, 2-56UNC-2B, 5.00", chem film, lock washer and flat" "PARTOCC:17310" "17310" "M2" "48SL-5S-50-C-LF" "Series 48SL–5 WEDGE–LOK, 2-56UNC-2B, 5.00", chem film, lock washer and flat" "PARTOCC:65494" "65494" "J4E" "311P822-MC-095-BS-D" "GSFC CPCI J3 RA MALE 95 Position 0.123" tails"
注意 Part_Description 字段中的值,例如 "Series 48SL–5 WEDGE–LOK, 2-56UNC-2B, 5.00", chem film, lock washer and flat"。这个字段以双引号开始和结束,但其内部也包含了一个双引号(5.00")。如果直接使用 pd.read_csv 并尝试写回,可能会得到如下结果:
原始字段: "Series 48SL–5 WEDGE–LOK, 2-56UNC-2B, 5.00", chem film, lock washer and flat"
写回后可能变为: "Series 48SL–5 WEDGE–LOK, 2-56UNC-2B, 5.00, chem film, lock washer and flat"""
这表明Pandas在读取时可能将内部的引号错误地解释为字段的结束,或者在写入时为了符合CSV标准而对内部引号进行了不必要的转义(例如,双引号变为两个双引号)。我们的目标是实现输入与输出的精确匹配。
这种方法适用于文件格式严格遵循以下规则的场景:
其核心思想是,在读取时指示 csv 模块忽略引用处理,然后手动通过字符串切片去除字段的外部双引号。
import csv
def parse_tsv_with_manual_quoting(filepath: str) -> list[list[str]]:
"""
解析一个制表符分隔文件,该文件的字段被双引号包裹,且内部可能含有双引号。
通过忽略引用并手动切片来处理。
"""
rows: list[list[str]] = []
with open(filepath, newline="", encoding='utf-8') as f:
# 使用 csv.QUOTE_NONE 忽略引用,并指定制表符为分隔符
reader = csv.reader(f, delimiter=" ", quoting=csv.QUOTE_NONE)
# 处理标题行:去除外部双引号
header = [x[1:-1] for x in next(reader)]
rows.append(header) # 将处理后的标题也加入rows,或单独保存
# 处理数据行:去除每个字段的外部双引号
for row in reader:
processed_row = [x[1:-1] for x in row]
rows.append(processed_row)
return rows[0], rows[1:] # 返回标题和数据
# 示例使用
exp_fn = "test.exp"
header, data_rows = parse_tsv_with_manual_quoting(exp_fn)
print("Header:", header)
print("Data Rows:")
for row in data_rows:
print(row)上述代码将正确解析出以下数据结构:
Header: ['HEADER', 'ID', 'Part Reference', 'Value', 'Part_Description'] Data Rows: ['PARTOCC:17306', '17306', 'M1', '48SL-5S-50-C-LF', 'Series 48SL–5 WEDGE–LOK, 2-56UNC-2B, 5.00", chem film, lock washer and flat'] ['PARTOCC:17310', '17310', 'M2', '48SL-5S-50-C-LF', 'Series 48SL–5 WEDGE–LOK, 2-56UNC-2B, 5.00", chem film, lock washer and flat'] ['PARTOCC:65494', '65494', 'J4E', '311P822-MC-095-BS-D', 'GSFC CPCI J3 RA MALE 95 Position 0.123" tails']
如果目标是将数据写回 原始的“坏格式”,即每个字段都被双引号包裹,且内部的双引号不进行额外转义,则需要手动添加外部双引号。
def write_tsv_with_manual_quoting(filepath: str, header: list[str], data_rows: list[list[str]]):
"""
将解析后的数据写回制表符分隔文件,并保持原始的“坏格式”。
"""
with open(filepath, "w", newline="", encoding='utf-8') as f:
writer = csv.writer(f, delimiter=" ", quoting=csv.QUOTE_NONE)
# 手动为每个字段添加外部双引号
writer.writerow([f'"{field}"' for field in header])
for row in data_rows:
writer.writerow([f'"{field}"' for field in row])
# 示例使用
output_fn = "check_manual.exp"
write_tsv_with_manual_quoting(output_fn, header, data_rows)
print(f"数据已写回至 {output_fn},请检查其格式是否与原始文件一致。")注意事项:
当需要确保输入文件和输出文件在字节级别上完全一致时,自定义解码(decode)和编码(encode)函数是更精确和鲁棒的选择。这种方法明确地定义了“坏格式”的解析和生成规则。
import sys
END = "
"
"""预期行结束符"""
DELIM = " "
"""预期分隔符"""
QUOT = '"'
"""预期引用字符"""
Row = list[str]
def decode(line: str) -> Row:
"""
解码一个“坏格式”的CSV/TSV行。
确保行以END结尾,按DELIM分割行,并验证每个字段是否以QUOT开始和结束,然后去除这些外部QUOT。
返回字段列表。
"""
if not line.endswith(END):
raise ValueError(f"行不以 {repr(END)} 结尾")
line = line[:-len(END)] # 移除行结束符
fields = line.split(DELIM)
for i, field in enumerate(fields):
if not (field.startswith(QUOT) and field.endswith(QUOT)):
raise ValueError(f"字段 {i} 未被 {repr(QUOT)} 包裹: {repr(field)}")
fields[i] = field[1:-1] # 移除外部双引号
return fields
def encode(row: Row) -> str:
"""
将行编码回其原始的“坏格式”。
"""
# 为每个字段添加外部双引号,然后用制表符连接
line = DELIM.join([f"{QUOT}{field}{QUOT}" for field in row])
return line + END # 添加行结束符
def exit_err(msg: str):
print(msg, file=sys.stderr)
sys.exit(1)
# 读取并解码文件
input_filepath = "test.exp"
output_filepath = "check_custom.exp"
decoded_rows: list[Row] = []
with open(input_filepath, encoding='utf-8') as f:
try:
header = decode(next(f))
decoded_rows.append(header)
except ValueError as e:
exit_err(f"无法解码标题行: {e}")
for i, line in enumerate(f):
try:
decoded_rows.append(decode(line))
except ValueError as e:
exit_err(f"无法解码第 {i+2} 行: {e}") # i从0开始,所以是i+2行
# 编码并写回文件
with open(output_filepath, "w", encoding='utf-8') as f:
for row in decoded_rows:
f.write(encode(row))
print(f"数据已通过自定义编解码器处理并写回至 {output_filepath}。")
# 验证输入输出一致性
# 在命令行中运行:python your_script_name.py; diff test.exp check_custom.exp
# 如果没有任何输出,则表示文件内容完全一致。这种方法的优点是其精确性和鲁棒性。通过明确定义解码和编码规则,我们可以确保即使面对非标准的“坏格式”文件,也能实现数据的完美往返,从而保证输入和输出文件在内容和格式上完全一致。
这种方法尝试在Pandas的框架内解决问题,通过在读取前对文件内容进行正则表达式预处理,并在写入时配置 to_csv 参数,以期达到与原始格式匹配的效果。
import csv
import re
from io import StringIO
import pandas as pd
input_filepath = "test.exp"
output_filepath = "check_pandas_re.exp"
with open(input_filepath, encoding='utf-8') as f:
text = f.read()
# 预处理:将形如 '数字.数字"' 的内部双引号转义为 '数字.数字"'
# 这样Pandas在读取时,可以通过 escapechar='\' 识别并正确处理
text = re.sub(r'(d+.d+)"', r'g<1>\"', text)
# 使用 StringIO 将预处理后的文本作为文件对象传递给 Pandas
df = pd.read_csv(StringIO(text), quotechar='"', sep=" ", escapechar="\")
# 准备输出文件
out_f = StringIO()
# 写入CSV:
# sep=" ":制表符分隔
# quoting=csv.QUOTE_ALL:所有字段都用双引号包裹
# doublequote=False:不将内部双引号转义为双倍双引号
# escapechar="\":使用反斜杠作为转义字符,对应我们之前的预处理
# index=False:不写入DataFrame的索引
df.to_csv(
out_f,
sep=" ",
quoting=csv.QUOTE_ALL,
doublequote=False,
escapechar="\",
index=False,
)
# 获取Pandas写入后的字符串
output_text = out_f.getvalue()
# 后处理:将转义的反斜杠还原,以匹配原始的“坏格式”
output_text = output_text.replace(r'"', '"')
with open(output_filepath, "w", encoding='utf-8') as f:
f.write(output_text)
print(f"数据已通过Pandas结合正则表达式处理并写回至 {output_filepath}。")
# 同样可以通过 diff 命令验证 input_filepath 和 output_filepath 的一致性注意事项:
处理带有内嵌双引号的制表符分隔文件,尤其当需要保持原始的“坏格式”时,选择合适的策略至关重要。
如果您的主要目标是读取数据并将其导入到应用程序中进行处理,并且文件格式严格(所有字段被外部双引号包裹,内部无制表符/换行符),那么 方法一(使用 csv 模块手动解析) 是最简洁高效的。它直接获取了干净的数据。
如果您的核心需求是实现输入文件和输出文件在格式和内容上的完美往返(即,即使原始格式“不标准”,也要精确还原),那么 方法二(自定义解码与编码函数) 是最可靠和精确的。它提供了对解析和生成过程的完全控制,适用于对数据保真度要求极高的场景。
如果您希望尽量利用Pandas的强大功能,并且能够接受通过正则表达式对文件内容进行预处理和后处理,那么 方法三(结合正则表达式预处理和Pandas参数) 是一种选择。但请注意,这需要对正则表达式有良好的掌握,并且可能需要根据实际文件中的内部引号模式进行细致调整。
在实际应用中,理解数据源的具体“坏格式”是解决问题的关键。根据格式的复杂性和对数据往返保真度的要求,选择最适合的方法。通常,对于非标准格式,自定义的解析逻辑(方法二)能提供最大的灵活性和准确性。
以上就是Pandas中处理内嵌双引号的制表符分隔文件:读写策略与最佳实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号