この記事は、クロスマート・テックアドカレ14日目の記事です。
qiita.com
前日は、@gomagoma000さんの「ディズニーランドを最も効率よく楽しむ方法をグラフ理論とPythonで考える」という記事でした。
ディズニーランド好きなガチ勢のみなさんにはとても有用な記事です。
ぜひ見て見てください!
qiita.com
さて、今回の記事では
PDF生成ライブラリwkhtmltopdf(Python)の代替を探す旅 〜Playwright (Katlin) 編 〜
と題しまして、書いていこうと思います。
みなさん、Pythonのhtmlからpdfを作成するライブラリ wkhtmltopdf はご存知でしょうか。
Pythonではかなり広く使われていたライブラリなのですが、内部で利用しているQt WebKit(Qt のレンダリングエンジン)が公式サポート停止してしまいました。
その流れを受け、wkhtmltopdf自体もメンテが停止してしまいました。GitHubページも2023年1月を以ってアーカイブ状態にあります。
github.com
一応、ライブラリ自体はまだダウンロードできる状態にあるため、使うことはできますが、もう機能追加もバグがあっても修正されない状態にあります。
ダウンロードもいつできなくなってもおかしくない状態です。
Pythonで使える良い代替ライブラリがないか探して使って見たりしたのですが、うまくcssなどが当たらない、性能がwkhtmltopdfより出ない、などの理由により、これというものが見つかっていませんでした。
ですが、ふと思いつきました。
別にPythonにこだわらずとも別の言語で使えるものがあるのでは・・・?
最近、KotlinでMicronautを使い始めていた私。 Kotlin(Java)で使えそうなライブラリを探しました。
flying-saucer、itextなども使って見ましたが、うまくcssなどが当たらない、そもそも有料 など、決定打に欠けましたが、
良いものがありました! Playwright (Java版) です!!
playwright.dev
そもそもPlaywrightとはWebアプリのE2Eテスト向けのライブラリなのですが、ブラウザ操作の自動化などに使われることも多いです。
さらに、WebページをPDF化する機能もあり、PDF生成ライブラリとしても使われています。
内部でブラウザを動作させてWebページを表示するので、最新のcss3やhtml5の再現も全く問題ありません。
こちらはPython版もあり、代替として検討もしたのですが、性能があまり出ませんでした。
もしかしたらJavaなら十分なパフォーマンスが出るかもしれないということで、今回試してみました。
性能比較
それでは早速実験です。今回はhtml文字列をPDF化する という箇所に限定して計測します。(データベースからhtmlを生成する、などの別の箇所のパフォーマンスについては計測対象にしない)
htmlは、PDFにすると3ページほどの小さいページと、100ページを超える大きいページを利用しました。
Pythonのwkhtmltopdfはこのようなコードで計測しました。
import tempfile from datetime import datetime from jinja2 import Template from starlette.templating import Jinja2Templates def get_public_pdf(): templates = Jinja2Templates(directory=str(Path(BASE_DIR, "app/templates/"))) template: Template = templates.get_template("sample.html") html = template.render() # テンポラリファイルにPDFを作成する file_path: str = tempfile.NamedTemporaryFile(mode="w").name print("開始日時" + (datetime.now()).strftime("%Y-%m-%d %H:%M:%S.%f")) Pdf.create_by_html(html, file_path) print("終了日時" + (datetime.now()).strftime("%Y-%m-%d %H:%M:%S.%f"))
Playwright(Kotlin) はこのようなコードで計測しました。
ブラウザは毎回新しく開かないように、Factoryで一度開いたら使い回す工夫をしています。
import com.microsoft.playwright.Browser import com.microsoft.playwright.Page.PdfOptions import com.microsoft.playwright.options.Margin import io.micronaut.http.annotation.* import java.nio.file.Paths import java.time.LocalDateTime @Controller("/public/pdf") class PdfController(private val browser: Browser) { @Get("/") fun createPdf() : String { // resourcesのsample.htmlを読み込む val html: String = PdfController::class.java.getResource("/sample.html").readText() println("開始日時: " + LocalDateTime.now()) val context = browser.newContext() val page1 = context.newPage() page1.setContent(html) // HTMLをセット // PDFとして保存 page1.pdf( PdfOptions() .setPath(Paths.get("example.pdf")) // 保存先のパス .setFormat("A4") // 用紙サイズ(例: A4, Letter など) .setPrintBackground(true) // 背景の印刷を有効化 .setMargin(Margin().setTop("6mm").setRight("6mm").setBottom("6mm").setLeft("6mm")) // 余白 .setScale(0.8) // スケール ) page1.close() println("PDFが生成されました: example.pdf") println("終了日時: " + LocalDateTime.now()) context.close() return "OK" } }
PDFはこのような明細レコードがたくさんある表のような請求書PDFになっています。
実験結果
それぞれ、何回か計測した結果をグラフでまとめてみました。
Python wkhtmltopdf は小さいPDFで概ね2秒、100ページ越えのPDFでは概ね7〜8秒
Java(kotlin) playwrightは 小さいPDFで概ね0.2〜0.3秒、100ページ越えのPDFでは概ね2〜3秒
という結果となりました。
Java(kotlin) playwrightがサイズに関わらず、安定して早くPDF生成できるということがわかりました!
Javaはやっぱええなぁ・・・と思うと同時に、PDF生成処理を主サービスからは切り離して、共通サービスとして利用することで、
性能もメンテナンスが活発にされているライブラリに乗り換えることができそうだ、という道筋を立てることができました!!
まとめ
今回の記事では、PythonのPDF生成ライブラリ wkhtmltopdf のサポート終了を受け、Kotlinで利用可能な代替ライブラリ Playwright を活用したPDF生成の実験結果をご紹介しました。
結果として、Playwright(Java版)は性能面で非常に優れており、特に大規模なPDF生成においても安定した高速処理が可能であることが分かりました。最新のCSS3やHTML5の再現性が高い点も、大きな魅力です。
また、KotlinやJavaを活用することで、PDF生成処理をサービスから切り離し、共通サービスとして再利用するアーキテクチャの可能性が広がりました。これにより、性能向上だけでなく、メンテナンス性や将来的な拡張性も期待できます。
既存の環境にこだわらず、他の言語や技術に目を向けることで新しい選択肢を発見できるということを、今回の試行で改めて実感しました。
PDF生成の課題でお悩みの方は、ぜひ一度 Playwright を試してみてはいかがでしょうか。特にKotlinやJava環境での利用を考えている方には、非常におすすめできるソリューションです!
最後まで見ていただき、ありがとうございました!