前言 !!!!温馨提示:假如你是小白,啥都不懂,请直接跳到文章的最后!!!
原本我是没想写这篇文章,契机是某一天我突然打开微信的存储空间占用才发现。多少?😡我寻思平常也不怎么用微信,怎么占用的空间这么离谱,居然有20个G左右,这是在微信上存了个小电影吗?
抱着好奇心的我,打开了微信的物理存储位置,其他平台我没试过,仅在windows平台下测试过 ,存储位置一般能通过这样打开
找寻到存储文件的位置,一般是子目录 FileStorage\File
这个位置就是用来存储通过微信保存的各种文件,而且会按照日期排序
随便点进去一个会发现,这个文件夹下存储了大量相同的文件,然后在原有的文件的名字后面加上(1),(2),(3),(1)(1)这种序号,也就是说这些文件都是被重复存储了
存在的问题 通过上面的流程,可以得出一个结论,微信是存储了大量重复文件,才会占用这么大的存储空间 ,我在网上也找了一些解释,这个似乎跟微信的存储策略有关
据悉,这种情况出现的原因之一是微信的文件转发策略导致。例如将同一份文件转发给多个好友,每一次转发都会重新保存一份在手机上,重复文件占用了手机大量存储空间。而且过大的空间占用也会使手机出现明显的卡顿和发热等情况,影响正常使用。
也就是说,我们完全可以把这些重复的文件全部删除,但是我的微信记录已经有好几年了,要是一个个删除,是一件费时费力的方法。于是!🤓我突发奇想,既然这样的话,编写一个批量删除的程序不就行了?😗
效果展示 老规矩,我喜欢在看一系列繁琐的步骤前先看看效果,这样我才有继续看下去的欲望,相信大多数人都是这样想的😝
首先执行程序,选择是否要进行递归删除,递归删除意思就是,你选择的目录的子目录里面的重复文件也会被删除。这里我们选择n
选择路径
执行效果,按回车可以退出
删除的文件会到回收站里面,而不是永久删除,这样就能有撤销的余地了
问题的解决与思路 这块的内容会涉及到一些专业知识,假如你不懂,那么直接跳过就行,使用我打包好的程序即可。
首先通过观察可以发现,微信在保存这些重复文件的特点是在文件名后加上序号,比如
1 2 3 4 字符串.doc 字符串(1).doc 字符串(2).doc 字符串(1)(1).doc
我们可以通过正则表达式去匹配这种字符串,“字符”+“序号”.”后缀名”
1 2 3 4 5 """ .*表示匹配任意字符 (\d)匹配带有括号的序号 """ .*\(\d\).*
这样我们就能通过程序识别这些文件
其他,有次非常关键的点是,假如你在微信勾选了这个选项,那么存储的文件会因为只读权限,导致删除失败,因此在识别到文件之后,我们还需要改变文件的权限,才能删除,这里可以使用os.chmod函数做到
1 2 3 4 5 6 7 8 for file in matchfilelist: try : os.chmod(file, stat.S_IWRITE) send2trash.send2trash(os.path.normpath(file)) print (f"成功删除了文件: {file} " ) except Exception as e: print (f"删除文件时出现错误: {file} ,错误信息: {e} " )
最后我们需要解决的问题是,由于文件夹的子目录数量非常多,因此我们可能不想对每个文件夹都单独执行一遍程序,最好是能递归地自动执行,这里我们可以使用os.walk,它可以记录目录的文件数,从而能递归对子目录也进行删除操作
1 2 3 def recursive_delete_file (directory ): for root, dirs, files in os.walk(directory): delete_file(root)
完整程序实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 import osimport statimport reimport tkinter as tkfrom tkinter import filedialogimport send2trashdef delete_file (folder_path ): pattern = re.compile (r'.*\(\d\).*' ) filelist=os.listdir(folder_path) matchfilelist=[] for file in filelist: if re.match (pattern,file): matchfilelist.append(folder_path+"/" +file) for file in matchfilelist: try : os.chmod(file, stat.S_IWRITE) send2trash.send2trash(os.path.normpath(file)) print (f"成功删除了文件: {file} " ) except Exception as e: print (f"删除文件时出现错误: {file} ,错误信息: {e} " ) def recursive_delete_file (directory ): for root, dirs, files in os.walk(directory): delete_file(root) def main (): root = tk.Tk() root.withdraw() print ("是否采取递归删除?(y/n),递归操作相当危险,请谨慎选择" ) flag=input () file_path = filedialog.askdirectory() if flag=="y" or flag=='Y' : recursive_delete_file(file_path) else : delete_file(file_path) root.destroy() input ("按回车键退出" ) if __name__=='__main__' : main()
2024-10-10更新 最近我正在学习pyqt,因此🤓!!我用qt框架将原来的代码封装了一下,做了一个简单的界面(学以致用嘛😗)
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 import osimport statimport refrom sys import argvfrom sys import exitimport send2trashfrom PyQt5.QtWidgets import *from PyQt5.QtGui import *from PyQt5.QtCore import *QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) from PyQt5 import QtCore, QtWidgetsclass Ui_Form (object ): def setupUi (self, Form ): Form.setObjectName("文件删除工具" ) Form.resize(383 , 182 ) self .verticalLayoutWidget = QtWidgets.QWidget(Form) self .verticalLayoutWidget.setGeometry(QtCore.QRect(0 , 0 , 381 , 181 )) self .verticalLayoutWidget.setObjectName("verticalLayoutWidget" ) self .verticalLayout = QtWidgets.QVBoxLayout(self .verticalLayoutWidget) self .verticalLayout.setContentsMargins(0 , 0 , 0 , 0 ) self .verticalLayout.setObjectName("verticalLayout" ) self .textBrowser = QtWidgets.QTextBrowser(self .verticalLayoutWidget) self .textBrowser.setObjectName("textBrowser" ) self .verticalLayout.addWidget(self .textBrowser, 0 , QtCore.Qt.AlignVCenter) self .horizontalLayout = QtWidgets.QHBoxLayout() self .horizontalLayout.setObjectName("horizontalLayout" ) self .pushButton = QtWidgets.QPushButton(self .verticalLayoutWidget) self .pushButton.setObjectName("pushButton" ) self .horizontalLayout.addWidget(self .pushButton) self .pushButton_2 = QtWidgets.QPushButton(self .verticalLayoutWidget) self .pushButton_2.setObjectName("pushButton_2" ) self .horizontalLayout.addWidget(self .pushButton_2) self .verticalLayout.addLayout(self .horizontalLayout) self .retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi (self, Form ): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form" , "Form" )) self .textBrowser.setHtml(_translate("Form" , "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n" "<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n" "p, li { white-space: pre-wrap; }\n" "</style></head><body style=\" font-family:\'SimSun\'; font-size:9pt; font-weight:400; font-style:normal;\">\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-family:\'Ubuntu Mono derivative Powerline\',\'Consolas\',\'Courier New\',\'monospace\'; font-size:16pt; color:#0a3069;\">是否采取递归删除?</span></p>\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; line-height:24px; background-color:#ffffff;\"><span style=\" font-family:\'Ubuntu Mono derivative Powerline\',\'Consolas\',\'Courier New\',\'monospace\'; font-size:16pt; color:#0a3069;\">注:递归删除的意思是,将文件子目录的文件也会一并删除</span></p>\n" "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; line-height:24px; background-color:#ffffff;\"><span style=\" font-family:\'Ubuntu Mono derivative Powerline\',\'Consolas\',\'Courier New\',\'monospace\'; font-size:16pt; color:#0a3069;\">请谨慎使用!!!</span></p></body></html>" )) self .pushButton.setText(_translate("Form" , "是" )) self .pushButton_2.setText(_translate("Form" , "否" )) def delete_file (folder_path ): pattern = re.compile (r'.*\(\d\).*' ) filelist=os.listdir(folder_path) matchfilelist=[] for file in filelist: if re.match (pattern,file): matchfilelist.append(folder_path+"/" +file) for file in matchfilelist: try : os.chmod(file, stat.S_IWRITE) send2trash.send2trash(os.path.normpath(file)) print (f"成功删除了文件: {file} " ) except Exception as e: print (f"删除文件时出现错误: {file} ,错误信息: {e} " ) def recursive_delete_file (directory ): for root, dirs, files in os.walk(directory): delete_file(root) class delete_tool_widget (QWidget,Ui_Form): def __init__ (self ): super ().__init__() self .setWindowTitle("文件删除工具" ) self .setupUi(self ) self .pushButton.clicked.connect(self .yes_event) self .pushButton_2.clicked.connect(self .no_event) def yes_event (self ): directory = QFileDialog.getExistingDirectory(None ,"选择目录" ,"." ,QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) recursive_delete_file(directory) exit(app.exec_()) def no_event (self ): directory = QFileDialog.getExistingDirectory(None ,"选择目录" ,"." ,QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks) delete_file(directory) exit(app.exec_()) if __name__=='__main__' : app=QApplication(argv) window=delete_tool_widget() window.show() exit(app.exec_()) input ()
界面
给小白 假如上面的内容你完全不知道是什么,不会python,也不知道啥是脚本。没事,没事🫠,直接下载我打包好的exe程序就行了😈
初版程序: https://github.com/kashima19960/Wechat_Duplicatefiles_Delete/releases/download/v1/main.exe
2024-10-10更新的程序: https://github.com/kashima19960/Wechat_Duplicatefiles_Delete/releases/download/v2/main.exe
免责声明 本程序旨在帮助用户删除特定模式的文件。使用本程序可能会导致数据丢失,包括重要文件。请在使用前确保您已充分了解本程序的功能,并已做好数据备份。 作者不对使用本程序造成的任何直接或间接损失负责,包括但不限于数据丢失、硬件损坏或业务中断。使用本程序即表示您同意自行承担所有风险。 请在使用前仔细阅读并理解本程序的使用说明。如果您不同意本免责声明的任何部分,请不要使用本程序。