Python實現(xiàn)智能磁盤監(jiān)控并自動清理垃圾文件
"C 盤滿了"——這句話大概是 IT 運(yùn)維中聽到最多的求助了。與其每次手動清理,不如讓 Python 自動搞定。
磁盤空間問題的真相
企業(yè)環(huán)境中磁盤空間不足的常見原因:
- 日志文件膨脹:IIS 日志、應(yīng)用日志、數(shù)據(jù)庫日志沒人清理
- 臨時文件堆積:Windows 臨時目錄、瀏覽器緩存、安裝殘留
- 重復(fù)文件:同一個文件被復(fù)制了 N 份到不同位置
- 大文件遺忘:測試時拷入的 ISO、數(shù)據(jù)庫備份、錄像文件
- 回收站和縮略圖緩存:用戶刪了文件但沒清回收站
手動排查?右鍵→屬性→看看哪個盤滿了→再一層層找……效率極低。
基礎(chǔ)篇:磁盤空間全景掃描
import os
import shutil
from pathlib import Path
from collections import defaultdict
def get_disk_usage():
"""獲取所有磁盤分區(qū)的使用情況"""
print("=" * 60)
print(f"{'盤符':<8}{'總?cè)萘?:<12}{'已用':<12}{'可用':<12}{'使用率':<8}")
print("=" * 60)
results = []
for partition in os.popen("wmic logicaldisk get caption,size,freespace").readlines()[1:]:
parts = partition.strip().split()
if len(parts) < 3:
continue
drive = parts[0]
total = int(parts[1]) # 字節(jié)
free = int(parts[2])
used = total - free
usage_percent = (used / total * 100) if total > 0 else 0
# 格式化顯示
total_gb = total / 1024**3
used_gb = used / 1024**3
free_gb = free / 1024**3
# 根據(jù)使用率選擇顯示標(biāo)記
if usage_percent >= 90:
icon = "??"
elif usage_percent >= 80:
icon = "??"
else:
icon = "??"
print(f"{icon} {drive:<6}{total_gb:>8.1f} GB{used_gb:>8.1f} GB"
f"{free_gb:>8.1f} GB{usage_percent:>6.1f}%")
results.append({
"drive": drive,
"total_gb": total_gb,
"used_gb": used_gb,
"free_gb": free_gb,
"usage_percent": usage_percent,
})
return results
# 使用
# disks = get_disk_usage()
實戰(zhàn)一:大文件掃描器
快速找出占用空間最多的文件:
def scan_large_files(
directory,
min_size_mb=100,
top_n=20,
exclude_dirs=None
):
"""
掃描大文件
directory: 掃描目錄
min_size_mb: 最小文件大?。∕B)
top_n: 返回前 N 個
exclude_dirs: 排除的目錄列表
"""
if exclude_dirs is None:
exclude_dirs = [
r"C:\Windows", r"C:\Program Files",
r"C:\Program Files (x86)",
]
large_files = []
min_size_bytes = min_size_mb * 1024 * 1024
print(f"正在掃描 {directory}(最小 {min_size_mb} MB)...")
for root, dirs, files in os.walk(directory):
# 排除目錄
dirs[:] = [
d for d in dirs
if not any(
os.path.normpath(os.path.join(root, d)).startswith(
os.path.normpath(excl)
)
for excl in exclude_dirs
)
]
for file in files:
try:
filepath = os.path.join(root, file)
size = os.path.getsize(filepath)
if size >= min_size_bytes:
mtime = os.path.getmtime(filepath)
large_files.append({
"path": filepath,
"size_mb": round(size / 1024**2, 1),
"size_bytes": size,
"modified": mtime,
})
except (PermissionError, OSError):
continue
# 按大小排序
large_files.sort(key=lambda x: x["size_bytes"], reverse=True)
print(f"\n發(fā)現(xiàn) {len(large_files)} 個大文件,TOP {top_n}:\n")
for f in large_files[:top_n]:
from datetime import datetime
mod_time = datetime.fromtimestamp(f["modified"]).strftime("%Y-%m-%d")
print(f" {f['size_mb']:>8.1f} MB {mod_time} {f['path']}")
return large_files
# 使用:掃描 D 盤超過 500MB 的文件
# large_files = scan_large_files(r"D:\", min_size_mb=500, top_n=30)
實戰(zhàn)二:目錄大小分析
找出哪些目錄占用了最多空間:
def get_directory_sizes(
root_dir,
max_depth=2,
top_n=15,
exclude_dirs=None
):
"""
分析目錄大小
max_depth: 遞歸深度
"""
if exclude_dirs is None:
exclude_dirs = []
dir_sizes = defaultdict(int)
print(f"正在分析 {root_dir} 的目錄大小...")
for root, dirs, files in os.walk(root_dir):
# 計算當(dāng)前深度
rel_path = os.path.relpath(root, root_dir)
depth = rel_path.count(os.sep) + (1 if rel_path != "." else 0)
if depth > max_depth:
dirs.clear() # 不再深入
continue
# 排除目錄
dirs[:] = [
d for d in dirs
if not any(os.path.join(root, d).startswith(excl) for excl in exclude_dirs)
]
# 統(tǒng)計當(dāng)前目錄下文件大小
current_size = 0
for file in files:
try:
current_size += os.path.getsize(os.path.join(root, file))
except (PermissionError, OSError):
pass
# 累加到各級父目錄
if depth <= max_depth:
dir_sizes[root] += current_size
# 排序
sorted_dirs = sorted(dir_sizes.items(), key=lambda x: x[1], reverse=True)
print(f"\nTOP {top_n} 目錄:\n")
total = sum(s for _, s in sorted_dirs)
for path, size in sorted_dirs[:top_n]:
size_gb = size / 1024**3
percent = (size / total * 100) if total > 0 else 0
# 進(jìn)度條可視化
bar_len = 30
filled = int(bar_len * percent / 100)
bar = "█" * filled + "?" * (bar_len - filled)
print(f" {size_gb:>8.2f} GB {bar} {percent:>5.1f}%")
print(f" {path}")
return sorted_dirs
# 使用
# dirs = get_directory_sizes(r"C:\Users", max_depth=2, exclude_dirs=[r"C:\Users\Default"])
實戰(zhàn)三:重復(fù)文件檢測
清理重復(fù)文件可以釋放大量空間:
import hashlib
def get_file_hash(filepath, block_size=65536):
"""計算文件的 MD5 哈希"""
hasher = hashlib.md5()
try:
with open(filepath, "rb") as f:
for block in iter(lambda: f.read(block_size), b""):
hasher.update(block)
return hasher.hexdigest()
except (PermissionError, OSError):
return None
def find_duplicate_files(directory, min_size_kb=100):
"""
找出重復(fù)文件(基于文件大小+MD5雙重校驗)
min_size_kb: 只檢測大于此大小的文件
"""
# 第一步:按文件大小分組
print("第一步:按文件大小分組...")
size_groups = defaultdict(list)
for root, dirs, files in os.walk(directory):
for file in files:
try:
filepath = os.path.join(root, file)
size = os.path.getsize(filepath)
if size >= min_size_kb * 1024:
size_groups[size].append(filepath)
except (PermissionError, OSError):
continue
# 只保留有多個文件的組
potential_duplicates = {
size: files for size, files in size_groups.items()
if len(files) >= 2
}
print(f"發(fā)現(xiàn) {len(potential_duplicates)} 組大小相同的文件")
# 第二步:對大小相同的文件計算 MD5
print("第二步:計算 MD5 確認(rèn)重復(fù)...")
duplicates = {} # hash -> [filepath, ...]
total_to_check = sum(len(files) for files in potential_duplicates.values())
checked = 0
for size, files in potential_duplicates.items():
for filepath in files:
file_hash = get_file_hash(filepath)
checked += 1
if file_hash:
if file_hash not in duplicates:
duplicates[file_hash] = []
duplicates[file_hash].append(filepath)
# 只保留真正重復(fù)的
true_duplicates = {
h: files for h, files in duplicates.items()
if len(files) >= 2
}
# 統(tǒng)計可釋放空間
total_wasted = 0
for h, files in true_duplicates.items():
# 保留一個,其余都是浪費
size = os.path.getsize(files[0])
total_wasted += size * (len(files) - 1)
print(f"\n發(fā)現(xiàn) {len(true_duplicates)} 組重復(fù)文件")
print(f"可釋放空間: {total_wasted / 1024**3:.2f} GB\n")
# 按浪費空間排序
dup_sorted = sorted(
true_duplicates.items(),
key=lambda x: os.path.getsize(x[1][0]) * (len(x[1]) - 1),
reverse=True
)
for h, files in dup_sorted[:10]:
size = os.path.getsize(files[0])
wasted = size * (len(files) - 1)
print(f" 重復(fù) {len(files)} 份, 各 {size/1024**2:.1f} MB, "
f"浪費 {wasted/1024**2:.1f} MB")
for f in files:
print(f" {f}")
print()
return true_duplicates
def cleanup_duplicates(duplicate_map, dry_run=True):
"""
清理重復(fù)文件(保留每組中的第一個)
dry_run: True=只報告不刪除
"""
total_freed = 0
for h, files in duplicate_map.items():
# 保留第一個,刪除其余
keep = files[0]
remove = files[1:]
for filepath in remove:
if dry_run:
size = os.path.getsize(filepath)
total_freed += size
print(f" [模擬刪除] {filepath} ({size/1024**2:.1f} MB)")
else:
try:
size = os.path.getsize(filepath)
os.remove(filepath)
total_freed += size
print(f" [已刪除] {filepath} ({size/1024**2:.1f} MB)")
except Exception as e:
print(f" [失敗] {filepath}: {e}")
action = "可釋放" if dry_run else "已釋放"
print(f"\n{action}空間: {total_freed / 1024**3:.2f} GB")
if dry_run:
print("\n?? 這是模擬運(yùn)行,沒有實際刪除文件。")
print("確認(rèn)無誤后,設(shè)置 dry_run=False 重新運(yùn)行。")
return total_freed
# 使用
# dups = find_duplicate_files(r"D:\Data", min_size_kb=1024)
# cleanup_duplicates(dups, dry_run=True) # 先模擬
# cleanup_duplicates(dups, dry_run=False) # 確認(rèn)后刪除
實戰(zhàn)四:智能清理策略
不同類型的垃圾文件需要不同的清理策略:
from datetime import datetime, timedelta
import re
class DiskCleaner:
"""智能磁盤清理器"""
# 常見可清理的目錄和模式
CLEANUP_TARGETS = {
"Windows 臨時文件": r"C:\Windows\Temp",
"用戶臨時文件": os.path.join(os.environ.get("TEMP", ""), ""),
"縮略圖緩存": os.path.join(
os.environ.get("LOCALAPPDATA", ""), "Microsoft", "Windows", "Explorer"
),
"Windows 更新緩存": r"C:\Windows\SoftwareDistribution\Download",
"Prefetch 緩存": r"C:\Windows\Prefetch",
}
# 可清理的文件模式
CLEANUP_PATTERNS = {
"日志文件": [r"\.log$"],
"臨時文件": [r"\.tmp$", r"\.temp$", r"~\$"],
"緩存文件": [r"\.cache$", r"\.bak$"],
"縮略圖數(shù)據(jù)庫": [r"thumbcache_.*\.db$"],
}
# 可清理的目錄模式
CLEANUP_DIR_PATTERNS = {
"node_modules": r"node_modules$",
"__pycache__": r"__pycache__$",
".pytest_cache": r"\.pytest_cache$",
"pip cache": r"pip[\\/]cache$",
}
def __init__(self, dry_run=True):
self.dry_run = dry_run
self.report = defaultdict(lambda: {"count": 0, "size": 0})
def clean_temp_directories(self):
"""清理系統(tǒng)臨時目錄"""
print("\n=== 清理臨時目錄 ===")
for name, path in self.CLEANUP_TARGETS.items():
if not os.path.exists(path):
continue
total_size = 0
file_count = 0
for item in os.listdir(path):
item_path = os.path.join(path, item)
try:
if os.path.isfile(item_path):
size = os.path.getsize(item_path)
total_size += size
file_count += 1
if not self.dry_run:
os.remove(item_path)
elif os.path.isdir(item_path):
size = sum(
os.path.getsize(os.path.join(dp, f))
for dp, dn, fn in os.walk(item_path)
for f in fn
)
total_size += size
file_count += 1
if not self.dry_run:
shutil.rmtree(item_path)
except (PermissionError, OSError):
pass
self.report[name]["count"] = file_count
self.report[name]["size"] = total_size
action = "可清理" if self.dry_run else "已清理"
if file_count > 0:
print(f" {name}: {action} {file_count} 項 "
f"({total_size/1024/1024:.1f} MB)")
def clean_old_logs(self, days=30, log_dirs=None):
"""清理 N 天前的舊日志"""
print(f"\n=== 清理 {days} 天前的日志 ===")
if log_dirs is None:
log_dirs = [
r"C:\inetpub\logs",
r"C:\Windows\System32\winevt\Logs",
]
cutoff = time.time() - (days * 86400)
total_cleaned = 0
for log_dir in log_dirs:
if not os.path.exists(log_dir):
continue
for root, dirs, files in os.walk(log_dir):
for file in files:
if not file.endswith(".log"):
continue
filepath = os.path.join(root, file)
try:
mtime = os.path.getmtime(filepath)
if mtime < cutoff:
size = os.path.getsize(filepath)
total_cleaned += size
if self.dry_run:
from datetime import datetime as dt
mod_date = dt.fromtimestamp(mtime).strftime("%Y-%m-%d")
print(f" [模擬] {filepath} ({size/1024/1024:.1f} MB, {mod_date})")
else:
os.remove(filepath)
except (PermissionError, OSError):
pass
action = "可釋放" if self.dry_run else "已釋放"
print(f"\n日志清理: {action} {total_cleaned/1024/1024:.1f} MB")
def clean_recycle_bin(self):
"""清空回收站"""
import ctypes
print("\n=== 清空回收站 ===")
if self.dry_run:
# 估算回收站大小
print(" [模擬] 清空回收站")
return
try:
# Windows API 清空回收站
result = ctypes.windll.shell32.SHEmptyRecycleBinW(None, None, 7)
if result == 0:
print(" 回收站已清空")
else:
print(f" 清空回收站返回: {result}")
except Exception as e:
print(f" 清空回收站失敗: {e}")
def run_full_cleanup(self):
"""執(zhí)行完整清理"""
mode = "模擬" if self.dry_run else "實際"
print(f"{'=' * 60}")
print(f" 磁盤清理 ({mode}模式)")
print(f"{'=' * 60}")
self.clean_temp_directories()
self.clean_old_logs(days=30)
self.clean_recycle_bin()
# 匯總
total_size = sum(item["size"] for item in self.report.values())
print(f"\n{'=' * 60}")
action = "總計可釋放" if self.dry_run else "總計已釋放"
print(f" {action}: {total_size / 1024 / 1024:.1f} MB")
print(f"{'=' * 60}")
if self.dry_run:
print("\n確認(rèn)無誤后,使用 DiskCleaner(dry_run=False) 執(zhí)行實際清理")
return total_size
import time
# 使用示例
# cleaner = DiskCleaner(dry_run=True) # 先模擬
# cleaner.run_full_cleanup()
# cleaner = DiskCleaner(dry_run=False) # 確認(rèn)后執(zhí)行
# cleaner.run_full_cleanup()
實戰(zhàn)五:磁盤空間監(jiān)控與告警
import json
from datetime import datetime, timedelta
from pathlib import Path
class DiskMonitor:
"""磁盤空間持續(xù)監(jiān)控器"""
def __init__(self, data_file="disk_history.json"):
self.data_file = Path(data_file)
self.history = self._load_history()
self.threshold_warning = 80 # 警告閾值 %
self.threshold_critical = 95 # 嚴(yán)重閾值 %
def _load_history(self):
"""加載歷史數(shù)據(jù)"""
if self.data_file.exists():
with open(self.data_file, "r") as f:
return json.load(f)
return []
def _save_history(self):
"""保存歷史數(shù)據(jù)"""
with open(self.data_file, "w") as f:
json.dump(self.history, f, ensure_ascii=False, indent=2)
def record_snapshot(self):
"""記錄當(dāng)前磁盤快照"""
snapshot = {
"timestamp": datetime.now().isoformat(),
"drives": [],
}
for partition in os.popen(
"wmic logicaldisk get caption,size,freespace"
).readlines()[1:]:
parts = partition.strip().split()
if len(parts) < 3:
continue
total = int(parts[1])
free = int(parts[2])
usage = ((total - free) / total * 100) if total > 0 else 0
snapshot["drives"].append({
"drive": parts[0],
"total_gb": round(total / 1024**3, 2),
"free_gb": round(free / 1024**3, 2),
"usage_percent": round(usage, 1),
})
self.history.append(snapshot)
# 只保留最近 90 天的數(shù)據(jù)
cutoff = (datetime.now() - timedelta(days=90)).isoformat()
self.history = [
h for h in self.history
if h["timestamp"] >= cutoff
]
self._save_history()
return snapshot
def check_alerts(self, snapshot=None):
"""檢查是否需要告警"""
if not snapshot:
snapshot = self.record_snapshot()
alerts = []
for drive in snapshot["drives"]:
usage = drive["usage_percent"]
free_gb = drive["free_gb"]
if usage >= self.threshold_critical:
alerts.append({
"severity": "CRITICAL",
"drive": drive["drive"],
"message": (
f"{drive['drive']} 磁盤空間嚴(yán)重不足!"
f"已用 {usage}%,僅剩 {free_gb:.1f} GB"
),
})
elif usage >= self.threshold_warning:
alerts.append({
"severity": "WARNING",
"drive": drive["drive"],
"message": (
f"{drive['drive']} 磁盤空間不足警告:"
f"已用 {usage}%,剩余 {free_gb:.1f} GB"
),
})
return alerts
def analyze_trend(self, drive_letter, days=30):
"""分析磁盤使用趨勢"""
cutoff = (datetime.now() - timedelta(days=days)).isoformat()
recent = [
h for h in self.history
if h["timestamp"] >= cutoff
]
# 提取目標(biāo)盤符的數(shù)據(jù)點
data_points = []
for h in recent:
for d in h["drives"]:
if d["drive"] == drive_letter:
data_points.append({
"time": h["timestamp"],
"usage": d["usage_percent"],
"free_gb": d["free_gb"],
})
break
if len(data_points) < 2:
print(f"數(shù)據(jù)不足(需要至少 2 個數(shù)據(jù)點,當(dāng)前 {len(data_points)} 個)")
return None
# 計算趨勢
first = data_points[0]
last = data_points[-1]
usage_change = last["usage"] - first["usage"]
free_change = last["free_gb"] - first["free_gb"]
days_span = len(data_points) # 近似天數(shù)
daily_usage_increase = usage_change / days_span if days_span > 0 else 0
daily_free_decrease = abs(free_change) / days_span if days_span > 0 else 0
# 預(yù)計何時達(dá)到閾值
if daily_usage_increase > 0:
days_to_warning = (self.threshold_warning - last["usage"]) / daily_usage_increase
days_to_critical = (self.threshold_critical - last["usage"]) / daily_usage_increase
else:
days_to_warning = float('inf')
days_to_critical = float('inf')
report = {
"drive": drive_letter,
"period": f"最近 {days} 天",
"data_points": len(data_points),
"current_usage": f"{last['usage']}%",
"current_free": f"{last['free_gb']} GB",
"usage_change": f"+{usage_change:.1f}%" if usage_change > 0 else f"{usage_change:.1f}%",
"free_change": f"{free_change:.1f} GB",
"daily_increase": f"+{daily_usage_increase:.2f}%" if daily_usage_increase > 0 else "穩(wěn)定",
"days_to_warning": f"{days_to_warning:.0f} 天" if days_to_warning < 365 else "無風(fēng)險",
"days_to_critical": f"{days_to_critical:.0f} 天" if days_to_critical < 365 else "無風(fēng)險",
}
print(f"\n=== {drive_letter} 磁盤使用趨勢 ({report['period']}) ===")
print(f" 當(dāng)前使用率: {report['current_usage']}, 剩余: {report['current_free']}")
print(f" 變化: 使用率 {report['usage_change']}, 空間 {report['free_change']}")
print(f" 日均增長: {report['daily_increase']}")
print(f" 預(yù)計觸發(fā)警告: {report['days_to_warning']}")
print(f" 預(yù)計磁盤滿: {report['days_to_critical']}")
return report
# 使用示例
# monitor = DiskMonitor()
# monitor.record_snapshot() # 記錄快照
# alerts = monitor.check_alerts()
# monitor.analyze_trend("C:", days=30)
小結(jié)
| 需求 | 方案 | 復(fù)雜度 |
|---|---|---|
| 磁盤全景 | wmic + shutil | 入門 |
| 大文件掃描 | os.walk + 大小排序 | 入門 |
| 目錄大小分析 | os.walk + 深度控制 | 中等 |
| 重復(fù)文件檢測 | 文件大小 + MD5 雙重校驗 | 中等 |
| 自動清理 | 按類型策略清理 | 中等 |
| 趨勢分析 | 歷史數(shù)據(jù) + 線性回歸 | 進(jìn)階 |
| 告警通知 | 閾值檢查 + 郵件/微信推送 | 進(jìn)階 |
磁盤空間管理是運(yùn)維的基本功。建好監(jiān)控,設(shè)好告警,自動清理,把"C 盤滿了"這種低級問題消滅在萌芽狀態(tài)。
以上就是Python實現(xiàn)智能磁盤監(jiān)控并自動清理垃圾文件的詳細(xì)內(nèi)容,更多關(guān)于Python磁盤監(jiān)控的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python使用虛擬環(huán)境(安裝下載更新卸載)命令
這篇文章主要為大家介紹了Python使用虛擬環(huán)境(安裝下載更新卸載)命令,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
numpy中hstack vstack stack concatenate函數(shù)示例詳解
這篇文章主要為大家介紹了numpy中hstack vstack stack concatenate函數(shù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
python獲取圖片中兩個點的具體坐標(biāo)并將圖無損裁剪下來
這篇文章主要為大家詳細(xì)介紹了python如何獲取圖片中兩個點的具體坐標(biāo)并將圖無損裁剪下來,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2025-11-11
pandas中的DataFrame按指定順序輸出所有列的方法
下面小編就為大家分享一篇pandas中的DataFrame按指定順序輸出所有列的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-04-04
Python+Pygame實戰(zhàn)之實現(xiàn)小蜜蜂歷險記游戲
這篇文章主要為大家介紹了如何利用Python中的Pygame模塊實現(xiàn)小蜜蜂歷險記游戲,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Python游戲開發(fā)有一定幫助,需要的可以參考一下2022-08-08

