场景与目的: 写简单的脚本,执行任务,数据量不是很多, 希望记录历史(像是使用mongodb 一样),和防止重复执行, 同时达到 事件日志的目的。(希望数据可视化,不引入其它数据库,也不需要创建表(sqllite))
1. HistoryUtils.py (class版本)
2. HistoryUtil.py 脚本版
1. HistoryUtils.py
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # @mail : lshan523@163.com # @Time : 2026/1/15 10:46 # @Author : Sea # @File : HistoryUtil.py # @Purpose : # @history : # 小文件场景下,使用json文件存储历史记录,每次追加,后续可以查询,修改,删除 # **************************** import os import json from typing import Dict, Any, List, Optional import uuid import tempfile import shutil class HistoryUtils: def __init__(self, history_file="./history/history.json"): #历史记录文件 self.history_file = history_file def save(self, history={},history_file= None): """ 保存历史记录 把文件写入 本地json文件,可以一直追加,后续可以查询 :param history_file: 历史记录文件 eg:./history/history.json :param history: 历史记录 eg:{"name": "张三9", "age": 27} :return: """ history_file = self.history_file if not history: print("history is None") return # history 必须是 dict assert isinstance(history, dict), "history 必须是 dict" history["_id"]=str(uuid.uuid4()).replace("-", "") # 创建文件 if not os.path.exists(history_file): print("history.json 不存在 创建文件 "+str(history_file)) os.makedirs(os.path.dirname(history_file), exist_ok=True) with open(history_file, 'a', encoding='utf-8') as f: f.write(json.dumps(history, ensure_ascii=False)) f.write("\n") print("保存历史记录") def find_one(self,query: Dict[str, Any]={},history_file: str= None) -> List[Dict[str, Any]]: """ 查询历史记录 :param history_file: 历史记录文件 :param query: {"name": "张三1"} :return: """ print("查询历史记录") history_file = self.history_file result = self.query_all_history(query,1) if result: return result[0] return None def find(self,query: Dict[str, Any] = {}, limit: Optional[int] = None,history_file: str = None) -> List[Dict[str, Any]]: """ 查询所有匹配的历史记录 Args: history_file: 历史记录文件路径 query: 查询条件字典,如 {"name": "张三1"} Returns: 所有匹配的记录列表,按时间倒序排列(最新的在前) """ history_file = self.history_file results = [] try: with open(history_file, 'r', encoding='utf-8') as f: # 读取所有行并反转(最新的在前) lines = f.readlines() for line in reversed(lines): line = line.strip() if not line: # 跳过空行 continue try: history = json.loads(line) # 条件匹配 if all(history.get(key) == value for key, value in query.items()): results.append(history) print(f"查询到匹配记录: {history}") if limit and len(results) >= limit: break except json.JSONDecodeError: line_preview =line[:50] + "..." if len(line) > 50 else line print(f"JSON解析失败,跳过行: {line_preview}") continue except FileNotFoundError: print(f"文件不存在: {history_file}") return [] except Exception as e: print(f"查询失败: {e}") return [] print(f"共查询到 {len(results)} 条匹配记录") return results def update(self,query, update, limit=1,history_file=None): """ 更新历史记录 文件: ./history/history.json 内容:{"name": "张三6", "age": 24} \n {"name": "张三7", "age": 25} \n {"name": "张三8", "age": 26} \n 高效更新 - 直接定位并修改 只更新前limit条匹配的记录 Args: history_file: 历史记录文件路径 query: 查询条件 {"name": "张三8", "age": 26} update: 更新内容 {"name": "张三8", "age": 18} limit: 最多更新的条数(默认1) Returns: 实际更新的条数 """ history_file = self.history_file updated_count = 0 if not os.path.exists(history_file): return 0 with open(history_file, 'r+', encoding='utf-8') as f: while updated_count < limit: line_start = f.tell() line = f.readline() if not line: break line = line.rstrip('\n') if not line: continue try: record = json.loads(line) # 检查是否匹配 if all(record.get(k) == v for k, v in query.items()): # 移动到行开始位置 f.seek(line_start) # 更新记录 record.update(update) # 写入更新后的行 updated_line = json.dumps(record, ensure_ascii=False) + '\n' # 读取当前位置到文件末尾 f.readline() # 跳过原始行 remaining = f.read() # 重新定位并写入 f.seek(line_start) f.write(updated_line) if remaining: f.write(remaining) # 截断文件 f.truncate() # 重置文件指针以便继续读取 f.seek(line_start + len(updated_line)) updated_count += 1 print(f"快速更新第{updated_count}条: {record}") if updated_count >= limit: print("~~ 达到更新限制,结束 ~~") break except json.JSONDecodeError: continue print(f"总共更新了 {updated_count} 条记录") return updated_count def delete(self, query={}, limit=1, history_file=None): """ 删除历史记录 文件: ./history/history.json 内容:{"name": "张三6", "age": 24} \n {"name": "张三7", "age": 25} \n {"name": "张三8", "age": 26} \n Args: query: 删除条件 eg: {"name": "张三8", "age": 26} history_file: 历史记录文件路径 limit: 删除的条数 Returns: 实际删除 """ history_file = self.history_file if not query and limit == 1: return 0 deleted_count = 0 temp_file_path = None try: # 检查文件是否存在 if not os.path.exists(history_file): print(f"文件 {history_file} 不存在") return 0 # 确保目录存在 os.makedirs(os.path.dirname(history_file), exist_ok=True) # 在同目录下创建临时文件,避免跨磁盘问题 temp_dir = os.path.dirname(os.path.abspath(history_file)) if not temp_dir: # 如果是当前目录 temp_dir = os.path.dirname(os.path.abspath(__file__)) # 创建临时文件 temp_fd, temp_file_path = tempfile.mkstemp(dir=temp_dir, suffix='.tmp') # 读取原文件,过滤记录 with open(history_file, 'r', encoding='utf-8') as src_file, \ open(temp_fd, 'w', encoding='utf-8') as temp_file: for line in src_file: line = line.strip() if not line: temp_file.write('\n') continue try: record = json.loads(line) # 检查是否匹配查询条件 match = True for key, value in query.items(): if key not in record or record[key] != value: match = False break # 如果匹配且尚未达到删除限制,则跳过(删除) if match and (limit == 0 or deleted_count < limit): deleted_count += 1 continue # 否则保留记录 temp_file.write(line + '\n') except json.JSONDecodeError: # 如果不是有效的JSON,保留原样 temp_file.write(line + '\n') # 关闭文件后移动 # 先备份原文件 backup_file = history_file + '.bak' if os.path.exists(history_file): shutil.copy2(history_file, backup_file) # 用临时文件替换原文件 shutil.move(temp_file_path, history_file) # 删除备份文件(可选) if os.path.exists(backup_file): os.remove(backup_file) print(f"删除了 {deleted_count} 条记录") return deleted_count except Exception as e: print(f"删除过程中出现错误: {e}") # 如果发生错误,恢复备份 if 'backup_file' in locals() and os.path.exists(backup_file): print("尝试恢复备份...") try: shutil.move(backup_file, history_file) print("备份已恢复") except: print("恢复备份失败") # 清理临时文件 if temp_file_path and os.path.exists(temp_file_path): try: os.remove(temp_file_path) except: pass return 0 def test_save_history(): utils = HistoryUtils("./history/history1.json") for i in range(10): utils.save( {"name": "张三"+str(i), "age": i}) utils.find_one( {"name": "张三"+str(i)}) utils.update({"name": "张三"+str(i)}, {"age": 198},5) # utils.delete({"age": 198}, 5) if __name__ == '__main__': utils = HistoryUtils("./history/history1.json") # test_save_history() rts = utils.find(query={"name": "张三9"}, limit=5) print(rts)
2. HistoryUtil.py
#!/usr/bin/env python3 # -*- coding: utf-8 -*- # @mail : lshan523@163.com # @Time : 2026/1/15 10:46 # @Author : Sea # @File : HistoryUtil.py # @Purpose : # @history : # 小文件场景下,使用json文件存储历史记录,每次追加,后续可以查询 # **************************** import os import json from typing import Dict, Any, List, Optional import uuid import tempfile import shutil # 历史记录文件 HISTORY_FILE="./history/history.json" def save(history={},history_file=HISTORY_FILE): """ 保存历史记录 把文件写入 本地json文件,可以一直追加,后续可以查询 :param history_file: 历史记录文件 eg:./history/history.json :param history: 历史记录 eg:{"name": "张三9", "age": 27} :return: """ if not history: print("history is None") return # history 必须是 dict assert isinstance(history, dict), "history 必须是 dict" history["_id"]=str(uuid.uuid4()).replace("-", "") # 创建文件 if not os.path.exists(history_file): print("history.json 不存在 创建文件 "+str(history_file)) os.makedirs(os.path.dirname(history_file), exist_ok=True) with open(history_file, 'a', encoding='utf-8') as f: f.write(json.dumps(history, ensure_ascii=False)) f.write("\n") print("保存历史记录") def find_one(query: Dict[str, Any]={},history_file: str= HISTORY_FILE) -> List[Dict[str, Any]]: """ 查询历史记录 :param history_file: 历史记录文件 :param query: {"name": "张三1"} :return: """ print("查询历史记录") result = find(query,1) if result: return result[0] return None def find(query: Dict[str, Any] = {}, limit: Optional[int] = None,history_file: str = HISTORY_FILE) -> List[Dict[str, Any]]: """ 查询所有匹配的历史记录 limit 限制, 默认倒序 Args: history_file: 历史记录文件路径 query: 查询条件字典,如 {"name": "张三1"} Returns: 所有匹配的记录列表,按时间倒序排列(最新的在前) """ results = [] try: with open(history_file, 'r', encoding='utf-8') as f: # 读取所有行并反转(最新的在前) lines = f.readlines() for line in reversed(lines): line = line.strip() if not line: # 跳过空行 continue try: history = json.loads(line) # 条件匹配 if all(history.get(key) == value for key, value in query.items()): results.append(history) print(f"查询到匹配记录: {history}") if limit and len(results) >= limit: break except json.JSONDecodeError: line_preview =line[:50] + "..." if len(line) > 50 else line print(f"JSON解析失败,跳过行: {line_preview}") continue except FileNotFoundError: print(f"文件不存在: {history_file}") return [] except Exception as e: print(f"查询失败: {e}") return [] print(f"共查询到 {len(results)} 条匹配记录") return results def update(query, update, limit=1,history_file=HISTORY_FILE): """ 更新历史记录 文件: ./history/history.json 内容:{"name": "张三6", "age": 24} \n {"name": "张三7", "age": 25} \n {"name": "张三8", "age": 26} \n 高效更新 - 直接定位并修改 只更新前limit条匹配的记录 Args: history_file: 历史记录文件路径 query: 查询条件 {"name": "张三8", "age": 26} update: 更新内容 {"name": "张三8", "age": 18} limit: 最多更新的条数(默认1) Returns: 实际更新的条数 """ updated_count = 0 if not os.path.exists(history_file): return 0 with open(history_file, 'r+', encoding='utf-8') as f: while updated_count < limit: line_start = f.tell() line = f.readline() if not line: break line = line.rstrip('\n') if not line: continue try: record = json.loads(line) # 检查是否匹配 if all(record.get(k) == v for k, v in query.items()): # 移动到行开始位置 f.seek(line_start) # 更新记录 record.update(update) # 写入更新后的行 updated_line = json.dumps(record, ensure_ascii=False) + '\n' # 读取当前位置到文件末尾 f.readline() # 跳过原始行 remaining = f.read() # 重新定位并写入 f.seek(line_start) f.write(updated_line) if remaining: f.write(remaining) # 截断文件 f.truncate() # 重置文件指针以便继续读取 f.seek(line_start + len(updated_line)) updated_count += 1 print(f"快速更新第{updated_count}条: {record}") if updated_count >= limit: print("~~ 达到更新限制,结束 ~~") break except json.JSONDecodeError: continue print(f"总共更新了 {updated_count} 条记录") return updated_count def delete(query={}, limit=1, history_file="history/history.json"): """ 删除历史记录 文件: ./history/history.json 内容:{"name": "张三6", "age": 24} \n {"name": "张三7", "age": 25} \n {"name": "张三8", "age": 26} \n Args: query: 删除条件 eg: {"name": "张三8", "age": 26} history_file: 历史记录文件路径 limit: 删除的条数 Returns: 实际删除 """ if not query and limit == 1: return 0 deleted_count = 0 temp_file_path = None try: # 检查文件是否存在 if not os.path.exists(history_file): print(f"文件 {history_file} 不存在") return 0 # 确保目录存在 os.makedirs(os.path.dirname(history_file), exist_ok=True) # 在同目录下创建临时文件,避免跨磁盘问题 temp_dir = os.path.dirname(os.path.abspath(history_file)) if not temp_dir: # 如果是当前目录 temp_dir = os.path.dirname(os.path.abspath(__file__)) # 创建临时文件 temp_fd, temp_file_path = tempfile.mkstemp(dir=temp_dir, suffix='.tmp') # 读取原文件,过滤记录 with open(history_file, 'r', encoding='utf-8') as src_file, \ open(temp_fd, 'w', encoding='utf-8') as temp_file: for line in src_file: line = line.strip() if not line: temp_file.write('\n') continue try: record = json.loads(line) # 检查是否匹配查询条件 match = True for key, value in query.items(): if key not in record or record[key] != value: match = False break # 如果匹配且尚未达到删除限制,则跳过(删除) if match and (limit == 0 or deleted_count < limit): deleted_count += 1 continue # 否则保留记录 temp_file.write(line + '\n') except json.JSONDecodeError: # 如果不是有效的JSON,保留原样 temp_file.write(line + '\n') # 关闭文件后移动 # 先备份原文件 backup_file = history_file + '.bak' if os.path.exists(history_file): shutil.copy2(history_file, backup_file) # 用临时文件替换原文件 shutil.move(temp_file_path, history_file) # 删除备份文件(可选) if os.path.exists(backup_file): os.remove(backup_file) print(f"删除了 {deleted_count} 条记录") return deleted_count except Exception as e: print(f"删除过程中出现错误: {e}") # 如果发生错误,恢复备份 if 'backup_file' in locals() and os.path.exists(backup_file): print("尝试恢复备份...") try: shutil.move(backup_file, history_file) print("备份已恢复") except: print("恢复备份失败") # 清理临时文件 if temp_file_path and os.path.exists(temp_file_path): try: os.remove(temp_file_path) except: pass return 0 def test_save_history(): for i in range(10): save( {"name": "张三"+str(i), "age": i}) find_one( {"name": "张三"+str(i)}) update({"name": "张三"+str(i)}, {"age": 198},5) if __name__ == '__main__': # test_save_history() delete({"age": 198}, 5)
浙公网安备 33010602011771号