Python

【Pythonで解決!】Officeファイルをフォルダ構造そのままPDFに一括変換!エラーログ付きで安心自動化

Officeファイルをフォルダ構造そのままPDFに一括変換!エラーログ付きで安心自動化
Buy Me A CoffeeBuy Me a Coffee at ko-fi.com

「大量のExcelやPowerPointファイルをPDFに変換する作業、フォルダごとに整理し直すのが面倒…」
「手作業での変換は時間がかかるし、ミスも心配…」

そんなお悩みを抱えるあなたへ!この記事では、Microsoft Officeドキュメント(Excel、PowerPoint)を、元のフォルダ構造を保ったままPDFファイルに一括変換するPythonスクリプトをご紹介します。さらに、エラーが発生した場合にはログも出力されるので、問題点の把握も簡単。日々の定型業務を自動化し、貴重な時間を有効活用しましょう!

こんな方におすすめ!

  • 大量のOfficeファイルを定期的にPDFに変換する必要がある方
  • 変換後のファイルを元のフォルダ構成で管理したい方
  • 手作業による変換ミスや時間のロスを減らしたい方
  • プログラミング(Python)で業務効率化に挑戦してみたい方

スクリプトの主な機能

  • 指定フォルダ内の一括処理: 入力フォルダを指定するだけで、サブフォルダ内のファイルも全て処理対象に。
  • Excel・PowerPoint対応: .xlsx, .xls, .xlsm (Excel) や .pptx, .ppt, .pptm (PowerPoint) ファイルをPDFに変換します。
  • フォルダ構造の完全維持: 入力フォルダのサブフォルダ構造を、そのまま出力先フォルダに再現します。
  • GUIによる簡単操作: 入力フォルダと出力フォルダは、使い慣れたダイアログで選択できます。
  • エラーログ出力: 変換中にエラーが発生した場合、詳細なログファイル(conversion_log.log)が生成されるため、原因究明が容易です。
  • 一時ファイルのスキップ: Officeが自動生成する一時ファイル(~$で始まるファイル)は処理対象外とし、エラーを防ぎます。

準備するもの

  • Windows環境: このスクリプトはWindows OS上で動作します。
  • Microsoft Office: ExcelおよびPowerPointがインストールされている必要があります。
  • Pythonのインストール: Pythonが未導入の場合は、Python公式サイトからダウンロードしてインストールしてください。
  • pywin32ライブラリ: PythonからOfficeアプリケーションを操作するために必要です。コマンドプロンプトやターミナルで以下のコマンドを実行してインストールします。
pip install pywin32

【完全版】Python PDF自動変換スクリプト

以下が、OfficeファイルをPDFに一括変換するPythonスクリプトです。

import os
import tkinter as tk
from tkinter import filedialog
import win32com.client
import logging
import pythoncom # pywin32ライブラリの一部

# 定数
XL_TYPE_PDF = 0  # Excel: PDF形式
PP_SAVE_AS_PDF = 32  # PowerPoint: PDF形式

# ログ設定
log_file_name = 'conversion_log.log'
try:
    script_dir = os.path.dirname(os.path.abspath(__file__))
    log_file_path = os.path.join(script_dir, log_file_name)
except NameError:
    script_dir = os.getcwd()
    log_file_path = os.path.join(script_dir, log_file_name)

logging.basicConfig(
    filename=log_file_path,
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',
    encoding='utf-8'
)

def select_folder(title_msg):
    """フォルダ選択ダイアログを表示し、選択されたフォルダパスを返す"""
    root = tk.Tk()
    root.withdraw()
    root.attributes('-topmost', True)
    folder_path = filedialog.askdirectory(title=title_msg)
    root.destroy()
    return folder_path

def convert_excel_to_pdf(excel_app_instance, input_file_path, output_file_path):
    """ExcelファイルをPDFに変換する"""
    workbook = None
    try:
        logging.info(f"Excelファイル変換開始: {input_file_path}")
        workbook = excel_app_instance.Workbooks.Open(input_file_path, ReadOnly=True)
        workbook.ExportAsFixedFormat(Type=XL_TYPE_PDF, Filename=output_file_path)
        logging.info(f"Excelファイル変換成功: {output_file_path}")
        return True
    except Exception as e:
        logging.error(f"Excelファイル変換エラー ({input_file_path}): {e}")
        return False
    finally:
        if workbook:
            workbook.Close(SaveChanges=False)

def convert_ppt_to_pdf(ppt_app_instance, input_file_path, output_file_path):
    """PowerPointファイルをPDFに変換する"""
    presentation = None
    try:
        logging.info(f"PowerPointファイル変換開始: {input_file_path}")
        presentation = ppt_app_instance.Presentations.Open(input_file_path, ReadOnly=True, WithWindow=False)
        presentation.SaveAs(output_file_path, FileFormat=PP_SAVE_AS_PDF)
        logging.info(f"PowerPointファイル変換成功: {output_file_path}")
        return True
    except Exception as e:
        logging.error(f"PowerPointファイル変換エラー ({input_file_path}): {e}")
        return False
    finally:
        if presentation:
            presentation.Close()

def main_process():
    logging.info("処理開始")
    com_initialized_here = False

    try:
        pythoncom.CoInitializeEx(pythoncom.COINIT_APARTMENTTHREADED)
        com_initialized_here = True
    except AttributeError:
        try:
            pythoncom.CoInitialize()
            com_initialized_here = True
        except pythoncom.com_error as e_coinit:
            if e_coinit.hresult == -2147417850: # RPC_E_CHANGED_MODE
                logging.info("COMは既に別のモードで初期化されています。続行します。")
            else:
                logging.error(f"COMライブラリの初期化に失敗しました (CoInitialize): {e_coinit}")
                print(f"COMライブラリの初期化に失敗しました: {e_coinit}。処理を終了します。")
                return
    except pythoncom.com_error as e:
        if e.hresult == -2147417850: # RPC_E_CHANGED_MODE
            logging.info("COMは既に別のモードで初期化されています。続行します。")
        else:
            logging.error(f"COMライブラリの初期化に失敗しました (CoInitializeEx): {e}")
            print(f"COMライブラリの初期化に失敗しました: {e}。処理を終了します。")
            return

    input_dir = select_folder("変換元のOfficeファイルが含まれるフォルダを選択してください")
    if not input_dir:
        logging.warning("入力フォルダが選択されませんでした。処理を終了します。")
        print("入力フォルダが選択されませんでした。処理を終了します。")
        if com_initialized_here:
             pythoncom.CoUninitialize()
        return

    output_dir = select_folder("PDFファイルの出力先フォルダを選択してください")
    if not output_dir:
        logging.warning("出力フォルダが選択されませんでした。処理を終了します。")
        print("出力フォルダが選択されませんでした。処理を終了します。")
        if com_initialized_here:
            pythoncom.CoUninitialize()
        return

    logging.info(f"入力フォルダ: {input_dir}")
    logging.info(f"出力フォルダ: {output_dir}")
    print(f"入力フォルダ: {input_dir}")
    print(f"出力フォルダ: {output_dir}")
    print(f"ログファイルは次の場所に生成されます: {log_file_path}")

    excel_app = None
    ppt_app = None

    try:
        try:
            excel_app = win32com.client.Dispatch("Excel.Application")
            excel_app.Visible = False
            excel_app.DisplayAlerts = False
        except Exception as e_excel_dispatch:
            logging.error(f"Excelアプリケーションの起動に失敗しました: {e_excel_dispatch}")
            excel_app = None

        try:
            ppt_app = win32com.client.Dispatch("Powerpoint.Application")
        except Exception as e_ppt_dispatch:
            logging.error(f"PowerPointアプリケーションの起動に失敗しました: {e_ppt_dispatch}")
            ppt_app = None

        if not excel_app and not ppt_app:
            logging.error("ExcelおよびPowerPointアプリケーションの起動に両方失敗しました。処理を終了します。")
            print("Officeアプリケーションの起動に失敗したため、処理を実行できません。")
            return

        converted_files = 0
        failed_files = 0

        for current_root, _, files_in_current_root in os.walk(input_dir):
            files_to_process = [f for f in files_in_current_root if not f.startswith('~$')]

            for file_item in files_to_process:
                full_input_file_path = os.path.join(current_root, file_item)
                file_name_lower = file_item.lower()

                relative_path_from_input = os.path.relpath(current_root, input_dir)
                
                if relative_path_from_input == '.':
                    output_sub_directory_path = output_dir
                else:
                    output_sub_directory_path = os.path.join(output_dir, relative_path_from_input)

                output_file_name_base = os.path.splitext(file_item)[0]
                final_output_pdf_path = os.path.join(output_sub_directory_path, output_file_name_base + ".pdf")

                if not os.path.exists(output_sub_directory_path):
                    try:
                        os.makedirs(output_sub_directory_path)
                        logging.info(f"出力サブフォルダ作成: {output_sub_directory_path}")
                    except OSError as e_makedirs:
                        logging.error(f"出力サブフォルダ作成失敗 ({output_sub_directory_path}): {e_makedirs}")
                        failed_files += 1
                        continue

                if file_name_lower.endswith(('.xlsx', '.xls', '.xlsm')):
                    if excel_app:
                        if convert_excel_to_pdf(excel_app, full_input_file_path, final_output_pdf_path):
                            converted_files += 1
                        else:
                            failed_files += 1
                    else:
                        logging.warning(f"Excelアプリケーションが利用できないため、スキップ: {full_input_file_path}")
                        failed_files +=1

                elif file_name_lower.endswith(('.pptx', '.ppt', '.pptm')):
                    if ppt_app:
                        if convert_ppt_to_pdf(ppt_app, full_input_file_path, final_output_pdf_path):
                            converted_files += 1
                        else:
                            failed_files += 1
                    else:
                        logging.warning(f"PowerPointアプリケーションが利用できないため、スキップ: {full_input_file_path}")
                        failed_files += 1

        logging.info(f"変換成功: {converted_files} ファイル, 変換失敗: {failed_files} ファイル")
        print(f"処理完了。変換成功: {converted_files} ファイル, 変換失敗: {failed_files} ファイル")
        if failed_files > 0:
            print(f"詳細はログファイル ({log_file_path}) を確認してください。")

    except Exception as e_main:
        logging.critical(f"予期せぬエラーが発生しました: {e_main}", exc_info=True)
        print(f"予期せぬエラーが発生しました: {e_main}")
    finally:
        if excel_app:
            try:
                excel_app.Quit()
            except Exception as e_excel_quit:
                logging.error(f"Excelアプリケーションの終了中にエラー: {e_excel_quit}")
        if ppt_app:
            try:
                ppt_app.Quit()
            except Exception as e_ppt_quit:
                logging.error(f"PowerPointアプリケーションの終了中にエラー: {e_ppt_quit}")

        if com_initialized_here:
            pythoncom.CoUninitialize()
        logging.info("処理終了")

if __name__ == "__main__":
    main_process()

スクリプトの使い方

  • 保存: 上記のコードをテキストエディタ(VSCode、メモ帳など)にコピーし、ファイル名を例えば office_to_pdf_converter.py として保存します。
  • 実行: コマンドプロンプトを開き、保存したファイルがあるディレクトリに移動して、以下のコマンドでスクリプトを実行します。
python office_to_pdf_converter.py
  • フォルダ選択:
  • 最初に「変換元のOfficeファイルが含まれるフォルダを選択してください」というダイアログが表示されるので、PDFにしたいOfficeファイルが格納されている親フォルダを選択します。
  • 次に「PDFファイルの出力先フォルダを選択してください」というダイアログが表示されるので、変換後のPDFファイルを保存したいフォルダを選択します。
  • 処理実行: 選択後、自動的に変換処理が開始されます。コンソールに進捗(入力・出力フォルダ、ファイル数など)が表示されます。
  • ログ確認: 処理完了後、スクリプトと同じ場所に conversion_log.log というログファイルが生成されます。エラーが発生した場合は、このファイルで詳細を確認できます。

スクリプトのポイント解説

このスクリプトは、いくつかのPython標準ライブラリとpywin32ライブラリを組み合わせて実現されています。

  • osモジュール: ファイルやフォルダのパスを扱ったり(os.path.join, os.path.splitext, os.path.relpath)、フォルダ内を探索したり(os.walk)、フォルダを作成したり(os.makedirs)するために使用しています。フォルダ構造を維持する上で中心的な役割を果たします。
  • tkinterモジュール: Python標準のGUIライブラリです。filedialogを使って、ユーザーが直感的にフォルダを選択できるようにしています。
  • win32com.client (pywin32の一部): WindowsのCOM (Component Object Model) を介して、ExcelやPowerPointといったOfficeアプリケーションをPythonから操作するための強力なライブラリです。ファイルの開封、PDF形式での保存、アプリケーションの終了などを行います。
  • loggingモジュール: 処理の記録やエラー発生時の情報をファイルに出力するために使用します。問題発生時の原因究明に役立ちます。
  • pythoncom (pywin32の一部): COMライブラリの初期化 (CoInitializeEx) と解放 (CoUninitialize) を行い、スクリプトがOfficeアプリケーションと正しく連携できるようにします。特に、スレッドに関連する設定 (COINIT_APARTMENTTHREADED) は、Officeオートメーションで重要です。
  • フォルダ構造の維持: os.walkで入力フォルダを再帰的に探索し、os.path.relpathで入力ルートからの相対パスを取得。これを元に出力先のサブフォルダパスを構築し、os.makedirsで必要に応じてフォルダを作成することで、元の構造を正確に再現しています。

ご利用にあたっての注意点

  • このスクリプトはWindows環境専用です。
  • Microsoft Office (Excel, PowerPoint) がPCにインストールされている必要があります。
  • パスワードで保護されたファイルや、破損しているファイルは正常に変換できない場合があります。これらのエラーはログに出力されます。
  • 処理するファイル数が多い場合や、ファイルサイズが大きい場合は、変換に時間がかかることがあります。
  • スクリプト実行中は、Officeアプリケーション(ExcelやPowerPoint)を手動で操作しないでください。予期せぬ動作の原因となる可能性があります。

まとめ|OfficeファイルのPDF変換を自動化して業務効率をアップ!

このPythonスクリプトを使えば、面倒なOfficeファイルのPDF変換作業とフォルダ整理から解放され、大幅な時間短縮とミスの削減が期待できます。手作業に追われる日々から一歩踏み出し、自動化の力で業務効率を向上させましょう!

今回のスクリプトはExcelとPowerPointに対応していますが、少し変更を加えることでWord文書(.docx, .doc)の変換に対応させることも可能です。ぜひ、ご自身の業務に合わせてカスタマイズしてみてください。

この情報が、あなたの業務改善の一助となれば幸いです。ぜひお試しいただき、ご意見や改善点などあればコメントでお知らせください!