はじめに
pythonでExcelの自動化は有名ですが、さらにExcelファイルをPDF出力し、そのファイルを圧縮するとこまで深掘りしようと思います。
正確には「DockerでExcel処理→PDF出力→PDF圧縮を自動化する」の方が正しいですが、ご了承ください。
最後にリポジトリも貼っているのでよろしければぜひ!
背景
学生団体の活動でWebアプリ開発をしました。
そのWebアプリのAPIとして「JsonからExcel操作してPDF出力」といった機能が必要になりました。
Excel操作はpythonのopenpyxlで良いとして、PDF出力するためのライブラリ(pythonに限らず)を模索しましたが、レガシーなものが多かったため最終的にlibreofficeをコンテナに導入することで機能が実装できました。
結論
Docker上に以下を導入することで可能になります。
導入するもの | 役割 |
---|---|
openpyxl | Excel処理 |
libreoffice | PDF化 |
PyPDF2 | PDFを扱える(今回はPDF圧縮で用いる) |
実装してみる
ディレクトリ構成は以下の通りとなっています。
.
├── app
│ ├── ExcelPdfConverter.py
│ ├── files
│ │ ├── sample-compressed.pdf
│ │ ├── sample.pdf
│ │ └── sample.xlsx
│ └── sample.xlsx
├── docker
│ ├── Dockerfile
│ └── requirements.txt
└── docker-compose.yml
補足
sample.xlsxは後ほど扱います。
requirements.txt
openpyxl==3.0.10
PyPDF2==2.11.2
Dockerfile
Dockerfileはできる限りDockerfileのベスト・プラクティスに則っています
※libreofficeで日本語を扱うときは、日本語フォント入れないと文字化けします
FROM python:3.9-buster as builder
COPY requirements.txt /
RUN /usr/local/bin/python -m pip install --upgrade pip && pip install --no-cache-dir -r requirements.txt
FROM python:3.9-slim-buster
ENV PYTHONUNBUFFERED=1
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libreoffice \
fonts-takao-gothic \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p home/myuser/app
RUN adduser myuser && chown -R myuser home/myuser/app
USER myuser
WORKDIR /home/myuser/app
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages/
docker-compose.yml
Dockerfileでuserを作成したのでvolumesの指定を間違わないようにしてください
version: "3"
services:
app:
build:
context: ./docker
dockerfile: Dockerfile
restart: always
tty: true
volumes:
- ./app:/home/myuser/app
ExcelPdfConverter.py
subprocessモジュールを利用して、pythonからlibreoffieを利用している。
※例なのであくまで参考程度にお願いします
import subprocess
import os
import re
import PyPDF2
import openpyxl as px
class ExcelPdfConverter:
def __init__(self, original_excel_file: str, out_dir:str="/home/myuser/app/files/") -> None:
if not os.path.isfile(original_excel_file):
raise FileNotFoundError
if not re.search(r'.xlsx$', original_excel_file):
raise Exception("Please input .xlsx format file")
self.__original_excel_file: str = original_excel_file
self.__base_name: str = os.path.splitext(os.path.basename(original_excel_file))[0]
self.__out_dir: str = out_dir
def editExcel(self) -> None:
wb = px.load_workbook(self.__original_excel_file)
ws = wb.active
ws["A2"].value = "001"
ws["B2"].value = "山田太郎"
ws["C2"].value = "18"
wb.save(f"{self.__out_dir+self.__base_name}.xlsx")
def convertPdf(self) -> None:
cmd: list = []
cmd.append("libreoffice")
cmd.append("--headless")
cmd.append("--nologo")
cmd.append("--convert-to")
cmd.append("pdf")
cmd.append("--outdir")
cmd.append(self.__out_dir)
cmd.append(f"{self.__out_dir+self.__base_name}.xlsx")
subprocess.run(" ".join(cmd), shell=True)
def pdfCompress(self) -> None:
writer = PyPDF2.PdfFileWriter()
reader = PyPDF2.PdfFileReader(f"{self.__out_dir+self.__base_name}.pdf")
for i in range(reader.getNumPages()):
page = reader.getPage(i)
page.compressContentStreams()
writer.addPage(page)
with open(f"{self.__out_dir+self.__base_name}-compressed.pdf", "wb") as fp:
writer.write(fp)
if __name__ == "__main__":
converter = ExcelPdfConverter("sample.xlsx")
converter.editExcel()
converter.convertPdf()
converter.pdfCompress()
libreofficeオプション(一部抜粋)
オプション | 概要 |
---|---|
headless | GUI無しで起動 |
nologo | スプラッシュスクリーン無しで起動 |
convert-to | 変換するファイル形式 |
outdir | (convert-toと併用)変換するファイルの保存場所 |
補足
詳細は libreoffice --help で確認してください
実際に使ってみる
ソースコードは大半xlsxファイルに依存するので流れとしては
- 自動化させたいxlsxファイルがある
- openpyxlでソースを書く
- libreofficeでPDF化させる
- PyPDF2で圧縮する
になります。
1.適当なxlsxを用意する(sample.xlsx)
IDと名前と歳をカラムに持つファイルを用意しました。
2. Dockerでコンテナ起動
$ docker compose build
$ docker compose up -d
$ docker ps
CONTAINER ID ...省略
96bae4353519
$ docker exec -it 96bae4353519 /bin/bash
myuser@96bae4353519:~/app$ -ここから先はコンテナ内
3. スクリプト実行
「javaの環境ないよ」とか色々言ってきますが、気にしなくても大丈夫です。
myuser@96bae4353519:~/app$ python ExcelPdfConverter.py
javaldx: Could not find a Java Runtime Environment!
Please ensure that a JVM and the package libreoffice-java-common
is installed.
If it is already installed then try removing ~/.config/libreoffice/4/user/config/javasettings_Linux_*.xml
Warning: failed to read path from javaldx
convert /home/myuser/app/files/sample.xlsx -> /home/myuser/app/files//sample.pdf using filter : calc_pdf_Export
Overwriting: /home/myuser/app/files//sample.pdf
myuser@96bae4353519:~/app$
sample.pdfを見てみると
無事に出力できています
圧縮の前と後のサイズを比べてみる
$ ls -l -h app/files/
12K Dec 7 21:42 sample-compressed.pdf
13K Dec 7 21:42 sample.pdf
今回はシンプル過ぎたので圧縮するまでもありませんでした。
この後、別のファイルで試しましたが数百バイトの圧縮とPyPDF2の圧縮は非常に弱かったです。気持ち軽くなった程度なので、ghostscript
とか試してみたいです。
まとめ
1つのdockerコンテナで完結するので煩雑にならず、様々なユースケースに対応できますが、主にwebAPIとして使うことになりそうです