Pythonでエクセルの画像貼り付けを自動化するツールを作ってみた

プログラミングのこと
スポンサーリンク

初めまして!Hurariと申します。

今回はPythonという言語を使った「エクセル画像貼り付けの自動化ツール」をご紹介いたします。

本記事をご覧の皆さんは、仕事や学校でエクセルを使用されることが多いと思います。

しかし、エクセルってちょっと痒いところに手が届かなかったりしますよね。

例えば、画像をセルごとに貼り付けしてマトリックスのように表示したい時など。
もしかすると、私のエクセル知識が低いだけかもしれませんが…。

そこで、面倒な作業は自動化してPCに任せよう!ということでエクセル画像貼り付けツールの作成をご紹介します。

少しでも、エクセル作業にお困りの方のお役に立てれば幸いです。

この記事におすすめの方は?

  • エクセルに画像を貼り付けする作業が面倒だと感じている方
  • 自動化ツールの作成に興味のある方
  • 会社(学校)の業務効率化をしたい方

※私自身、2021年からプログラミングの勉強を始めたため、コード記述に改善の余地があるかもしれませんが、ご容赦ください。

スポンサーリンク

エクセル画像貼り付け自動化ツールのご紹介

はじめに、今回ご紹介するツールで普段のエクセル操作がどのように自動化できるのかをご紹介します。

自動化前のエクセル作業

エクセルに画像を貼り付けよう。

  1. エクセルを開く
  2. 貼り付けたい画像のあるフォルダを開く
  3. 1つずつ画像をコピペしてセルに貼り付ける
  4. 画像サイズに合わせてセルの幅を調整する

※もしくはエクセルの「挿入」から複数画像を選択して、エクセルに挿入された複数画像を1つずつセルに貼り付け直すといった方法

画像が多いほど作業が大変だな…

自動化後のエクセル作業

自動でエクセルに画像を貼り付けよう!

  1. 自動化ツールを立ち上げる
  2. 画像を貼り付けたいエクセルを選択する
  3. 貼り付けたい画像が格納されたフォルダを選択する
  4. 画像サイズやマトリックスのサイズを指定する
  5. 貼り付けを実行する

この自動化ツールを活用することで圧倒的に時間の削減ができます!

初期設定だけで簡単に貼り付けができた!

スポンサーリンク

ツールを作成するのに必要な環境

使用言語:Python

この自動化ツールはPythonというインタープリンタ型の言語で作成しています。

インタープリンタ型って何?

プログラミング言語には2種類あり「インタープリンタ型」と「コンパイラ型」があります。

まず、コードは人が理解して書きやすいように作られているのですが、この記述したコードを機械が理解して実行する必要があります。

このソースコードを機械が理解するコード(機械語)に変換する作業が、プログラムの実行中に行われるか、あらかじめ変換しておくのかの違いが特徴になります。

コードエディタ:VSCとJupyterLabについて

ツールを作成するにあたり、コードを記述したり動作確認ができる環境が必要です。

ちなみに私は、VSC(Visual Studio Code)やJupyterLabを使ってツールを作成しました

それぞれの特徴について簡単に説明すると、VSCはアプリケーションで、JupyterLabはブラウザ上で動くコードエディターになります。

※2021年9月にJupyterLab Desktop Appがローンチされたため、アプリ上でも使用することができるようになりました

JupyterLabは対話型実行環境と呼ばれており、コードを1行ずつ簡単に動作確認することができるため、こちらでツールを作成していくのがおすすめです。

VSCについて

Visual Studio CodeはMicrosoftが開発しているWindows、Linux、macOS用のソースコードエディタである。

https://ja.wikipedia.org/wiki/Visual_Studio_Code

下記の公式サイトからダウンロードすることができます。

JupyterLabについて

JupyterLabはProject Jupyterの次世代ユーザーインターフェースである。 柔軟で強力なユーザーインターフェイスで、クラシックなJupyter Notebookの使い慣れたすべてのビルディングブロック(ノートブック、ターミナル、テキストエディター、ファイルブラウザー、豊富な出力など)を提供する。

https://ja.wikipedia.org/wiki/Project_Jupyter#JupyterLab

環境構築の方法

VSCやJupyterLabを使って、Pythonのコードを実行するにあたり、環境構築というものが必要になります。

環境構築については「KinoCode」さんのYouTubeが非常に分かりやすくオススメです。
私もpython勉強のご参考にさせていただきました。

※下記リンクのブログ内に動画があります

【Python超入門コース】03.環境構築 for Mac|プログラミングをする準備をしよう!【プログラミング初心者向け入門講座】
環境構築とは、プログラムを書いたり、実行できたりする環境を自分のコンピュータに整えることをいいます。 環境構築のために、VisualStudioCode本体、VisualStudioCodeのPython拡張機能、anacondaの3つをインストールします。
Pandas入門|02.jupyter Labの使い方|プログラムの記述や実行、表やグラフも表示できるPythonユーザーに人気のツール
Jupyter Labとはなんでしょうか? Jupyter Labは、プログラムの実行環境のことです。つまり、プログラムを実行できるツールのようなものです。つまり、大きなくくりでいうと、キノコードのPython超入門コースで使ったVSCodeと同じようなものです。

また、2021年9月に新しくローンチされたJupyterLab Desktop Appのインストール方法はこちらのサイトが分かりやすいです。

※こちらのDesktop App版を使用する場合は環境構築が不要なようです。

JupyterLab Desktop のインストールと始め方(Windows) - ガンマソフト
JupyterLab Desktop Appのインストール方法と始め方をWindowsで説明します。パソコンにインストールするだけですぐにJupyterをPythonで利用できます。もう環境構築で苦労する必要はありません。これでデータ分析の学習を簡単に始められます。

エクセル画像貼り付け自動化のコード

自動化のソースコード

ここまでにご紹介した自動化ツールのソースコードがこちら!

# ライブラリのインポート
import os
from openpyxl import Workbook
from openpyxl.drawing.image import Image as XLImage
from PIL import Image as PILImage
from natsort import natsorted
from glob import glob
import openpyxl as xl
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import Tk,StringVar,ttk,filedialog,messagebox,LEFT


# メインウィンドウの設定
root = tk.Tk()
root.title('Excel自動画像挿入ツール')
root.resizable(False, False)


# エクセルファイルの参照ボタン操作時
def excel_ref_clicked():
    fTyp = [("EXCELファイル","*.xlsx")]
    iDir = os.path.abspath(os.path.dirname('__file__'))
    file_path = filedialog.askopenfilename(filetypes = fTyp, initialdir = iDir)
    file1.set(file_path)


# 画像フォルダの参照ボタン操作時
def fig_ref_clicked():
    iDir = os.path.abspath(os.path.dirname('__file__'))
    folder_path = filedialog.askdirectory(initialdir = iDir)
    file2.set(folder_path)
    
    figs_list = get_fig_list()
    figs_name.set(tuple(figs_list))

            
# 選択した画像フォルダから格納されたファイルを抽出
def get_fig_list():
    fig_folder = file2.get()
    fig_type = combobox1_s.get()
    
    #フォルダの所在確認
    fig_folder_exist = os.path.isdir(fig_folder)
    
    if fig_folder_exist:
        figs_path = fig_folder + '/*' + fig_type
        figs_list = glob(figs_path)
    
        if not figs_list:
            # エラー表示
            dispmsg = "選択したフォルダ内に指定の拡張子ファイルが存在していません。\n\n"
            dispmsg += "再度、フォルダを選択してください。\n\n"
            dispmsg += "参照画像フォルダ:\n"
            dispmsg += str(fig_folder)
            messagebox.showinfo('画像を格納したフォルダが見つかりません', dispmsg)
            figs_list = []
    
    else:
        figs_list = []
        
    return figs_list


# 選択した画像のフォルダに新しいディレクトリを作成
def make_new_directory():
    fig_folder = os.path.abspath(file2.get())

    #フォルダの所在確認
    fig_folder_exist = os.path.isdir(fig_folder)

    if fig_folder_exist:       
        # 画像の一時保存用フォルダを作成する
        new_directory = fig_folder + "/Temp_Save_Folder"
        
        if not os.path.exists(new_directory):
            os.mkdir(new_directory)

    else:
        # エラー表示:存在していないフォルダ名の指定
        dispmsg = "選択したフォルダが存在していません。再度、フォルダを選択してください。\n\n"
        dispmsg += str(fig_folder)
        messagebox.showinfo('フォルダが見つかりません', dispmsg)
        new_directory = []
  
    return new_directory


# 実行ボタン操作時
def start_clicked():
    
    if os.path.isfile(file1.get()) & os.path.isdir(file2.get()):
        
        max_row = combobox2_s.get() # 貼り付ける行数を指定
        max_col = combobox3_s.get() # 貼り付ける列数を指定

        # エクセルのセル(2,2)を基準に挿入する
        basis_row = 2
        basis_column = 2

        row = basis_row
        column = basis_column
        
        figs_list = get_fig_list()
                
        if len(figs_list) != 0 :
            
            new_directory = make_new_directory()
            
            wb = xl.load_workbook(file1.get())   
            ws = wb.create_sheet()

            for i in natsorted(figs_list):
                
                img = PILImage.open(i)                   
                resize = int(combobox4_s.get())
                img_resize = img.resize((img.width // resize , img.height // resize), PILImage.LANCZOS)

                root, ext = os.path.splitext(i)
                basename = os.path.basename(root)

                img_resize.save(os.path.join(new_directory, basename + '_resize' + ext), quality=95) 

                xl_img = XLImage(os.path.join(new_directory, basename + '_resize' + ext))

                width = xl_img.width
                height = xl_img.height

                set_cell = ws.cell(row,column).coordinate
                cell_col = ws.cell(row,column).column_letter
                cell_row = ws.cell(row,column).row

                ws.add_image(xl_img, set_cell)

                ws.row_dimensions[cell_row].height = height * 0.8
                ws.column_dimensions[cell_col].width = width *0.15
                
                if bool_check.get() == False :
                    
                    if (row == int(max_row) + basis_row - 1) & (column == int(max_col) + basis_column - 1):
                        break
                    
                    elif column == int(max_col) + basis_column - 1:
                        row += 1
                        column = basis_column  

                    else :
                        column += 1
                
                else:
                                        
                    if (row == int(max_row) + basis_row - 1) & (column == int(max_col) + basis_column - 1):
                        break

                    elif row == int(max_row) + basis_row - 1:
                        column += 1
                        row = basis_row  

                    else :
                        row += 1

            wb.save(file1.get())

            # メッセージ表示
            print('Insert Pictures Success!')
            dispmsg = "Success!\n\n指定のExcelファイルに新しいシートを追加し画像を挿入しました。\n\n"
            dispmsg += "出力ファイル名:\n"
            dispmsg += str(file1.get()) + "\n\n"
            dispmsg += "サイズ編集後の画像ファイルの一時保存先:\n"        
            dispmsg += str(new_directory)
            messagebox.showinfo('Success', dispmsg)

        else:
            # メッセージ表示
            dispmsg = "Failure!\n\n画像ファイルが見つかりませんでした。処理を中止します。\n\n"
            dispmsg += "再度、画像フォルダを選択して下さい。"
            messagebox.showinfo('Failure', dispmsg)
    
    else:
        # メッセージ表示
        dispmsg = "Failure!\n\nExcelファイルまたは画像フォルダを選択して下さい。\n\n"
        dispmsg += "参照Excelファイル:\n"
        dispmsg += str(file1.get()) + "\n\n"
        dispmsg += "参照画像フォルダ:\n"
        dispmsg += str(file2.get())
        messagebox.showinfo('Failure', dispmsg)

        
# >> 各種ウィジェットの作成
# メインフレームの作成と設置
frame = ttk.Frame(root)
frame.grid(column = 0, row = 0, sticky = tk.NSEW, padx = 5, pady = 10)

# リストボックス(Listbox)を配置
figs_name = StringVar()
fig_name_entry = tk.Listbox(frame, listvariable = figs_name)

#エクセル参照ラベルの作成
label1_s = StringVar()
label1_s.set('Excelファイル指定 >>')
label1 = ttk.Label(frame, textvariable = label1_s)

#参照パスラベルの作成
file1 = StringVar()
file1_entry = ttk.Entry(frame, textvariable = file1)
button1 = ttk.Button(frame, text="参照" , command = excel_ref_clicked)

#画像フォルダ参照ラベルの作成
label2_s = StringVar()
label2_s.set('画像フォルダ指定 >>')
label2 = ttk.Label(frame, textvariable = label2_s)

#参照パスラベルの作成
file2 = StringVar()
file2_entry = ttk.Entry(frame, textvariable = file2)
button2 = ttk.Button(frame, text = "参照" , command = fig_ref_clicked)

#画像ファイルの拡張子ラベルの作成
label3_s = StringVar()
label3_s.set('画像ファイルの拡張子 >>')
label3 = ttk.Label(frame, textvariable = label3_s)

#拡張子の選択コンボボックス
combobox1_s = StringVar()
combobox1 = ttk.Combobox(frame, textvariable = combobox1_s, width=10)
combobox1.set('.png')
combobox1['values'] = ('.png', '.jpeg', '.bmp')
combobox1.bind('<<ComboboxSelected>>')

#貼り付けサイズ指定のラベルの作成
label4_s = StringVar()
label4_s.set('表サイズ : 行 >>')
label4 = ttk.Label(frame, textvariable = label4_s)

#貼り付けサイズ指定(行)のコンボボックス
combobox2_s = StringVar()
combobox2 = ttk.Combobox(frame, textvariable = combobox2_s, width=5)
combobox2.set('1')
combobox2['values'] = ('1','2','3','4','5','6','7','8','9','10','11','12')
combobox2.bind('<<ComboboxSelected>>')

#貼り付けサイズ指定のラベル作成
label5_s = StringVar()
label5_s.set('表サイズ : 列 >>')
label5 = ttk.Label(frame, textvariable = label5_s)

#貼り付けサイズ指定(列)のコンボボックス
combobox3_s = StringVar()
combobox3 = ttk.Combobox(frame, textvariable = combobox3_s, width=5)
combobox3.set('1')
combobox3['values'] = ('1','2','3','4','5','6','7','8','9','10','11','12')
combobox3.bind('<<ComboboxSelected>>')

#挿入画像のサイズ変更ラベルの作成
label6_s = StringVar()
label6_s.set('挿入画像サイズ : 倍率1/x >>')
label6 = ttk.Label(frame, textvariable = label6_s)

#挿入画像サイズの変更指定コンボボックス
combobox4_s = StringVar()
combobox4 = ttk.Combobox(frame, textvariable = combobox4_s, width=5)
combobox4.set('1')
combobox4['values'] = ('1','2','3','4','5')
combobox4.bind('<<ComboboxSelected>>')

#ブールチェック用のチェックボタン作成
bool_check = tk.BooleanVar(value = True)
bool_check.set(False)
checkbutton = ttk.Checkbutton(frame, text = '縦挿入', variable = bool_check)

#実行ボタンの作成
button_execute = ttk.Button(frame, text = "実行", command = start_clicked)


# >> 各種ウィジェットの設置
# エクセルファイル参照
label1.grid(row = 0, column = 0)
file1_entry.grid(row = 0, column = 1)
button1.grid(row = 0, column = 2)

# 画像貼り付けサイズ
label4.grid(row = 0, column = 3)
combobox2.grid(row = 0, column = 4)
label5.grid(row = 0, column = 5)
combobox3.grid(row = 0, column = 6)

# 画像挿入の順序
checkbutton.grid(row = 0, column = 7)

# 画像フォルダ参照
label2.grid(row = 1, column = 0)
file2_entry.grid(row = 1, column = 1)
button2.grid(row = 1, column = 2)

# 画像ファイルの拡張子
label3.grid(row = 1, column = 3)
combobox1.grid(row = 1, column = 4)

# 挿入画像サイズ
label6.grid(row = 1, column = 5)
combobox4.grid(row = 1, column = 6)

# 参照フォルダ内の画像ファイル一覧
fig_name_entry.grid(row = 2, column = 0, columnspan=8, sticky = tk.EW)

# 実行ボタン
button_execute.grid(row = 3, column = 7)

root.mainloop()

自動化ツールのソースコードの中身については、別記事にまとめようと考えているので、その機会にご覧ください。

ツールの使用上の注意点

こちらのツールの使用上の注意点は5つです。

  1. 画像を貼り付けるエクセルは、閉じた状態でツールを使用する
  2. 挿入画像のサイズを変更する場合、画質が低下する可能性がある
  3. 画像の貼り付けは画像ファイルの名前順に実行される
  4. 挿入に使用した画像は「一時保存フォルダ」に別途保存されるため、データ容量に注意する
  5. エクセルにテキストボックスや図形を使用している場合、ツールを使用すると消えてしまう

1. エクセルを閉じた状態で使用する

これは、コード上で「指定したエクセルを開く→画像を挿入→保存」という動作をしているため、はじめにエクセルファイルを開いていると実行中にエラーが生じます。

そのため、エクセルを閉じた状態でツールを使用してください。

2. 画像サイズ変更で画質低下

貼り付ける画像のサイズが元画像からの変更が大きいほど、画質が低下してしまいます。

コード上でリサイズ後の画像を高画質保存にして対策はしているのですが、どうやら上手くいっていないようです。
画質の低下を避けるため、あまり画像サイズの変更は推奨しません。

3. 画像貼り付けはファイル名順に行われる

こちらは、画像を参考にしながらご説明いたします。

このように画像ファイルを保存している場合、自動貼り付けを実行すると、下記のような順序で画像を挿入し保存します。

そのため、ファイル名順に実行するプログラムに考慮して、あらかじめ画像ファイル名を付与しておく必要があります。

4. 一時保存フォルダ

画像の貼り付けに使用したファイルは、一時保存フォルダを作成し、そこに格納するようにプログラムを作成しています。

そのため、知らない間にデータ容量を圧迫しているということも起きかねないので、不要な場合は削除してください。

※一時保存フォルダは、参照した画像フォルダ内に「Temp_Save_Folder」の名前で新しく作成されます。下記の例の場合、「貼り付けグラフ」(画像貼り付けに使用したフォルダ)内に作成されています。

5. 元のエクセルに挿入してあった図形が消える

こちらは、使用過程で気づいたエラーになります。
画像貼り付け実行時における、もともとエクセルに挿入していた図形やグラフのビフォーアフターをご覧ください。

このように、ツールを使用して画像挿入を実行した場合、もとのエクセルに挿入していたテキストボックスや図形は消えてしまいます。

セルに入力したテキストや数値、グラフはそのまま残るため、かろうじて問題ないかもしれませんが、 図形を多用している方は使用時に注意してください。

この場合、画像を貼り付ける専用のエクセルを作成しておき、そこから自動挿入した画像データをコピペするというやり方が楽かと思います。

ツールの改善点

ツールを改善するとすれば、前述の注意点をふまえた下記があげられます。

  1. 画像サイズ変更の画質低下対策
  2. 変更後の画像サイズ指定を倍率ではなく、ピクセル指定にする
  3. もとのエクセルに挿入されていた図形が削除されないような対策

私個人的には使用上で特に困る内容がないため、これらの改善点を修正しておりませんが、皆さんは使用上の用途に合わせてコード修正を行ってみてください。

また、Pythonの勉強を進めるにあたり、より便利な機能や追加で発見した課題などがあれば修正・更新していきたいと思います。

ツールを職場で展開する場合

こちらを業務効率化ツールとして展開する場合は、exeファイル化をしてください。

exe化することにより、展開された側のPCにpythonがインストールされていなくても、ツールを使用することができます。

このpyファイル(pythonの実行ファイル:.py)のexe化は2つの手順で完了します。

  1. pyinstallerをインストールする
  2. exe化のコマンドを実行する

まずは、pyinstallerというライブラリをインストールします。

インストールはコマンドプロンプトやターミナルで下記のコマンドを実行してください。

pip install pyinstaller

インストールが完了したのちに、下記のコマンドを入力して実行してください。

pyinstaller (pyファイル名) --onefile --noconsole

※上記コマンドの実行時は”()”は不要です

上記のコマンドを実行することにより、pyファイルのexe化ができます。

ちなみに、”–onefile”はexe化のファイルを1つにまとめて作成してくれるコマンドです。
入力していないと大量のファイルが出力されるので必須です。

また、”–noconsole”は実行時にコマンドプロンプトの画面を表示されなくするコマンドです。

exeファイルを実行したときに、コマンドプロンプトが表示されると不安になる方もいると思うので、忘れずに記述しましょう。

この他にも、Pythonで仕事の自動化をしたい時にオススメの参考書はこちら!

おわりに:プログラミングは実際に手を動かそう

このツールを作成するに至った背景は、「エクセルの単純作業が面倒」という想いからでした。

私自身は電気系の出身では無いため、プログラミングを行った経験は高専で「FORTRAN」の授業を受けたくらいです。

そんな私が、YouTubeでpython動画やライブラリについて調べて作ってみたのがこのツールです。

コードの書き方や、「もっとこうすればいいのに」というところが多々あるかもしれませんが、業務効率化や自動化ツールに興味のある方のご参考になれば幸いです。

また、本記事でご紹介したツールのソースコードについては、別記事でまとめようと思っています。

そのため、記事を読んで「ここどういうこと?」「こんな機能は無いのかな」という感想もあるかと思いますが、まずはコピペで使ってみていただければと思います。

使用していく中で、私も気づいていないエラーの発生等があるかもしれませんが、「こんな機能欲しい!」「こういう修正すればいいんじゃないか」という発見があれば、ぜひ共有いただけたら嬉しいです。

ご覧いただき、ありがとうございました!
それではまた。よろしくお願いいたします!

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