Linux下Python3.7实现URL的doc/docx实时转PDF方案问询
需求为抓取外部站点的.doc/.docx文件,转换为PDF格式后返回内容,后续会为返回内容添加content-type响应头,通过自有CMS发布,经CDN缓存后,在HTML页面使用Adobe PDF Embed API完成PDF展示。当前开发环境为Python 3.7,部署环境为Linux。
当前可运行的实现代码
def generate_pdf(): subprocess.call(['soffice', '--convert-to', 'pdf', 'https://arbitrary.othersite.com/anyfilename.docx']) sleep(1) myfile = open('anyfilename.pdf', 'rb') content = myfile.read() os.remove('anyfilename.pdf') return content
期望实现的逻辑
直接传入待转换文件的URL,调用转换命令后直接返回PDF内容,无需额外本地文件操作,理想逻辑代码如下:
def generate_pdf(url): result = subprocess.call(['soffice', '--convert-to', 'pdf', url]) content = result return content
现存问题
实际业务场景中待转换URL可能携带各类请求参数、包含非法路径字符,很难预判soffice转换后生成的本地PDF文件名;同时现有方案中休眠等待、写临时文件、读文件、删除文件的流程冗余、效率较低。
核心诉求:是否有可行方案,能够直接基于URL完成doc/docx到PDF的流式实时转换,省去临时文件读写、等待、删除的冗余流程?
LibreOffice(即命令行调用的soffice)本身不支持直接读取远程URL后流式输出PDF到标准输出,没法完全绕开临时文件,但可以通过规范临时文件处理逻辑,完全解决文件名不可控、冗余等待、手动删文件的问题,流程效率也能拉满,具体实现思路如下:
- 先把远程文件下载到系统临时目录,用固定规则生成临时文件名存储,完全规避URL带特殊字符、文件名不可预判的问题
- 调用soffice时用
--outdir参数明确指定输出到临时目录,转换过程用subprocess.run同步等待执行完成,不需要加固定sleep等待,转换完成直接就能拿到输出文件 - 读完PDF内容后,靠临时文件的自动清理机制删除源文件和生成的PDF,不需要手动处理删除逻辑,也不会残留垃圾文件
可直接运行的实现代码
import os import tempfile import subprocess import requests def generate_pdf(url): # 创建独立临时目录,用完自动整体清理,避免残留文件 with tempfile.TemporaryDirectory() as tmp_dir: # 下载远程doc/docx到临时目录,固定源文件后缀避免soffice识别失败 resp = requests.get(url, timeout=30) resp.raise_for_status() # 从响应头判断源文件后缀,默认给docx suffix = ".docx" content_type = resp.headers.get("content-type", "") if "msword" in content_type: suffix = ".doc" elif "officedocument.wordprocessingml" in content_type: suffix = ".docx" src_path = os.path.join(tmp_dir, f"src{suffix}") with open(src_path, "wb") as f: f.write(resp.content) # 调用soffice转换,指定输出到临时目录,同步等待执行完成,不需要sleep subprocess.run( [ "soffice", "--headless", "--nologo", "--nofirststartwizard", "--convert-to", "pdf", "--outdir", tmp_dir, src_path ], check=True, timeout=60 ) # 输出PDF路径固定,不需要猜文件名 pdf_path = os.path.join(tmp_dir, "src.pdf") with open(pdf_path, "rb") as f: return f.read()
优化说明
- 去掉了固定
sleep(1)的等待逻辑,用subprocess.run同步阻塞等转换进程退出,转换完成立刻读文件,不会浪费等待时间,也不会出现文件还没生成就读的问题 - 所有文件都存在随机生成的临时目录里,源文件和生成的PDF文件名完全可控,不需要处理URL带特殊字符、query参数导致输出文件名乱码/不可预判的问题
- 临时目录用
with上下文管理,代码执行完不管是成功还是报错,目录和里面的所有文件都会被系统自动清理,不需要手动写删除逻辑,也不会产生垃圾文件 - 加了必要的超时和异常判断,避免进程卡死、远程文件下载失败的问题
如果追求更高的转换性能,可以提前启动soffice的常驻服务模式,不需要每次转换都启动一次LibreOffice进程,转换速度能提升3-5倍,只需要把启动命令改成常驻服务模式,再用接口提交转换任务即可,本质的临时文件处理逻辑和上述代码一致。
注:不存在完全不落地临时文件的soffice转换方案,LibreOffice的转换逻辑本身就需要读写本地磁盘,上述方案已经把磁盘IO的冗余步骤全部去掉,临时文件都存在系统临时目录,读写性能和内存操作差异极小,完全满足实时转换的性能要求。
内容的提问来源于stack exchange,提问作者Ken

