أتمتة ترتيب Downloads بـ Python بدون حذف ملف بالغلط
هتطلع من المقال بسكربت آمن يرتب مجلد Downloads تلقائيًا، ويقلل تنظيف أسبوعي من 45 دقيقة تقريبًا إلى 6 دقائق متابعة فقط.
مستوى القارئ: مبتدئ إلى متوسط
المشكلة باختصار
اللي بيحصل فعلاً إن Downloads بيجمع كل شيء: فواتير PDF، صور WhatsApp Web، ملفات ZIP، exports من أدوات SaaS، وملفات كود مؤقتة. بعد أسبوعين بتلاقي 300 ملف، وتبدأ تنقل يدويًا. الطريقة دي بتفشل لأنها تعتمد على مزاجك، وممكن تنقل ملف مهم أو تحذف ملف لسه محتاجه.
الافتراض إنك على Windows، وعندك Python 3.10 أو أحدث، ومجلد Downloads فيه أقل من 10 آلاف ملف. لو عندك أكثر من كده، نفس الفكرة تشتغل، لكن هتحتاج تقسيم وتشغيل أهدأ.
الفكرة: انقل، لا تحذف
ركز: أول نسخة من أي أوتوميشن ملفات لازم تكون دفاعية. يعني تعمل dry-run الأول، تكتب log، وتنقل الملفات بدل ما تحذفها. مثال بسيط: ملف invoice.pdf يروح إلى Downloads/Sorted/Documents، وصورة screenshot.png تروح إلى Downloads/Sorted/Images. لو اسم الملف موجود، السكربت يضيف رقم بدل ما يستبدله.
الـ trade-off هنا واضح. هتكسب ترتيب يومي ثابت ومخاطر أقل. هتخسر شوية مرونة، لأن التصنيف مبني على الامتداد وليس على محتوى الملف. ده مناسب كبداية، وبعدها ممكن تضيف OCR أو قواعد حسب الاسم.
السكربت العملي
اعمل ملف اسمه sort_downloads.py. شغّله أول مرة بـ --dry-run. لو المخرجات منطقية، شيله في التشغيل المجدول.
from pathlib import Path
import argparse
import shutil
import logging
RULES = {
"Documents": {".pdf", ".docx", ".xlsx", ".csv", ".txt"},
"Images": {".png", ".jpg", ".jpeg", ".webp", ".gif"},
"Archives": {".zip", ".rar", ".7z", ".tar", ".gz"},
"Code": {".py", ".js", ".ts", ".json", ".sql"},
}
def category_for(path: Path) -> str:
suffix = path.suffix.lower()
for category, extensions in RULES.items():
if suffix in extensions:
return category
return "Other"
def unique_target(target: Path) -> Path:
if not target.exists():
return target
stem, suffix = target.stem, target.suffix
for i in range(1, 1000):
candidate = target.with_name(f"{stem}-{i}{suffix}")
if not candidate.exists():
return candidate
raise RuntimeError(f"Too many duplicates for {target.name}")
def sort_downloads(downloads: Path, dry_run: bool) -> None:
sorted_root = downloads / "Sorted"
log_file = sorted_root / "sort-downloads.log"
sorted_root.mkdir(exist_ok=True)
logging.basicConfig(
filename=log_file,
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
)
for item in downloads.iterdir():
if item.name == "Sorted" or item.is_dir():
continue
category = category_for(item)
target_dir = sorted_root / category
target = unique_target(target_dir / item.name)
message = f"{item.name} -> {target.relative_to(downloads)}"
print("DRY" if dry_run else "MOVE", message)
logging.info(("DRY " if dry_run else "MOVE ") + message)
if not dry_run:
target_dir.mkdir(parents=True, exist_ok=True)
shutil.move(str(item), str(target))
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--downloads", default=str(Path.home() / "Downloads"))
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args()
sort_downloads(Path(args.downloads), args.dry_run)
اختبره كده:
python .\sort_downloads.py --dry-run
python .\sort_downloads.pyتشغيله يوميًا على Windows
بدل ما تفتكر تشغله، استخدم Task Scheduler من خلال schtasks. الأمر التالي يشغله كل يوم الساعة 8 مساءً. عدّل مسار Python ومسار السكربت حسب جهازك.
schtasks /Create /SC DAILY /ST 20:00 /TN "SortDownloads" /TR "python C:\Users\ahmed\Scripts\sort_downloads.py"لو عندك 200 ملف جديد أسبوعيًا، التنظيف اليدوي ممكن ياخد 30 إلى 45 دقيقة. بعد التشغيل اليومي، غالبًا هتحتاج 5 إلى 6 دقائق أسبوعيًا تراجع مجلد Other والـ log. الرقم تقديري، لكنه قابل للقياس من وقتك الفعلي قبل وبعد.
الـ trade-offs وما يجب الانتباه له
- الاعتماد على الامتداد: سريع وبسيط، لكنه لا يعرف إن ملف PDF فاتورة أو عقد. لو عايزها تدعم فهم المحتوى، ضيف OCR أو قواعد بالاسم لاحقًا.
- النقل بدل الحذف: أكثر أمانًا، لكنه لا يوفر مساحة تخزين كبيرة. المكسب هنا تنظيم وتقليل أخطاء، مش تنظيف disk عميق.
- التشغيل اليومي: يقلل الفوضى تدريجيًا، لكنه ممكن ينقل ملف أنت لسه بتحمله لو اتشغل في وقت غلط. اختار وقتًا بعيدًا عن شغلك المعتاد.
متى لا تستخدم هذه الطريقة
لا تستخدمها على مجلد مشترك بين فريق بدون مراجعة صلاحيات وسياسة أسماء واضحة. لا تستخدمها لو عندك workflow يعتمد على بقاء الملفات في جذر Downloads. ولا تبدأ بدون --dry-run، لأن أول تشغيل هو أهم نقطة لكشف قواعد التصنيف الغلط.
مصادر اعتمد عليها المقال
- توثيق Python pathlib للتعامل مع المسارات بطريقة آمنة.
- توثيق Python shutil لاستخدام
shutil.moveفي نقل الملفات. - توثيق Microsoft schtasks /Create لتشغيل المهمة يوميًا.
الخطوة التالية
شغّل السكربت بـ --dry-run على Downloads الحقيقي، واقرأ أول 20 سطر من المخرجات. لو لقيت ملفًا واحدًا رايح لمكان غلط، عدّل RULES قبل ما تشغّل النقل الفعلي.