У меня есть куча файлов в разных папках, их оочень много, мне нужно удалить дубликаты файлов. Как мы выяснили в определенных папках дубликатами можно считать те файлы у которых совпадают только названия и размеры. Мне передали код, который полностью проверяет файлы вместе с хешами, но он долго работает, появился вопрос как его оптимизировать что бы он не учитывал хеши. Так же я пробовала написать код сама - т.к. я человек в программировании новый, то код конечно не очень красивый, с ним тоже есть момент, часто выкидывает ошибку что такого файла нет, путь которого передается для удаления, я вставила обработку ошибок, код стал полностью выполняться, но удаляются далеко не все дубликаты, примерно раз 9 меньше чем есть на самом деле.
-
Помоги оптимизировать первый код, который мне передали, что бы он не сравнивал хеши файлов
-
Помогите разобраться с тем кодом что написала я
Код с учётом сравнивания хешей файлов:
from collections import defaultdict
import hashlib
import os
import sys
def chunk_reader(fobj, chunk_size=1024):
"""Generator that reads a file in chunks of bytes"""
while True:
chunk = fobj.read(chunk_size)
if not chunk:
return
yield chunk
def get_hash(filename, first_chunk_only=False, hash=hashlib.sha1):
hashobj = hash()
file_object = open(filename, 'rb')
if first_chunk_only:
hashobj.update(file_object.read(1024))
else:
for chunk in chunk_reader(file_object):
hashobj.update(chunk)
hashed = hashobj.digest()
file_object.close()
return hashed
def check_for_duplicates(paths, hash=hashlib.sha1):
hashes_by_size = defaultdict(list) # dict of size_in_bytes: [full_path_to_file1, full_path_to_file2, ]
hashes_on_1k = defaultdict(list) # dict of (hash1k, size_in_bytes): [full_path_to_file1, full_path_to_file2, ]
hashes_full = {} # dict of full_file_hash: full_path_to_file_string
for path in paths:
log_filename = "log.txt"
#f = open(os.path.join(path, log_filename), 'w')
#f.close()
for dirpath, dirnames, filenames in os.walk(path):
# get all files that have the same size - they are the collision candidates
for filename in filenames:
full_path = os.path.join(dirpath, filename)
try:
# if the target is a symlink (soft one), this will
# dereference it - change the value to the actual target file
full_path = os.path.realpath(full_path)
file_size = os.path.getsize(full_path)
hashes_by_size[file_size].append(full_path)
except (OSError,):
# not accessible (permissions, etc) - pass on
continue
# For all files with the same file size, get their hash on the 1st 1024 bytes only
for size_in_bytes, files in hashes_by_size.items():
if len(files) < 2:
continue # this file size is unique, no need to spend CPU cycles on it
for filename in files:
try:
small_hash = get_hash(filename, first_chunk_only=True)
# the key is the hash on the first 1024 bytes plus the size - to
# avoid collisions on equal hashes in the first part of the file
# credits to @Futal for the optimization
hashes_on_1k[(small_hash, size_in_bytes)].append(filename)
except (OSError,):
# the file access might've changed till the exec point got here
continue
# For all files with the hash on the 1st 1024 bytes, get their hash on the full file - collisions will be duplicates
for __, files_list in hashes_on_1k.items():
f = open(os.path.join(path, log_filename), 'a')
if len(files_list) < 2:
continue # this hash of fist 1k file bytes is unique, no need to spend cpy cycles on it
for filename in files_list:
try:
full_hash = get_hash(filename, first_chunk_only=False)
duplicate = hashes_full.get(full_hash)
if duplicate:
os.remove(filename)
f.write('duplicated file "' + filename +'" was removed \n')
f.write('unique file - "' + duplicate +'"\n')
#print(duplicate)
#print(filename)
#print("Duplicate found: {} and {}".format(filename, duplicate))
else:
hashes_full[full_hash] = filename
except (OSError,):
# the file access might've changed till the exec point got here
continue
f.write('Checked for duplicate files \n')
f.close()
if __name__ == "__main__":
if sys.argv[1:]:
check_for_duplicates(sys.argv[1:])
else:
print("Please pass the paths to check as parameters to the script")
Да, я видела этот код в интернет, коллеги его просто скопировали и сами его не писали.
Вот мой код:
from collections import defaultdict
import hashlib
import os
import sys
# При запуске код спрашивает путь к папке в котрой делать проверку
route = input('Укажите полный путь к папке, в которой будет проходить проверка, затем нажмите Enter: ')
# На случай лишних пробелом удаляем все пробельные символы в начале и в конце строки
route = route.strip() # в начале строки
route = route.rstrip() # в конце строки
# Теперь полный путь записан в переменной route
# переходим в заданную директорию
os.chdir(route)
# Вытаскивам пути ко всем директориям
# ************************************************************************************************************************
from pathlib import Path
path = Path(route)
folder = [] # абсолютные пути вложенных папок первого уровня
for x in path.iterdir():
if x.is_dir():
folder.append(x)
# Вытащим пути файлов из вложенных директорий первого уровня
# ************************************************************************************************************************
file_paths = [] # абсолютные пути ко всем файлам из вложенных папок первого уровня
for t in range(0, len(folder)):
ested_folder = Path(str(folder[t]))
for d in ested_folder.iterdir():
file_paths.append(d)
# Сделаем список только из названий файлов с расширением
# ************************************************************************************************************************
file_names = [] # названия всех файлов из всех вложенных папок первого уровня с расширениями
for t in range(0, len(file_paths)):
file_names.append(Path(str(file_paths[t])).name)
# Сделаем список размеров файлов
# ************************************************************************************************************************
file_sizes = [] # размеры всех файлов из всех вложенных папок первого уровня в байтах
for t in range(0, len(file_paths)):
file_sizes.append(os.path.getsize(str(file_paths[t])))
# Сделаем список последних редактирований файлов в секундах
# ************************************************************************************************************************
file_modification_time = [] # время в секундах последних редактирований всех файлов
for t in range(0, len(file_paths)):
file_modification_time.append(os.path.getmtime(str(file_paths[t])))
# Берём первый элемент из списка названий файлов и ищем есть ли такое же название,
# если есть, то вытаскиваем индексы, сравниваем размеры, если одинаковые, то
# смотрим даты редактирований,
# записываем в файл путь старшего удаляемого файла и оставленного файла,
# затем по индексу старшего файла, заменяем на индексы соответствующие элементы
# если совпадения по имени нет, переходим к следующему элементу списка
#
# СПИСКИ:
#
# folder - абсолютные пути вложенных папок первого уровня
# file_paths - абсолютные пути ко всем файлам из вложенных папок первого уровня
# file_names - названия всех файлов из всех вложенных папок первого уровня с расширениями
# file_sizes - размеры всех файлов из всех вложенных папок первого уровня в байтах
# file_modification_time - время в секундах последних редактирований всех файлов
# comparison_value - значение для сравнения
# duplicates_names - список для работы с дубликатами
duplicates_names = []
duplicates_file_txt = open('duplicates.txt', 'w+') # создаём файл, куда запишем пути дубликатов
for q in range(0, len(file_names)):
# берём по-этапно каждое название файлов
# записываем в переменную имя файла с которым будем работать
comparison_value = file_names[q]
for w in range(q + 1, len(file_names)):
# перебираем все следующие имена файлов пока не найдем такое же название
if file_names[w] == comparison_value:
# ищем такое же название
# если есть дублированное название, то записываем индексы дубликатов в список
duplicates_names.append(w)
duplicates_names.append(q)
#print(duplicates_names) # int - каждый элемент
if file_sizes[duplicates_names[-2]] == file_sizes[duplicates_names[-1]]:
# сравниваем размеры файлов
if file_modification_time[duplicates_names[-2]] > file_modification_time[duplicates_names[-1]]:
# сравниваем последнее время редактирования
# удаляем - duplicates_names[-1]
# оставляем - duplicates_names[-2]
# записываем в файл какие файлы удаляем, а какие оставляем
duplicates_file_txt.write('Удаляемый файл: ' + str(file_paths[duplicates_names[-1]]) + '\n')
duplicates_file_txt.write('Оставленный файл: ' + str(file_paths[duplicates_names[-2]]) + '\n')
try:
os.remove(file_paths[duplicates_names[-1]]) # удаляем
except FileNotFoundError:
continue
# вместо пути удалённого файла записываем его индекс
file_names[duplicates_names[-1]] = duplicates_names[-1]
else:
# удаляем - duplicates_names[-2]
# оставляем - duplicates_names[-1]
duplicates_file_txt.write('Удаляемый файл: ' + str(file_paths[duplicates_names[-2]]) + '\n')
duplicates_file_txt.write('Оставленный файл: ' + str(file_paths[duplicates_names[-1]]) + '\n')
try:
os.remove(file_paths[duplicates_names[-1]]) # удаляем
except FileNotFoundError:
continue
file_names[duplicates_names[-2]] = duplicates_names[-2]
duplicates_file_txt.close()