异步框架tornado下使用pyppeteer将动态html转化为pdf

一、项目背景:

云上服务器存储html,前端通过传递给后端html_url, 由后端服务器获取html文件进行渲染,生成pdf,

然后将pdf上传云上服务器。

 

二、使用的框架/库:

tornado/ pyppeteer/docker

选择pyppeteer, 有如下依据: python官方库如xhtml2pdf只能处理类似富文本类的静态页面,而html需要js渲染, 

故借助浏览器是一种可行的实现方式; tornado是异步框架,pyppeteer是异步库,匹配。

 

三、实现过程:

1、选用pyppeteer的版本

tornado==5.1.1

pyppeteer==1.0.2

  

2、使用docker部署服务, 安装浏览器, dockerfile 如下:

FROM python:3.7-slim
COPY ./requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt -i http://mirrors.aliyun.com/pypi/simple --trusted-host mirrors.aliyun.com

# ===== 安装chrome浏览器, 配置 --no-sandbox =================
RUN apt-get update && apt-get install -y wget && \
    wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \
    apt-get install -y -f ./google-chrome-stable_current_amd64.deb && \
    sed -e '/chrome/ s/^#*/#/' -i /opt/google/chrome/google-chrome && \
    echo 'exec -a "$0" "$HERE/chrome" "$@" --user-data-dir="$HOME/.config/chrome" --no-sandbox --disable-dev-shm-usage' >> /opt/google/chrome/google-chrome
# ========================================================


# ========= 处理pyppeteer引起的僵尸进程问题 =================
ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 /usr/local/bin/dumb-init RUN chmod +x /usr/local/bin/dumb-init
# =======================================================
ADD . /project WORKDIR /project

 

3、实现模块

注意两个点: 下面两处注释处

    async def get_pdf_file2(self, opts):

        html_url = opts.get("html_url")

        browser = await pyppeteer.launch(
            {
                "executablePath": "/opt/google/chrome/google-chrome",  # docker配置的浏览器路径
                "timeout": 160000
            })
        page = await browser.newPage()

        # waitUntil参数: 直到 http 连接为 0,即页面加载完成
     # 这一步不配置, 会导致数据加载不完全,或数据未加载完毕浏览器关闭而导致报错 await page.goto(html_url, { 'waitUntil': 'networkidle0' }) pdf_file = await page.pdf({"format": 'A4'}) await browser.close() return pdf_file  

docker构建后,容器中对应的chrome的版本信息:

 

 

第一次构建镜像会比较慢,耗时53分钟,之后就利用了docker的缓存, 速度就比较快了。

  

四、总结:

1、在实现过程中,遇到无法加载浏览器的原因,因为pyppeteer自身有下载headerless浏览器chrumium的实现,但由于docker中缺乏启动该浏览器的依赖,会导致无法启动浏览器,而安装依赖则亦是一个耗时较大的过程,而且中间会存在配置依赖的过程,故选择直接安装稳定版的浏览器,并将其配置成为非沙箱模式。

2、测试过程中发现会出现大量僵尸进程,原因是每一次访问,浏览器都会启动一次,并且产生浏览器的子进程,而browser.close()只能关闭浏览器主进程,子进程交给操作系统的init进程来处理,而docker的init进程是无法像正常操作系统一样处理这些子进程,导致他们无法被处理,形成僵尸进程。如果该问题不处理,最终将导致内存、端口被挤爆。

3、数据加载不完全,且出现运行异常

pyppeteer.errors.NetworkError: Protocol error Target.sendMessageToTarget: Target closed.

 原因是浏览器渲染过程中, 去服务器取动态数据,由于是异步过程,数据还未加载完全,浏览器就被程序关闭了。查看源码,先尝试了延迟时间,但依旧无法处理,而且增加耗时,用户体验变差。后来查看文档,配置了'waitUntil': 'networkidle0', 成功解决了这个问题。

 

4、一个使用比较广泛的库,一定会考虑得比较完善,所以遇到问题一定要仔细查看文档和源码,一般能找到解决方法。当然,也可以上网查看他人解决方法,他山之石,可以攻玉。

 

5、方案确定后,遇到这种问题,一定要深挖,解决问题总是在山重水复的时候,不经意间就豁然开朗了。而带来的收获可不止是工作上的、知识领域的,经历过的都会明白。

posted @ 2022-11-04 11:48  Wolf_Stark  阅读(204)  评论(0编辑  收藏  举报