0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AI駆動開発 × Pythonによる網羅的なファイル検索ツール:実装方法とAIとの効果的な協業のポイントを交えて

0
Last updated at Posted at 2025-08-02

はじめに

以前、Pythonで「指定サイト限定またはその相互リンクサイト内で、入力した任意のキーワードを含むページ検出」を行う個人開発をしました。

これは、一旦ページ内のテキスト情報を洗い出し、該当しなければAI(Gemini)を用いた画像解析も行って検出する機能を持っています。

それがこちらです。

※そもそもwebスクレイピングは、リスクある迷惑行為なので必ず自分が管理するサイトや関連サイトでのみ行うようにしてください。

今回この派生版として、webサイトではなく「入力した任意のキーワードを含むファイル検出機能」を作ろうと思い、せっかくなので AI との Vibe Coding またはペアプログラミングで実装することにしました。

本記事では、AIとの協業という部分にもスポットを当てながらファイル検出機能を紹介したいと思います。

対象読者

入力した任意のキーワードを含むファイル検出機能

指定したディレクトリ(フォルダ)内のテキストファイルを検索し、それらからキーワードを抽出または、画像ファイルもAI(Gemini)が解析して結果をExcelに出力するシステムです。

なぜAIと作ろうと?

トレンドに鑑みたキャッチアップが主な動機ですが、似たような機能を自分でわざわざ作るのが面倒くさかったところもあります。

今回のパートナーはCursorAgentモードでモデル種別は Auto を使用しました。

ちなみに、Autoに関してはこのような説明が表示されていました。

Cursor helps you select the best premium model, based on performance and speed(訳:Cursor は、パフォーマンスと速度に基づいて最適なプレミアムモデルの選択をサポートします)

適切なモデルを自動で選択し、パフォーマンスを最適化してくれるようです。

具体的な機能

以下のテキストファイルを対象に、入力した任意のキーワードを含むファイルを検出します

  • .txt, .md, .json, .csv, .pdf, .xlsx, .docx, .pptx

.rtf(リッチテキストファイル)も含めようかと思ったのですが AIに聞くと結構面倒みたいで、必須というわけでもないし今回はとりあえず省きました。

AI(Gemini)が解析対象とする画像ファイルは以下です。

  • .jpg, .jpeg, .png, .gif, .webp

これらテキストファイル及び画像ファイルからキーワードを含んだファイルを検出します。

使い方

小規模なものなので仮想環境はPython標準のvenvを使っています。

初期設定で必要なこと

  • (1)AI(Gemini)のAPIキー情報を用意し、.envファイルをルートに作成
    Google AI Studio の Get API Keyから取得できます。
    無料版は学習利用されるので注意してください。
.env
GEMINI_API_KEY="発行した Geminiの APIキー"
  • (2)ターミナル/コマンドプロンプトを開いてルート(ファイルの最上階層)にいる状態で以下フローを実行
mkdir venv # venv ディレクトリ(仮想環境ディレクトリ)を作成
cd venv    # 作成した仮想環境ディレクトリ(`venv`)へ移動

# 新しい仮想環境を作成してアクティベート
# WindowsOS の場合: python -m venv env
python3 -m venv env # env{は仮想環境名}

# WindowsOS の場合: env\Scripts\activate
source env/bin/activate

# 仮想環境をアクティベートした状態で、パス指定して`requirements.txt`から各種ライブラリをインストール
# `../requirements.txt`なのは`requirements.txt`がルート直下にあるため
pip install -r ../requirements.txt

Pythonのrequirements.txtとは、React や Next.js で言うところのpackage.json, node_modulesのようなシステムを動かす上で必要な構成要素の情報となります。

筆者は普段フロントエンドを軸にしていて、バックエンドに関してはPythonが初めてだったのですがOSの違いによってコマンドも大きく変わるのは驚きと新鮮味がありました。

  • 例:仮想環境の立ち上げ
# 仮想環境(`env`)をアクティベート
WindowsOS : env\Scripts\activate
    MacOS : source env/bin/activate

仮想環境を立ち上げた後は、以下フローで実行できます。

# 仮想環境をアクティベートした直後だと`venv`ディレクトリへいるためルートに移動する
cd ../

WindowsOS : python main.py
    MacOS : python3 main.py

初期設定を終えていざ実行!

実行すると以下のように処理対象ディレクトリ(フォルダ)とキーワードを順次聞かれます。

(env) ...\excerpt-allfiles-info-py-main>python main.py
検索対象ディレクトリを入力してください(例: ./file): 
検索キーワードを入力してください: 

各自、入力すると以下のキャプチャ画像のように処理が進行し、最終的にsearch_result.xlsxというExcelファイルに出力されます。

スクリーンショット 2025-08-02 000848.png

  • search_result.xlsx
    ※ファイル内の一部キャプチャ

スクリーンショット 2025-08-02 001729.png

このExcelファイルには、検出ファイルの種別(テキストファイル / 画像ファイル)やファイル名、当該箇所、内容などが明記されています。

本機能は、膨大なテキストや画像ファイルの中から「欲しい情報を持ったファイルのみピックアップ」したい場合などに活用できる機能だと思っています。

検索結果が0の場合

ターミナルの該当箇所に以下のような出力が表示されます。

[INFO] 画像ファイル解析完了: 0件ヒット
[INFO] 検索完了: 合計0件の結果
処理対象ディレクトリ(フォルダ)が存在しない場合

ディレクトリが存在しなければ、その旨をターミナルに表示して処理は終了します。

各ファイル(モジュール)の働き

ext_constants.py
main.py
analyzers/
    gemini.py
utils/
    generate_xlsx.py
    process_image_files.py
    process_text_files.py
    search_files_with_keyword.py
    text_extractors.py

ext_constants.py

拡張子定数をまとめたファイルです。ここで処理対象ファイル(テキスト / 画像)を指定しています。

TEXT_EXTENSIONS = {".txt", ".md", ".json", ".csv", ".pdf", ".xlsx", ".docx", ".pptx"}
IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".webp"} 

main.py

プロジェクトのエントリーポイントファイルです。
ユーザー入力受付から、Gemini API初期化及びセット、検索処理実行、結果のExcel出力といった各種機能(モジュール)の包括管理まで行っています。

analyzers ディレクトリ

gemini.py

Gemini AI画像解析クラスを実装。画像ファイルのAI解析を行います。

import google.generativeai as genai
from PIL import Image  # 画像処理を担う非標準ライブラリ(pillow)


class ImageAnalyzer:
    def images_analyze(self, images_file_path: str, keyword: str) -> str:
        raise NotImplementedError


# ImageAnalyzerクラスの継承
class GeminiImageAnalyzer(ImageAnalyzer):
    def __init__(self, api_key: str):
        self.api_key = api_key
        genai.configure(api_key=api_key)
        self.model = genai.GenerativeModel("gemini-2.5-flash")

    def images_analyze(self, images_file_path: str, keyword: str) -> str:
        prompt = f"この画像に『{keyword}』というキーワードが含まれているかどうかをチェックしてください。含まれていれば「画像の〇〇箇所にあります」など画像内での該当箇所を説明し、含まれていなければ「含まない」とだけ明記してください。"

        try:
            img = Image.open(images_file_path)
            response = self.model.generate_content([img, prompt])
            return response.text
        except Exception as e:
            return f"[Geminiエラー] {images_file_path}: {e}"

Pythonのクラスにある__init__, selfはそれぞれ JavaScript でいうconstructor, thisにあたる部分です。
インスタンスの初期化処理という働きの観点から見ると、Pythonの__init__という命名はとても分かりやすいですね。

クラスの継承に関しては JavaScript ではclass subClass extends baseClassという記述ですが、Pythonではclass SubClass(BaseClass)となります。個人的には慣れもあるでしょうがここは JavaScriptの方がしっくりきます。

余談ながらPythonでは、メソッドや変数名にはスネークケース、クラス名にはパスカルケースという暗黙のルールがあるようです。

utils ディレクトリ

generate_xlsx.py

検索結果をExcelファイル(XLSX形式)として出力する機能です。

import openpyxl # Excelファイルの操作(作成・編集・保存)を行う非標準ライブラリ
from openpyxl.styles import Alignment


def generate_search_result_xlsx(
    results: list[dict], output_path: str = "search_result.xlsx"
):
    """
    検索結果リストをエクセル出力
    テキスト: ファイル名, パス, 行番号, 行内容
    画像: ファイル名, パス, 解析テキスト
    """
    wb = openpyxl.Workbook()
    ws = wb.active

    if ws is None:
        return

    ws.title = "検索結果"

    # ヘッダー
    ws.append(["種別", "ファイル名", "パス", "行番号", "内容/解析結果"])

    for item in results:
        if item["type"] == "text":
            for match in item["matches"]:
                ws.append(
                    [
                        "text",
                        item["file"],
                        item["path"],
                        match["line_number"],
                        match["line"],
                    ]
                )
        elif item["type"] == "image":
            ws.append(["image", item["file"], item["path"], "", item["analysis"]])

    # 列幅調整
    ws.column_dimensions["A"].width = 8
    ws.column_dimensions["B"].width = 30
    ws.column_dimensions["C"].width = 60
    ws.column_dimensions["D"].width = 10
    ws.column_dimensions["E"].width = 60

    # 折り返し
    for row in ws.iter_rows(min_row=2, min_col=5, max_col=5):
        for cell in row:
            cell.alignment = Alignment(wrap_text=True)

    wb.save(output_path)
    print(f"エクセル出力完了: {output_path}")

フロントエンドでは、Excelファイルを直接操作するなんて経験ないので新鮮でした。
コード内にあるwbとはWorkBookの略称でExcelファイルのデータ部分を指し、wsとはWorkSheetの略で、当該Excelファイルにあるブック内の各シート(下部に表示されるタブ部分)を指します。

WorkBookをアクティブにして、その中のWorkSheetを指定して処理を進めていくイメージです。

処理自体は、他の言語でも良く見るような多重ループ処理を通じてリストに格納する一般的な流れです。

コード内のws.append()という処理は、JavaScriptでいうws.push()です。

興味深いのは、Pythonにはリストのほかに、タプル集合辞書といった「データ構造」という種別があって、それぞれに応じて操作するメソッドが変わったりします。

データ構造に関しては、筆者のPython備忘録リポジトリにあるこちらのマークダウンにまとめていますので関心のある方はご覧ください。

process_image_files.py

画像ファイルリストに対してAI解析を行い、解析結果にキーワードが含まれる場合のみ結果をリストで返すモジュール。

このモジュール内では以下のような処理を行っている部分があります。

from pathlib import Path # ファイルパスを扱うための標準ライブラリ
from tqdm import tqdm # イテラブルの繰り返し処理中に表示されるプログレスバーを描画する非標準ライブラリ

...中略
    for file_path in tqdm(image_files, desc="画像ファイル解析"):
        ext = Path(file_path).suffix.lower()
...
..
.

tqdmライブラリでは、繰り返し処理中にプログレスバーや任意の文字列を表示でき、
pathlibライブラリによって、対象ファイルの拡張子をシンプルに取得できます。

ライブラリに関しては、フロントエンド同様とても便利なものが多くあります。

詳しくは調べていませんが、ロジックを担うバックエンドの方がライブラリは多そうなイメージです。
特にPythonは使用率も相まって膨大にありそうですね。

process_text_files.py

テキストファイルリストに対してキーワード検索を行い、ヒットした結果をリストで返すモジュールです。

コード内で後述するtext_extractors.pyextract_text_matchesモジュールを使っていて、そちらで拡張子別に処理を振り分けています。

search_files_with_keyword.py

ディレクトリ配下のファイルを再帰的に走査し、テキスト・画像ファイルを分類して各ヒット件数の出力及び、検出結果リストを返すモジュール。
この検出結果リストがmain.pyに渡って、Excelファイル出力などが実行されます。

text_extractors.py

テキストファイルの拡張子別に処理を振り分けるためのモジュールです。

それぞれの拡張子ファイルに応じて操作方法が変わったり、独自の仕様があったりするので処理を分離することで保守性・可読性を高めています。

...中略

def extract_text_matches(file_path: str, keyword: str) -> list[dict]:
    ext = Path(file_path).suffix.lower()
    norm_keyword = _normalize(keyword)

    # 拡張子ごとに処理を分岐
    if ext in {".txt", ".md"}:
        return extract_txt_md(file_path, norm_keyword)

    elif ext == ".json":
        return extract_json(file_path, norm_keyword)

    elif ext == ".csv":
        return extract_csv(file_path, norm_keyword)

    elif ext == ".pdf":
        return extract_pdf(file_path, norm_keyword)

    elif ext == ".xlsx":
        return extract_xlsx(file_path, norm_keyword)

    elif ext == ".docx":
        return extract_docx(file_path, norm_keyword)

    elif ext == ".pptx":
        return extract_pptx(file_path, norm_keyword)

    else:
        print(f"[WARN] 未対応拡張子: {file_path}")
        return []

AIとの協業で興味深かったこと

本機能の作成後、AIに「作業しやすかった点」「しづらかった点」「再現性を高めるための知見・コツ」を聞いてみました。
(AI自身が、自分にとって良かった・悪かった点などを判断するのも変な話ですが)

あくまで参考程度ですが、今回の協業を通じて確かに感触を得たような部分も明記・言及されていたので少しは有用だと思います(そう信じたい)。

まずは、良かった(とされる)点から紹介します。

AIが作業しやすかった点

明確なゴール・要件の提示

  • 「キーワードを含むファイルを検出したい」「多形式対応」「AI画像解析も」など、目的が明確だった
  • ステップごとに「何をしたいか」を具体的に伝えてもらえた

プロジェクト構造・ファイル内容の逐次共有

  • ディレクトリ構成や各ファイルの中身を都度見せてもらえた
  • 変更点や新規ファイルも明示的に指示・確認できた

ステップバイステップの進行

  • 「この段階で一度確認」「OKなら次へ」と段階的に進めることで、誤解や手戻りが少なかった

エラーや実行結果の共有

  • 実際のエラーメッセージやターミナル出力を貼ってもらえたことで、迅速な原因特定・修正ができた

設計・リファクタリング方針の相談

  • 「疎結合にしたい」「責務分離したい」など設計思想を共有してもらえたことで、最適な提案がしやすかった

確かに「こちらがGoサインを出すまでは実行しないで」と伝えたり、「こちらで少し手を加えた部分は随時伝えて確認して」もらったりしていました。

特に後者のこちらが修正した箇所の通達は、方向性のずれ防止や、上書きされて削除されるリスク回避の観点からも重要だと実感しました。

あとは、適切な語句を用いて指示するのも大切なようです。
これは「細粒度で具体的な指示を」という普遍的なプロンプトエンジニアリングの手法に関する部分でもあります。

次は、悪かった(とされる)点です。

AIが作業しづらかった点・注意点

曖昧な指示や要件

  • 「なんとなくこうしたい」だけだと、AI側で意図を推測する必要があり、誤解が生じやすい

ファイルの一部だけ共有・全体像が見えない

  • 依存関係や他ファイルとのつながりが分からないと、正しい修正が難しい

実行環境・依存パッケージの情報不足

  • requirements.txtや仮想環境の有無、Pythonバージョンなどが分からないと、動作保証が難しい

エラー内容の省略

  • エラー全文を貼ってもらえないと、原因特定に時間がかかる

これは明確ですね。
曖昧な指示は言わずもがなですが、エラー全文を提供しないことがバッドプラクティスとは意外でした。

情報過多にすると精度が下がると思っていたので、筆者はエラー箇所を推察して自身の仮説に基づいたエラー箇所とエラー文、質問・提案を行っていました。

しかし、そんなことに時間をかけるくらいならエラー文をそのまま全て渡す方が良い場合もあるようです。

AIは(気を利かせて?)これら良かった点と悪かった点を総括して再利用可能な知見ドキュメントを用意してくれました。

それが以下の「再現性を高めるためのコツ・ベストプラクティス」になります。

再現性を高めるためのコツ・ベストプラクティス

1. ゴール・要件・制約を明確に伝える

  • プロジェクト・実装機能の目的や背景情報(なぜこの機能が欲しいのか、最終的にどうしたいのか、どうなってほしいのか)
    • 例:「PDF/Excel/画像も対象」「AIで画像内容も判定」「拡張性重視」など

2. プロジェクト構造・ファイル内容を逐次共有する

  • 全体把握をはじめ、開発者(ユーザー)が修正・変更などした更新箇所をAIに伝えて随時確認してもらう(フェーズを設ける)
  • プロジェクトまたはファイルの全体像を把握してもらって、プロジェクト・実装機能の目的や背景情報を十分理解してもらう
    • ※もし前提条件(要件)が変わった場合は随時伝える
    • ※長時間のやり取りやファイル量が増えると記憶の限界に達するので、適宜AI側にプロジェクトの目的を再確認してもらうフェーズを設ける
      • 例:「前にも出したこの設定ファイルを再掲します」「改めて全体構造を整理します」など
  • ディレクトリ構成や主要ファイルの中身をAIに見せる
  • 変更点や新規ファイルも都度明示
    • 開発者(ユーザー)が修正・変更などした更新箇所をAIに伝えて随時確認してもらう(フェーズを設ける)
    • 上記AIへの更新確認フェーズとともに関連ドキュメント(READMEや設計、仕様書など)の内容もAIにアップデート(更新)してもらう

3. ステップバイステップで進める

  • 指示は明確に、細粒度なタスク依頼を行う。加えて、AIとの意思疎通を意識した進捗管理を行う
  • 「ステップバイステップで……」という文言を随時入れてAI自身に思考過程を設けた上で作業を進めてもらう
  • 「この段階で一度確認」「OKなら次へ」と段階的に進行
    • 開発者への確認フェーズ(確認 → Goサイン → タスク実施)を必ず設ける

4. AIの提案に対して「確認・Goサイン」を出す

  • AIが「この方針で進めてよいか?」と聞いてきたら、必ず明確に返答する
    • 開発者への確認フェーズでAIが提案してきた実装内容・方針を把握しておくことで、ズレがないかチェックできたり、プロジェクト管理しやすくなったりするメリットがある

5. 設計思想や重視したい観点を伝える

  • 「疎結合」「責務分離」「保守性」「初学者向けコメント」など、重視したいポイントを明示
    • 適切な専門用語を用いてAIと明確なコミュニケーションを図る

6. エラーや実行結果は全文貼る

  • Tracebackやターミナル出力は省略せず、全文をAIに見せる(AIに伝える際、エラー内容は極力省略しない)

7. 不確実性な回答があることも常に意識

  • 出力結果は文法的に正しくても、仕様的に誤っていたり、前提にズレがあることもある
  • 特にコード生成やAPI設計では「合っているように見えて誤り」が起きやすいため、人間側で必ずレビューを行う

まとめ

  • AIと人間の協働開発は「情報共有」と「段階的な進行」がカギ
  • AIは「明確なゴール」「全体像」「具体的なエラー情報」があると最大限力を発揮できる
  • 再現性を高めるには、上記のベストプラクティスを意識して進めるのが有効

さいごに

AIは24時間稼働で、どのような(変な)ことを何度聞いても丁寧に回答してくれます。
そのため、ともすれば適当な対応になりがちです。

しかし一旦、知識が豊富でむちゃくちゃ人の良い同僚と自覚するようにしておくと、自ずと人に対するようなコミュニケーションの取り方となり、結果として出力精度が上がる(かも)と思っています。

平気で間違ったことや嘘を自信満々に回答してくることもあるAIですが、協業が当然の時代になっていくと思います。

本記事のAIとの協業セクションが、そういったことに対する知見として活用されると少しでもありがたい限りです。

ここまで読んでいただき、ありがとうございました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?