
公司有个很简单的需求:
我用 python 写了一个脚本,GUI 使用的是 pyqt5
# -*- coding: utf-8 -*- import sys import os import csv import pandas as pd from datetime import datetime from PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QPushButton, QLineEdit, QVBoxLayout, QHBoxLayout, QFileDialog, QDateTimeEdit, QLabel, QMessageBox, QProgressBar, QStatusBar, ) from PyQt5.QtCore import QDateTime from PyQt5.QtGui import QIntValidator class CSV_Filter(QMainWindow): def __init__(self): super().__init__() self.central_widget = QWidget() self.setCentralWidget(self.central_widget) self.init_ui() def init_ui(self): # 创建组件 self.input_select_button = QPushButton('浏览...') self.input_path_text = QLineEdit() self.input_path_text.setReadOnly(True) self.datetime_start = QDateTimeEdit() self.datetime_end = QDateTimeEdit() self.time_diff_input = QLineEdit() self.start_button = QPushButton('开始合并') self.export_select_button = QPushButton('浏览...') self.export_path_text = QLineEdit() self.export_path_text.setReadOnly(True) # 创建进度条和状态栏 self.progress_bar = QProgressBar() self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) self.status_bar.addPermanentWidget(self.progress_bar) self.progress_bar.setValue(0) # 设置日期时间选择框 debug_time = QDateTime(2024, 5, 3, 19, 10) self.datetime_start.setDateTime(debug_time) self.datetime_end.setDateTime(debug_time) #now = QDateTime.currentDateTime() #self.datetime_start.setDateTime(now) #self.datetime_end.setDateTime(now) self.datetime_start.setCalendarPopup(True) self.datetime_end.setCalendarPopup(True) self.datetime_start.setDisplayFormat("yyyy-MM-dd HH:mm") self.datetime_end.setDisplayFormat("yyyy-MM-dd HH:mm") self.time_diff_input.setPlaceholderText("输入分钟数") self.time_diff_input.setValidator(QIntValidator()) self.start_button.setEnabled(False) layout = QVBoxLayout() layout.addWidget(QLabel("选择 log 路径:")) input_path_layout = QHBoxLayout() input_path_layout.addWidget(self.input_path_text) input_path_layout.addWidget(self.input_select_button) layout.addLayout(input_path_layout) layout.addWidget(QLabel("选择导出路径:")) export_path_layout = QHBoxLayout() export_path_layout.addWidget(self.export_path_text) export_path_layout.addWidget(self.export_select_button) layout.addLayout(export_path_layout) layout.addWidget(QLabel("开始时间:")) layout.addWidget(self.datetime_start) layout.addWidget(QLabel("时间差(分钟):")) layout.addWidget(self.time_diff_input) layout.addWidget(QLabel("结束时间:")) layout.addWidget(self.datetime_end) layout.addWidget(self.start_button) self.central_widget.setLayout(layout) self.setWindowTitle('CSV-Filter') self.input_select_button.clicked.connect(self.select_input_folder) self.export_select_button.clicked.connect(self.select_export_folder) self.start_button.clicked.connect(self.merge_csv) self.input_path_text.textChanged.connect(self.check_inputs) self.export_path_text.textChanged.connect(self.check_inputs) self.datetime_start.dateTimeChanged.connect(self.update_time_diff) self.datetime_end.dateTimeChanged.connect(self.update_time_diff) self.time_diff_input.textChanged.connect(self.update_end_time_from_diff) def select_input_folder(self): folder_path = QFileDialog.getExistingDirectory(self, '选择 log 所在的文件夹') if folder_path: self.input_path_text.setText(folder_path) def select_export_folder(self): folder_path = QFileDialog.getExistingDirectory(self, '选择导出 log 的文件夹') if folder_path: self.export_path_text.setText(folder_path) def check_inputs(self): flag_input = self.input_path_text.text().strip() != "" flag_export = self.export_path_text.text().strip() != "" self.start_button.setEnabled(flag_input and flag_export) def update_time_diff(self): start_time = self.datetime_start.dateTime() end_time = self.datetime_end.dateTime() time_dff = start_time.secsTo(end_time) / 60 self.time_diff_input.setText(str(int(time_diff))) def update_end_time_from_diff(self): try: time_diff_minutes = int(self.time_diff_input.text()) start_time = self.datetime_start.dateTime() new_end_time = start_time.addSecs(time_diff_minutes * 60) self.datetime_end.setDateTime(new_end_time) except ValueError: pass def merge_csv(self): input_path = self.input_path_text.text().strip() export_path = self.export_path_text.text().strip() start_time = self.datetime_start.dateTime().toPyDateTime() end_time = self.datetime_end.dateTime().toPyDateTime() csv_files = [] for root, dirs, files in os.walk(input_path): for file in files: if file.endswith('.csv'): csv_files.append(os.path.join(root,file)) if not csv_files: QMessageBox.warning(self,"提示","没有找到.csv 文件") return combined_df = pd.DataFrame() total_files = len(csv_files) self.progress_bar.setMaximum(total_files) self.progress_bar.setValue(0) self.status_bar.showMessage("正在处理 CSV 文件...") for index, csv_file in enumerate(csv_files, start=1): try: df = pd.read_csv(csv_file) df['Source File'] = csv_file df['DATE_TIME'] = pd.to_datetime( df['DATE_TIME'].str.extract(r'\[(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2})\]')[0], format='%Y/%m/%d %H:%M:%S', errors='coerce' ) combined_df = pd.concat([combined_df, df], ignore_index=True) except Exception as e: QMessageBox.warning(self, "读取错误", f"读取文件失败:{csv_file}\n\n 错误信息:{str(e)}") self.progress_bar.setValue(index) # 更新进度条 QApplication.processEvents() # 刷新界面 filtered_df = combined_df[(combined_df['DATE_TIME']>=start_time)&(combined_df['DATE_TIME']<=end_time)] filtered_df = filtered_df.sort_values(by='DATE_TIME') now = datetime.now() timestamp = now.strftime("%Y%m%d_%H%M") filename = f"filtered_log_{timestamp}.csv" filtered_df.to_csv(os.path.join(export_path,filename),index=False) self.status_bar.showMessage("完成!", 3000) QMessageBox.information(self, "完成", "已成功导出") if __name__ == '__main__': app = QApplication(sys.argv) window = CSV_Filter() window.show() sys.exit(app.exec_()) 但测试的时候发现 csv 数据很不规范
随便抽一条当个例子:
"37929","301","00 40 00 00 00 B9 30 30 3A 30 30 3A 30 32 3A 31 31 33 20 28 32 34 34 30 29 56 20 65 76 65 6E 74 20 36 35 30 20 70 75 62 6C 69 63 3A 38 2C 31 20 30 20 22 64 69 73 6B 3A 38 2C 30 22 20 22 22 0A ","[2025/02/20 12:00:51]","9250","DATA LOG","00:00:02:113 (2440)V event 650 public:8,1 0 "disk:8,0" "" " 数据应该是 7 列,但是读取到这里就会识别成 8 列然后报错. 我考虑过逐行读取不进行分列,只在其中用正则表达式抽选时间戳新增一列作为筛选的标准. 但因为原始数据中存在换行,这一条数据会被作为好几行读取,导致抽取时的损失
Python 新手,在 Chatgpt 的帮助下完成的,实在没办法了,有没有数据大手子帮忙看看
1 hertzry 243 天前 再写个功能规范原始数据后再读入。 |
2 zhusimaji 243 天前 如果不在意这些异常的数据的话,直接在接口 传参 skip error 就不会报错了。要保留所有数据,那就需要你自己 check 校验异常数据,尽量把其中你需要的数据提取出来 |
3 NoOneNoBody 243 天前 你的 csv 不规范也没办法,例如双引号内还有双引号 建议先预处理 csv ,再读入,例如将分隔符逗号前后的双引号换成其他不会歧义的字符,然后 read_csv 指定 quotechar=特殊字符 参数 其实这种数据,在生成 csv 时改一下分隔符,例如不用逗号用分号,以后处理就简单了,但现在没法回头了 |
4 Sawyerhou 242 天前 via Android df=pd.read_csv(csv_file,seq=r'","') 思路是把分割符由逗号, 改为逗号和双引号的组合, 我手边没电脑没测试这个方法, 你试试看,不确定好不好用。 |
5 Sawyerhou 242 天前 via Android |
6 F281M6Dh8DXpD1g2 242 天前 "合并多个 csv 文件.根据时间戳抽取其中一部分并导出" 这个需求其实不简单 有几个地方你要考虑 随便列列: 字段里面有标识符换行符不可见字符 字段不够 字段解析不了 最后一行没传完 |
7 mumbler 242 天前 用 cursor ,几分钟就写出来了,有问题让他改,2025 了,不要再手工写代码了 |
8 henix 242 天前 假设最后一列的双引号一定是成对出现的,可以自己写个 csv 解析,特殊处理最后一列 |
9 SOSdanOffical OP @liprais 是的哥,不过这是已经量产的商品导出的 log,而且不属于关键机能,估计是没可能让他们改了 |
10 zealotxxxx 242 天前 你要做的应该是先把数据规范化,确保没有异常值就可以了。无效数据报错或者过滤,自行修改即可。 |
11 biubiuF 242 天前 用原生 csvReader 读取,并且设置 newline='',再把 reader 对象转为 df |
12 sgld 240 天前 可以尝试让 ai 写个辅助代码,规范一下 csv 格式,最后一列里面有个 `,` 应该是这里导致第 7 列变成了第 7 列和第 8 列 |
13 sgld 240 天前 ```python import csv with open("test.csv", "r", encoding="latin-1") as f: reader = csv.reader( f, delimiter=",", quotechar='"', doublequote=True, escapechar="\\", skipinitialspace=True, ) try: for row in reader: cleaned_row = [field.strip().replace("\n", " ") for field in row] print("Parsed Columns:") for idx, col in enumerate(cleaned_row, 1): print(f"Column {idx}: {col}") except csv.Error as e: print(f"CSV 解析错误: {e}") ``` ``` Parsed Columns: Column 1: 37929 Column 2: 301 Column 3: 00 40 00 00 00 B9 30 30 3A 30 30 3A 30 32 3A 31 31 33 20 28 32 34 34 30 29 56 20 65 76 65 6E 74 20 36 35 30 20 70 75 62 6C 69 63 3A 38 2C 31 20 30 20 22 64 69 73 6B 3A 38 2C 30 22 20 22 22 0A Column 4: [2025/02/20 12:00:51] Column 5: 9250 Column 6: DATA LOG Column 7: 00:00:02:113 (2440)V event 650 public:8,1 0 disk:8,0" "" ``` |
14 sgld 240 天前 一次成功,hhh |
15 SOSdanOffical OP @sgld 太感谢了哥,我也去改改我的 我是用了个很笨的办法: 1. 读取整个文件 2. 用正则表达式体换掉所有的换行符(除了"后面跟着的换行符) 3. 用换行符分行为列表 4. 把整个列表读取为 pd 5. 从 pd 中抽时间戳 |