美文网首页
基于alpine搭建python及wkhtmltopdf环境

基于alpine搭建python及wkhtmltopdf环境

作者: yuanzicheng | 来源:发表于2020-06-23 15:45 被阅读0次

    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环境就在这个镜像基础上继续构建。

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

    相关文章

      网友评论

          本文标题:基于alpine搭建python及wkhtmltopdf环境

          本文链接:https://www.haomeiwen.com/subject/vvshfktx.html