Downloads整理・次世代版構想:PDF解析とClaude Vision統合

はじめに

現行のDownloadsフォルダ自動整理システムは、ファイル名ベースの判定で3段階の信頼度スコアリングを実現しています。しかし、ファイル名だけでは判定しきれないケースが多く、次世代版ではPDF解析とClaude Visionを統合した内容ベースの判定を目指します。

現行システムの課題

ファイル名依存の限界

現在の判定ロジック:

def classify_file(filename):
    score = 0
    category = None

    # キーワードマッチング
    if '請求書' in filename or 'invoice' in filename.lower():
        score = 90
        category = '03_書類/見積・請求'
    elif '契約' in filename or 'contract' in filename.lower():
        score = 85
        category = '03_書類/契約書'
    # ...

    return category, score

問題点:
– ファイル名が「Screenshot 2026-03-29.png」だと判定不能
– 「document.pdf」のような汎用名では分類できない
– 内容を見ずに判断するため誤分類リスクあり

中信頼度の多発

実際の運用結果(2026-03-29 04:00):

  • 高信頼度: 0件
  • 中信頼度: 4件(全て移動実行)
  • 低信頼度: 0件

中信頼度ばかりになる = ファイル名だけでは確信を持てない証拠。

次世代版の設計

Phase 1: PDF内容解析

PyPDF2やpdfplumberでテキスト抽出し、内容から判定します。

import pdfplumber

def extract_pdf_content(filepath):
    try:
        with pdfplumber.open(filepath) as pdf:
            text = ""
            for page in pdf.pages[:3]:  # 最初の3ページ
                text += page.extract_text() or ""
            return text
    except Exception as e:
        return None

def classify_pdf_by_content(filepath):
    content = extract_pdf_content(filepath)

    if not content:
        return classify_file(os.path.basename(filepath))  # フォールバック

    content_lower = content.lower()

    # 請求書判定
    if '請求書' in content or '合計金額' in content:
        if '株式会社' in content or '御中' in content:
            return ('03_書類/見積・請求', 95)

    # 契約書判定
    if '契約書' in content or '甲乙' in content:
        return ('03_書類/契約書', 90)

    # プロジェクト資料
    if '提案書' in content or 'proposal' in content_lower:
        return ('01_進行中プロジェクト', 80)

    return (None, 50)  # 不明

Phase 2: 画像解析(Claude Vision)

スクリーンショットやデザイン画像を Claude Vision で判定します。

def analyze_image_with_claude(filepath):
    """
    Claude Visionで画像内容を解析
    """
    import anthropic
    import base64

    with open(filepath, 'rb') as f:
        image_data = base64.b64encode(f.read()).decode('utf-8')

    client = anthropic.Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY'))

    message = client.messages.create(
        model="claude-3-sonnet-20240229",
        max_tokens=300,
        messages=[{
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/png",
                        "data": image_data
                    }
                },
                {
                    "type": "text",
                    "text": "この画像の内容を分類してください。以下のカテゴリから選んでください:\n- デザイン/UI(Webデザイン、UI設計)\n- 開発関連(エラー画面、コード)\n- 書類(請求書、契約書)\n- プレゼン資料\n- その他\n\nカテゴリ名のみ回答してください。"
                }
            ]
        }]
    )

    category_text = message.content[0].text.strip()

    # カテゴリマッピング
    mapping = {
        'デザイン/UI': ('04_メディア/デザイン', 85),
        '開発関連': ('02_開発/スクリーンショット', 80),
        '書類': ('03_書類/その他', 75),
        'プレゼン資料': ('01_進行中プロジェクト', 80)
    }

    return mapping.get(category_text, (None, 50))

Phase 3: ハイブリッド判定

ファイル名・PDF内容・画像解析を統合します。

def classify_file_advanced(filepath):
    filename = os.path.basename(filepath)
    ext = os.path.splitext(filename)[1].lower()

    # Step 1: ファイル名ベース(高速)
    category_name, score_name = classify_file(filename)

    # Step 2: 内容解析(PDF)
    if ext == '.pdf':
        category_content, score_content = classify_pdf_by_content(filepath)

        # スコアの高い方を採用
        if score_content > score_name:
            return category_content, score_content

    # Step 3: 画像解析(重い処理なので最後)
    if ext in ['.png', '.jpg', '.jpeg'] and score_name < 70:
        category_image, score_image = analyze_image_with_claude(filepath)

        if score_image > score_name:
            return category_image, score_image

    return category_name, score_name

インテリジェントなフォルダ作成

サブフォルダの自動生成

内容に基づいて詳細なサブフォルダを作成します。

def generate_subfolder(category, content_info):
    """
    カテゴリと内容情報からサブフォルダパスを生成

    例:
    - 03_書類/見積・請求/2026年3月/
    - 01_進行中プロジェクト/クロスリンク/求人システム/
    """
    base_folder = category

    # 日付ベース(書類系)
    if '書類' in category:
        year_month = datetime.now().strftime('%Y年%m月')
        return f"{base_folder}/{year_month}"

    # プロジェクト名ベース(進行中プロジェクト)
    if '進行中プロジェクト' in category and 'project_name' in content_info:
        project = content_info['project_name']
        return f"{base_folder}/{project}"

    return base_folder

ファイル名の自動リネーム

内容から意味のある名前を生成します。

def generate_smart_filename(original_name, content_info):
    """
    内容から賢いファイル名を生成

    例:
    - Screenshot.png → クロスリンク様_請求書_2026-03.png
    - document.pdf → ワークスキル実践講座_提案書_v2.pdf
    """
    ext = os.path.splitext(original_name)[1]

    parts = []

    if 'company' in content_info:
        parts.append(content_info['company'])

    if 'document_type' in content_info:
        parts.append(content_info['document_type'])

    if 'date' in content_info:
        parts.append(content_info['date'])

    if parts:
        return "_".join(parts) + ext

    return original_name

パフォーマンス最適化

段階的処理

コストの低い処理から順に実行します。

  1. ファイル名チェック(0.001秒)
  2. PDF解析(0.5-2秒)
  3. Claude Vision(3-5秒 + API料金)

キャッシング

同じファイルを再解析しないよう、結果をキャッシュします。

import hashlib
import json

def get_file_hash(filepath):
    with open(filepath, 'rb') as f:
        return hashlib.md5(f.read()).hexdigest()

def load_cache(cache_file='file_analysis_cache.json'):
    if os.path.exists(cache_file):
        with open(cache_file) as f:
            return json.load(f)
    return {}

def classify_with_cache(filepath):
    cache = load_cache()
    file_hash = get_file_hash(filepath)

    if file_hash in cache:
        return cache[file_hash]

    result = classify_file_advanced(filepath)

    cache[file_hash] = result
    save_cache(cache)

    return result

まとめ

ファイル名だけでなく内容を解析することで、分類精度を大幅に向上できます。次世代版では:

  • PDF内容解析で書類の自動分類
  • Claude Visionで画像の文脈理解
  • インテリジェントなサブフォルダ生成
  • 内容ベースのファイル名リネーム

これらを段階的に実装していきます。

関連記事:
– ダウンロードフォルダ自動整理の構築
– OpenClaw Cronジョブ活用術
– Python仮想環境共有テクニック