Pythonで重複ファイル・重複写真を見つけて整理する方法

python
自動化・詳細手順

Pythonで重複ファイル・重複写真を見つけて整理する方法

ファイルの中身を比較して、重複だけを安全に見つけ出します

プログラミング未経験OK 所要時間の目安:20分程度

この記事は、「Pythonでできる日常の自動化6選」で紹介した⑤重複ファイル・重複写真の整理の詳しい作り方をまとめた記事です。

スマホからパソコンに何度も写真を取り込むうちに、同じ写真が「IMG_001.jpg」「IMG_001(1).jpg」のように別名で何個も残っていませんか?ファイル名ではなく中身そのものを比較することで、本当に同じファイルだけを正確に見つけ出すことができます。今回もPython標準の機能だけを使うので、追加のライブラリのインストールは不要です。

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5

用意するもの

今回もPythonに標準で付属している機能だけを使います。pip installで追加のライブラリを用意する必要はありません。

  • Python本体
  • コードを書くためのエディタ(VS Codeなど)
INFO

Pythonのインストール、コードを書くためのエディタ(VS Code)の準備、コードの書き方・実行方法がまだの方は、先に「Pythonを始める前に:インストールとエディタの準備」を済ませてから、この続きに進んでください。

注意

このあとのコードは、実際にパソコン上のファイルを移動します。まずはテスト用に新しく作った空のフォルダにコピーしたファイルで試してから、普段使っているフォルダに適用することを強くおすすめします。

重複検出の仕組みを知る

ファイル名や更新日時で比較すると、「中身は同じだけど名前が違うファイル」を見逃してしまいます。そこでこの記事では、ファイルの中身そのものから計算される「ハッシュ値」という指紋のような値を使って比較します。中身が1バイトでも違えば、まったく違うハッシュ値になります。逆に言えば、ハッシュ値が同じ2つのファイルは、中身が完全に同じということになります。

MEMO

ハッシュ値の計算には、Python標準のhashlibというライブラリを使います。追加のインストールは不要です。

ファイルのハッシュ値を計算してみる

まずは1つのファイルで、ハッシュ値を計算する基本の書き方を試してみましょう。

import hashlib

def get_file_hash(file_path):
    hasher = hashlib.md5()
    with open(file_path, "rb") as f:
        while chunk := f.read(8192):
            hasher.update(chunk)
    return hasher.hexdigest()

print(get_file_hash("photo1.jpg"))

実行すると、ファイルの中身から計算された英数字の文字列(ハッシュ値)が表示されます。同じ中身のファイルであれば、ファイル名が違っても、どのパソコンで実行しても、必ず同じハッシュ値になります。

MEMO

ファイルを少しずつ(8192バイトずつ)読み込んでいるのは、動画などの大きなファイルでも一度にメモリを使いすぎないようにするための工夫です。

フォルダ内の重複ファイルを見つける

先ほどの関数を使って、フォルダ内のすべてのファイルのハッシュ値を計算し、同じハッシュ値を持つファイルをグループ化します。

from pathlib import Path
import hashlib

def get_file_hash(file_path):
    hasher = hashlib.md5()
    with open(file_path, "rb") as f:
        while chunk := f.read(8192):
            hasher.update(chunk)
    return hasher.hexdigest()

target_dir = Path.home() / "Desktop" / "test_photos"

hash_to_files = {}

for file in target_dir.iterdir():
    if file.is_file():
        file_hash = get_file_hash(file)
        hash_to_files.setdefault(file_hash, []).append(file)

# 同じハッシュ値のファイルが2つ以上あるものだけを「重複」として取り出す
duplicates = {h: files for h, files in hash_to_files.items() if len(files) > 1}

for file_hash, files in duplicates.items():
    print(f"重複グループ({len(files)}件):")
    for f in files:
        print(f"  {f.name}")

実行すると、中身が同じファイルどうしがグループごとに一覧表示されます。hash_to_files.setdefault(file_hash, []).append(file)は、「同じハッシュ値のリストがまだなければ新しく作り、あればそこに追加する」という書き方です。

実際に動かすと、こうなります

フォルダの中身。6つのJPEGファイルが並び、すべて23KB・同じ日付になっている
まず、フォルダを見ただけの状態です。6つのファイルが並んでいますが、サイズも日付も全部同じ。名前も「IMG_2024.jpg」「写真1.jpg」「写真1のコピー.jpg」とばらばらで、どれが同じ中身なのか目では分かりません。
ターミナルの出力。重複グループ(3件)としてIMG_2024.jpg・写真1.jpg・写真1のコピー.jpgが、重複グループ(2件)として写真3 (2).jpg・写真3.jpgが表示されている
上のコードを実行した結果です。名前が全然違う「IMG_2024.jpg」と「写真1.jpg」と「写真1のコピー.jpg」が同じ中身だと分かりました。名前ではなく中身で判定しているので、こうなります。

重複候補を安全にまとめて確認する

重複が見つかっても、いきなり削除するのはおすすめしません。まずは、各グループの1つ目だけを「残すファイル」として、残りを専用のフォルダへ移動し、目で見て確認できるようにしましょう。

import shutil

review_dir = target_dir / "重複候補"
review_dir.mkdir(exist_ok=True)

for file_hash, files in duplicates.items():
    keep = files[0]
    remove_candidates = files[1:]
    print(f"残す: {keep.name}")
    for f in remove_candidates:
        shutil.move(str(f), str(review_dir / f.name))
        print(f"  移動: {f.name}")

print("重複候補フォルダへの移動が完了しました")

実行すると、「重複候補」という新しいフォルダが作られ、各グループの2つ目以降のファイルがそこに移動します。元のフォルダには重複のないファイルだけが残るので、あとは「重複候補」フォルダの中身を確認し、問題なければまとめて削除する、という流れが安全です。

POINT

shutil.moveの代わりにshutil.copyを使えば、元のファイルを残したまま「重複候補」フォルダにコピーするだけの、さらに安全な確認もできます。

この記事のコードを動かして確かめました

この記事は2026年8月に書いたものです。載せている3つのコードを、上から順にそのまま動かし直しました。結果と、動かして初めて分かったことを書きます。

確かめた日2026年9月23日
環境Windows 11 / Python 3.14.7 / 追加インストールなし
試したもの7個(中身が同じ3個、別の同じ2個、1バイトだけ違う1個、テキスト1個)
結果3つとも、コードの書き換えなしで動きました

見つかった重複

重複グループ(3件):
  IMG_0421 (1).jpg
  IMG_0421.jpg
  コピー ~ IMG_0421.jpg
重複グループ(2件):
  umi.jpg
  umi_backup.jpg
POINT

1バイト違うだけで、ハッシュは まったく別のものになります。試しに、中身が 10,240バイト中 1バイトだけ違うファイルを混ぜておきました。サイズも拡張子も同じです。

c3cd26e0…  IMG_0421.jpg ほか2個(中身が同じ)
84299e60…  yama.jpg      ← 1バイトだけ違うもの

まったく似ていない値になっています。「サイズが同じだから同じ」ではないことが、ここで分かります。だから、見た目や容量ではなく中身を見るこのやり方が要るわけです。

「重複候補」フォルダへ移したあと

残す: IMG_0421 (1).jpg
  移動: IMG_0421.jpg
  移動: コピー ~ IMG_0421.jpg
残す: umi.jpg
  移動: umi_backup.jpg
注意

残る1つは選べていません。フォルダの並び順で決まります。上の結果を見てください。残ったのは IMG_0421 (1).jpg のほうで、元の名前である IMG_0421.jpg が移動しています。おそらく逆を期待されると思います。
理由は、コードの keep = files[0] が「最初に見つかった1つ」を残しているからです。iterdir() が返す順はファイル名順で、半角スペース(0x20)はドット(0x2E)より小さいため、IMG_0421 (1).jpg が先に来ます。

MEMO

名前が短いほうを残したいときは、1行 足すだけです。重複グループを取り出したあとに、名前の長さで並べ替えます。

files = sorted(files, key=lambda p: (len(p.name), p.name))
keep = files[0]

コピーは「(1)」「コピー ~」が付いて名前が長くなることが多いので、これだけでたいてい元のほうが残ります。ただし、これも確実ではありません。移動先は削除ではなく「重複候補」フォルダなので、中を目で見てから捨てる、という手順は変えないでください。

POINT

この記事の日付は2026年8月11日ですが、コードの動作は2026年9月23日に確認し直しています。動かなくなっていることに気づかれたら、お問い合わせから教えてください。

うまくいかないときのトラブルシューティング

見た目は同じでも少し編集した写真は重複として検出されますか?▼
検出されません。ハッシュ値は中身が1バイトでも違うと別の値になるため、リサイズや再保存によってファイルの中身が変わった写真は、別のファイルとして扱われます。
ファイル数が多いと時間がかかりますか?▼
ファイルの合計サイズが大きいほど時間がかかりますが、一般的な写真や書類であれば、数千枚程度でも数分以内に終わることがほとんどです。

まとめ

Python標準のhashlibを使って、ファイルの中身からハッシュ値を計算し、重複ファイルを正確に見つける方法を紹介しました。見つけた重複はいきなり削除せず、まず専用フォルダに移動して確認する、という安全な流れで整理してみてください。

他の自動化アイデアも見てみる

家計簿の自動集計やExcel作業の自動化など、Pythonでできる生活の自動化アイデアを6つ紹介しています。

6テーマ一覧を見る

コメント

タイトルとURLをコピーしました