12
6

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.

CIST (公立千歳科学技術大学)Advent Calendar 2022

Day 7

PythonでExcel処理→PDF出力→PDF圧縮を自動化する

Last updated at Posted at 2022-12-07

はじめに

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ファイルに依存するので流れとしては

  1. 自動化させたいxlsxファイルがある
  2. openpyxlでソースを書く
  3. libreofficeでPDF化させる
  4. PyPDF2で圧縮する

になります。

1.適当なxlsxを用意する(sample.xlsx)

IDと名前と歳をカラムに持つファイルを用意しました。

スクリーンショット 2022-12-07 21.02.43.png

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を見てみると

無事に出力できています

スクリーンショット 2022-12-07 21.52.07.png

圧縮の前と後のサイズを比べてみる

$ 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として使うことになりそうです

12
6
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
12
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?