基于alpine搭建python及wkhtmltopdf环境

1.引言

最近项目中遇到一个PDF相关的问题,需要通过程序生成PDF文件,PDF中包含较为复杂的内容(可能有表格、表单、文本、图片)。

一开始想到的方案是通过调用一些pdf库根据需要生成的内容手动绘制pdf文件,几年前使用java调用iText这个库做过类似的工作,但内容比较简单,而且效果不佳,不够美观。内容比较多的情况下,这种硬编码绘制pdf界面的工作量有点不敢想象,于是放弃了这个念头。

于是抱着找个现成的pdf工具直接拿来用的心态,打开github,搜索pdf,Language选择Python,按stars排序,结果可以分成以下几类:读写PDF文件内容、PDF操作(旋转、分割、合并、水印)、HTML转PDF、OCR文字图像识别...

到这里,初步选择“HTML转PDF”作为方向定位到WeasyPrint [★3.2k]、xhtml2pdf [★ 1.6k]、python-pdfkit [★ 1.1k] 这几个库。

依次尝试了这几个库发现只有★最少的python-pdfkit生成的pdf内容最为接近原始页面,没有很多动态效果的网页几乎是完全一样。并且使用起来也非常pythonic,一行代码就能完成转换动作。其它2个要么转换效果不够完美,要么使用过于复杂,没有太多尝试。

下图是将kotlin官网主页转成pdf后的效果,除页面上需要二次请求的部分图片和视频未显示出来,几乎跟原始页面完全一致。

wkhtmltopdf https://kotlinlang.org/ kotlin.pdf

python-pdfkit是用Python对wkthmltopdf [★ 9k]这个工具的封装,所以需要在系统上安装wkthmltopdf。

2.本地安装wkhtmltopdf及pdfkit

2.1 安装wkhtmltopdf

本地安装wkhtmltopdf比较简单,各大Linux操作系统的包管理工具都支持。

2.1.1 RedHat/CentOS/Fedora:
sudo dnf install wkhtmltopdf
2.1.2 Debian/Ubuntu:
$ sudo apt-get install wkhtmltopdf
2.1.3 macOS:
$ brew install caskroom/cask/wkhtmltopdf
2.1.4 windows

Windows平台可以去官网下载.exe文件安装,不过由于是国外网站,下载基本是龟速。

wkhtmltopdf安装完成以后,就可以在命令行测试是否安装成功了。

wkhtmltopdf https://www.baidu.com/ baidu.pdf

如果安装成功并且各种依赖没有问题,会在当前路径下生成一个pdf文件。

当然wkhtmltopdf支持许多参数,具体的用法可以查看帮助文档。

2.2 安装pdfkit

pdfkit可以在python环境下通过pip快速安装。

pip3 install pdfkit

3.服务器上安装wkhtmltopdf

服务器上安装wkhtmltopdf与本地Linux环境安装基本没有区别,使用包管理工具安装,千万别不自在尝试用.deb或.rpm包安装,否则依赖问题可能会让你不得不熬夜...

另外,还有一个问题,由于服务器一般没有图形界面,也就是X Server,而wkhtmltopdf依赖X Server,所以需要通过虚拟X Server来解决这个问题,具体的设置可以在pdfkit的wiki页面看到。

Using wkhtmltopdf without X server

4.Docker容器中安装wkhtmltopdf

到这里并没有结束,我们的目标在Docker容器中运行应用程序,所以需要在Docker容器中安装wkhtmltopdf。

之前为了省事,直接用python:3.8作为基础镜像,创建了应用程序的镜像。于是进入到该容器中尝试性地安装wkhtmltopdf,安装很顺利,结局很意外。在命令行运行wkhtmltopdf的时候找不到Qt相关的一个库文件。

# wkhtmltopdf -V
wkhtmltopdf: error while loading shared libraries: libQt5Core.so.5: cannot open shared object file: No such file or directory

一开始的思路是把这库文件加上,既然缺少,那么加上不就完事了嘛。结果四处搜索解决办法,百度、github issues、stackoverflow找到了同样的问题,按照热心网友提供的答案折腾了两个晚上才放弃...对,两个晚上还是没搞定,没办法只能换个思路来解决问题了。

在这期间,又尝试了使用ubuntu、centos等基础镜像,出现了上文提到的X Server相关的问题,一顿操作后wkhtmltopdf终于能正常运行了。

不过又发现了一个问题,发现之前构建的镜像(安装wkhtmltopdf后)大小达到了惊人的1.1G~1.2G...
其实这个问题以前也有注意到,只不过好歹能正常运行,就没有考虑性能和容量问题,哈哈!!

于是就想着今天就自己来构建一个小巧一些的镜像,所以就想到了这篇文章中的主角:Alpine

5.通过alpine构建最小镜像

到这里就直接放出Dockerfile了。

5.1 python 3.8环境

FROM alpine:edge
ENV LANG=C.UTF-8
RUN apk update
# 安装python、pip、pipenv
RUN apk add --no-cache python3=3.8.0-r0 && \
    ln -fs /usr/include/locale.h /usr/include/xlocale.h && \
    ln -fs /usr/bin/python3 /usr/local/bin/python && \
    ln -fs /usr/bin/pip3 /usr/local/bin/pip && \
    pip install pipenv

通过alpine安装python 3.8构建的镜像大小大概在80M左右。

5.2 python3.8 + wkhtmltopdf + 中文字体(思源黑体)

构建完python镜像后,就想能不能把wkhtmltopdf也加上呢?于是去alpine的安装包仓库查找了一下,幸运的是wkhtmltopdf已被收录进来,在community分区下。

https://pkgs.alpinelinux.org/packages?name=wkhtmltopdf&branch=edge

http://dl-cdn.alpinelinux.org/alpine/edge/community/

那么只要加上一行apk add wkhtmltopdf应该就可以了,最后添加中文字体。

FROM alpine:edge
ENV LANG=C.UTF-8
RUN apk update
# 安装python、pip、pipenv
RUN apk add --no-cache python3=3.8.0-r0 && \
    ln -fs /usr/include/locale.h /usr/include/xlocale.h && \
    ln -fs /usr/bin/python3 /usr/local/bin/python && \
    ln -fs /usr/bin/pip3 /usr/local/bin/pip && \
    pip install pipenv
RUN apk add --no-cache wkhtmltopdf
# 复制字体到字体目录,如果宿主机没有准备好字体文件,这里可以换成使用wget去下载
COPY fonts/SourceHanSansCN/SourceHanSansCN-Normal.otf /usr/share/fonts/

通过alpine安装python 3.8、wkhtmltopdf,并且安装中文字体后,镜像大小也仅有200M左右。

# 这里我将镜像命名为pypdf (python + wkhtmltopdf)
$ docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
pypdf                latest              2bd31821d26a        31 minutes ago      212MB

5.3 打包应用程序镜像

到这一步,在上面的Dockerfile内容后面加上自己的应用程序的相关内容就可以了,大致为复制代码 > 设置环境变量 > 安装依赖 > 启动应用程序等操作。

但是过程中会使用pip安装依赖,需要用到gcc等工具,所以先通过apk安装这些库,pip安装完成依赖后再将这些库移除以缩小镜像体积。

FROM alpine:edge
ENV LANG=C.UTF-8
RUN apk update
RUN apk add --no-cache python3=3.8.0-r0 \
        python3-dev libgfortran build-base libstdc++ libpng libpng-dev freetype freetype-dev && \
    ln -fs /usr/include/locale.h /usr/include/xlocale.h && \
    ln -fs /usr/bin/python3 /usr/local/bin/python && \
    ln -fs /usr/bin/pip3 /usr/local/bin/pip && \
    pip install pipenv
RUN apk add --no-cache wkhtmltopdf
COPY fonts/SourceHanSansCN/SourceHanSansCN-Normal.otf /usr/share/fonts/

# 复制代码 > 设置环境变量 > 安装依赖 > 启动应用程序
# 这里只是为了验证程序能否正常运行,简单起见,没有加uWSGI
ADD ./ /app/
WORKDIR /app/
ENV FLASK_DEBUG=0
ENV FLASK_HOST="0.0.0.0"
ENV FLASK_APP="run_app.py"
RUN pipenv install
EXPOSE 5000
CMD pipenv run python run_app.py

RUN apk del --purge build-base libgfortran libpng-dev freetype-dev python3-dev && rm -vrf /var/cache/apk/*

6.收工

镜像创建成功后,启动容器,运行程序,把生成pdf的逻辑走了一遍,一切正常,nice!!!

今天终于可以早点睡觉了,哈哈哈!!!

7.后记

本以为一切顺利、万事大吉的时候,又发生意外了!wkhtmltopdf这个工具居然还区分with patched qt版本和with unpatched qt 版本,只有patched qt版本支持诸如页眉、页脚、边距等高级操作。很不幸,在alpine系统上通过apk安装的是wkhtmltopdf 0.12.5 (with unpatched qt)。

业务需求无法实现自然是不行的,只得继续在github上寻找答案。

首先找到一种方案:在装好wkhtmltopdf 0.12.5 (with unpatched qt)的基础上安装及配置qt,由于对qt并不了解,看到洋洋洒洒几十行操作步骤,只能另寻他法。

第二种方案:事先在其它的系统上编译安装配置好wkhtmltopdf 0.12.5 (with patched qt),将最终得到的可执行文件移植到alpine系统上不就可以了,不过wkhtmltopdf依赖的库当然必不可少需要一并安装。至于可执行程序我自己就不去编译了,已经有热心人分享出来了,直接拿来用即可。最后我将这个方案写成了Dockerfile,wkhtmltopdf可执行程序也上传到github,另外我还一并添加了谷歌思源字体,毕竟要处理中文字体。

FROM alpine:edge
# 1.add dependencies for wkhtmltopdf
RUN apk add --update --no-cache \
    libgcc libstdc++ libx11 glib libxrender libxext libintl \
    ttf-dejavu ttf-droid ttf-freefont ttf-liberation ttf-ubuntu-font-family
# 2.copy executable wkhtmltopdf to the container's `/bin` folder
COPY wkhtmltopdf /bin/wkhtmltopdf
# 3.add chinese font `SourceHanSansCN` to the container
COPY fonts/SourceHanSansCN-Normal.otf /usr/share/fonts/SourceHanSansCN-Normal.otf

最后我做成了一个wkhtmltopdf的Docker镜像,额外的Python环境就在这个镜像基础上继续构建。

到这里,曲折的过程才算画上句号!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342