技術的な話

【Python】openpyxlでExcelを出力した際に数式の計算結果が取得できない問題 – 対策編

前回記事はこちら

環境

  • Docker 20.10.11
  • イメージ jupyter/datascience-notebook
  • Python 3.8
  • openpyxl 3.0.8

具体的な対策内容

概要

前回の原因と対策案を鑑みて以下の通りすることにしました。

  1. DockerにLibreOfficeを導入する。
  2. PythonからLibreOfficeをコマンドモードで起動する。
  3. 起動したLibreOfficeを使用してExcelファイルを開いて保存する。

対策をしていく

Dockerfileの修正

DockerにLibreOfficeを導入するために、インストールを行います。
日本語パッケージも入れています。

下記のバージョンは6.4で、現在(2022/02)だと7.3が最新安定版のようです。

ダウンロード先のURLが変更となっている場合もあるので、最新のLibreOfficeのバージョンを公式で確認してください。

FROM jupyter/datascience-notebook

# (省略)諸々変数を定義

# (省略)apt-get updateとか必要なアプリケーションをインストール

# LibreOffice Install
RUN wget https://download.documentfoundation.org/libreoffice/stable/6.4.7/deb/x86_64/LibreOffice_6.4.7_Linux_x86-64_deb.tar.gz --no-check-certificate -P /tmp/ && \
   wget http://download.documentfoundation.org/libreoffice/stable/6.4.7/deb/x86_64/LibreOffice_6.4.7_Linux_x86-64_deb_langpack_ja.tar.gz --no-check-certificate -P /tmp/ && \
   tar zxvf /tmp/LibreOffice_6.4.7_Linux_x86-64_deb.tar.gz -C /tmp/ && \
   tar zxvf /tmp/LibreOffice_6.4.7_Linux_x86-64_deb_langpack_ja.tar.gz -C /tmp/ && \
   cd /tmp/LibreOffice_6.4.7.2_Linux_x86-64_deb/DEBS/ && \
   apt install ./*.deb && \
   cd /tmp/LibreOffice_6.4.7.2_Linux_x86-64_deb_langpack_ja/DEBS/ && \
   apt install ./*.deb

これでLibreOfficeをインストールしたコンテナイメージが出来上がりました。

起動させてコンテナに接続するとLibreOfficeがインストールされていることを念のため確認します。

# コンテナに接続
# <your-service-name>はご自身の環境に合わせてください
$ sudo docker-compose exec <your-service-name> bash

# コンテナ内で確認
$ /usr/local/bin/libreoffice6.4 --help

# Jupyter上では下記で確認可能です
# 先頭に!を付与することでコマンドが実行可能です
!/usr/local/bin/libreoffice6.4 --help

コマンドで実行してExcelファイルを開いて保存してみる

厳密に言えばExcelからExcelに変換処理を行います。本来であればExcelからPDFファイルに変換したい時に有用なのですが…。

この変換処理を実行することで、開く⇒保存するという処理を行っているので上手く活用します。

下記はJupyter上からLibreOfficeの変換処理を実行している例です。

# 変換処理を行うコマンドです
# --headless:GUI無しで起動
# --language:日本語を指定してます
# --nofirststartwizard:初回のウィザードを無効にします 
# --convert-to:変換先の形式を指定します(今回はxlsx)
# --outdir:出力先
!/usr/local/bin/libreoffice6.4 --nolockcheck --nologo --headless --norestore \
--language=ja --nofirststartwizard --convert-to xlsx --outdir <path/to/output/folder> <path/to/source/file>

このコマンド実行後に変換されたファイルをopenpyxlで読み込むと無事に値が反映されていることが確認できました。

今回の対策を実施した場合の副作用

なんとか目標は達成できましたが、デメリットというか副作用が多少あります。

  • 日本語が違うフォントになっている。

私が見ていた感じだとこれぐらいでしたが、もう少し細かく確認していくと何かしら副作用があるかもしれません。流石に値が違うとかってことは無いと信じたいです(私が見た感じは値は変わってなかったです)。

MicrosoftのExcelで作成したファイルを、KingsoftやLibreOfficeで開いた時にレイアウトや細かい見た目が違うっていうやつですね。

今回は変換したExcelを値を取得するだけにして、出力先のExcelは別のテンプレートファイルを使用することで対応しました。

Windowsの場合はどうするの

これまで説明したものはWindowsでも実装可能です。Microsoft ExcelがインストールされてればLibreOfficeを使用することも無いです。

下記の場合ですね。

  • DockerイメージがWindowsベース+Jupyter+Excel
  • WindowsマシンにJupyterがインストールされている+Excel

xlwingsを使用すると手軽に実現可能です。

# jupyter上で下記を実行します
# 単純にMicrosoft Excelを起動してファイルを保存しているだけです
import xlwings
import win32com.client

app = win32com.client.Dispatch("Excel.Application")
app.Visible = False
app.DisplayAlerts = False

wb = app.Workbooks.Open('<path/to/source/file>')
wb.saveAs('<path/to/output/file>')

app.Quit()

まとめ

前回記事からだと結構長くなってしまいましたがまとめます。

現象

Pythonで取得した値をExcel上に出力して、出力された値を参照する数式はopenpyxlで開いても反映されない。

普通にExcelで開くのはいけます。それを再度Pythonで読み取ろうとした場合のみです。

原因

数式はMicrosoft Excel or その他表計算ソフトが起動した段階で評価され、保存時に値が書き込まれるため。

対策

LinuxならLibreoffice、WindowsならExcel(無ければLibreofficeでも代替可能)をPython経由で起動→保存すれば解決。ただし、Libreofficeの場合日本語フォントがおかしくなるので、処理を多少工夫する必要があります。

-技術的な話
-, , , ,