前言

!!!!温馨提示:假如你是小白,啥都不懂,请直接跳到文章的最后!!!

原本我是没想写这篇文章,契机是某一天我突然打开微信的存储空间占用才发现。多少?😡我寻思平常也不怎么用微信,怎么占用的空间这么离谱,居然有20个G左右,这是在微信上存了个小电影吗?

image-20240830152430948

抱着好奇心的我,打开了微信的物理存储位置,其他平台我没试过,仅在windows平台下测试过,存储位置一般能通过这样打开

屏幕截图 2024-08-30 153222

找寻到存储文件的位置,一般是子目录 FileStorage\File这个位置就是用来存储通过微信保存的各种文件,而且会按照日期排序

image-20240830153835086

随便点进去一个会发现,这个文件夹下存储了大量相同的文件,然后在原有的文件的名字后面加上(1),(2),(3),(1)(1)这种序号,也就是说这些文件都是被重复存储了

image-20240830154106033

存在的问题

通过上面的流程,可以得出一个结论,微信是存储了大量重复文件,才会占用这么大的存储空间,我在网上也找了一些解释,这个似乎跟微信的存储策略有关

据悉,这种情况出现的原因之一是微信的文件转发策略导致。例如将同一份文件转发给多个好友,每一次转发都会重新保存一份在手机上,重复文件占用了手机大量存储空间。而且过大的空间占用也会使手机出现明显的卡顿和发热等情况,影响正常使用。

也就是说,我们完全可以把这些重复的文件全部删除,但是我的微信记录已经有好几年了,要是一个个删除,是一件费时费力的方法。于是!🤓我突发奇想,既然这样的话,编写一个批量删除的程序不就行了?😗

效果展示

老规矩,我喜欢在看一系列繁琐的步骤前先看看效果,这样我才有继续看下去的欲望,相信大多数人都是这样想的😝

  1. 首先执行程序,选择是否要进行递归删除,递归删除意思就是,你选择的目录的子目录里面的重复文件也会被删除。这里我们选择n

    image-20240830161217202

  2. 选择路径

    image-20240830161448609
  3. 执行效果,按回车可以退出

image-20240830161613713

  1. 删除的文件会到回收站里面,而不是永久删除,这样就能有撤销的余地了

    image-20240830161721954

问题的解决与思路

这块的内容会涉及到一些专业知识,假如你不懂,那么直接跳过就行,使用我打包好的程序即可。

  1. 首先通过观察可以发现,微信在保存这些重复文件的特点是在文件名后加上序号,比如
1
2
3
4
字符串.doc
字符串(1).doc
字符串(2).doc
字符串(1)(1).doc

我们可以通过正则表达式去匹配这种字符串,“字符”+“序号”.”后缀名”

1
2
3
4
5
"""
.*表示匹配任意字符
(\d)匹配带有括号的序号
"""
.*\(\d\).*

这样我们就能通过程序识别这些文件

  1. 其他,有次非常关键的点是,假如你在微信勾选了这个选项,那么存储的文件会因为只读权限,导致删除失败,因此在识别到文件之后,我们还需要改变文件的权限,才能删除,这里可以使用os.chmod函数做到

    image-20240830160308823

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}")
  1. 最后我们需要解决的问题是,由于文件夹的子目录数量非常多,因此我们可能不想对每个文件夹都单独执行一遍程序,最好是能递归地自动执行,这里我们可以使用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 os
import stat
import re
import tkinter as tk
from tkinter import filedialog
import send2trash
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)

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 os
import stat
import re
from sys import argv
from sys import exit
import send2trash
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
from PyQt5 import QtCore, QtWidgets
class 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()


界面

image-20241010231832262

给小白

假如上面的内容你完全不知道是什么,不会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

免责声明

本程序旨在帮助用户删除特定模式的文件。使用本程序可能会导致数据丢失,包括重要文件。请在使用前确保您已充分了解本程序的功能,并已做好数据备份。 作者不对使用本程序造成的任何直接或间接损失负责,包括但不限于数据丢失、硬件损坏或业务中断。使用本程序即表示您同意自行承担所有风险。 请在使用前仔细阅读并理解本程序的使用说明。如果您不同意本免责声明的任何部分,请不要使用本程序。