使用Selenium实现HTML转PDF

在之前,州的先生为了在觅道文档中实现 markdown 转 pdf 的功能,考察和调研的市面上的一些通行解决方案,详见https://zmister.com/archives/1607.html

在那之后,觅道稳定选择了 Chromium + pyppeteer 的方案作为 HTML 转换 PDF 的技术栈。

但是这个方案并非完美,由于 PyPeeteer 这个第三方库年久失修,很多 Bug 没有修复,导致在觅道文档中调用它经常性地会出现异常。

不得已,州的先生只得另寻它法。由于觅道文档中生成的 PDF 是需要动态渲染一些图形的(比如 Echarts 图表、思维导图、流程图等),所以只能在基于浏览器内核进行渲染的工具中进行选择。因为 whtmltopdf 使用的是老旧的 webkit 作为渲染内核,第一个就将其否决掉。

然后基于对 PyQt5 的熟悉,在 Windows 上使用 PyQt5 的 QWebengine 小部件对 HTML 文件进行 PDF 转换,测试效果还行。结果代码上传到 Linux 服务器上之后,各种系统依赖性的确实和报错。考虑到安装部署的便捷性,只能放弃这个测试了很久的方案。

最后转向了使用 Selenium 调用 Chromium 浏览器的无头模式,将打开的 HTML 打印导出为 PDF,算是比较完美地解决了觅道文档中文集导出 PDF 的问题。下面来看看最核心的实现过程:

依赖库

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import base64

配置 chrome 的启动参数

webdriver_options = Options()
webdriver_prefs = {}

webdriver_options.add_argument('--headless')
webdriver_options.add_argument('--disable-gpu')
webdriver_options.add_argument('--no-sandbox')
webdriver_options.add_argument('--disable-dev-shm-usage')
webdriver_options.experimental_options['prefs'] = webdriver_prefs
webdriver_prefs['profile.default_content_settings'] = {'images': 2}

实例化一个 Chrome

首先在 Selenium 中 实例化一个 Chrome 对象:

driver = webdriver.Chrome(executable_path=settings.CHROMIUM_DRIVER_PATH,options=webdriver_options)

然后请求 HTML 文件,path 为 HTML 文件路径,也可以为 url:

driver.get(path)

加载及处理

首先等待请求加载的完成:

WebDriverWait(driver, timeout).until(staleness_of(driver.find_element_by_tag_name('html')))

然后,配置一个用于打印命令的字典:

calculated_print_options = {
            'landscape': False,
            'displayHeaderFooter': False,
            'printBackground': True,
            'preferCSSPageSize': True,
        }

接着,获取 selenium 当前 session 的相关信息,使用让 Chrome 执行 Page.printToPDF 这一用于打印页面的命令:

resource = "/session/%s/chromium/send_command_and_get_result" % driver.session_id
    url = driver.command_executor._url + resource
    body = json.dumps({'cmd': 'Page.printToPDF', 'params': calculated_print_options })
    response = driver.command_executor._request('POST', url, body)

获取到最后的响应:

result = response.get('value')

最后将响应写入文件之中:

with open('report.pdf', 'wb') as file:
    file.write(result)

这样,就实现了 HTML 到 PDF 文件的转换。

模块调用

实际上,Pypi 中已经存在第三方模块实现了上述的流程,并且添加了 PDF 文件压缩的功能。通过如下命令即可安装使用:

pip install pyhtml2pdf

具体的使用方法详见:https://pypi.org/project/pyhtml2pdf/

上述实现的觅道文档代码位于:https://gitee.com/zmister/MrDoc/blob/master/app_doc/report_html2pdf.py

猜你也喜欢

发表评论

邮箱地址不会被公开。