【问题标题】:Convert doc/docx to pdf in AWS Lambda?在 AWS Lambda 中将 doc/docx 转换为 pdf?
【发布时间】:2020-08-27 22:53:54
【问题描述】:

试过了:

  • 预制 Lambda 应用程序 docx 到 pdf(应用程序不再可部署) https://github.com/NativeDocuments/docx-to-pdf-on-AWS-Lambda
  • 安装 comtypes.client 和 win32com.client(在 lambda 中部署后似乎都不起作用) 出现错误:无法导入模块“lambda_function”:无法导入名称“COMError”

可能性:

-当我从 s3 获取 doc 文件时,在 Browser JS 中将其转换为 PDF。 - 以某种方式修复部署包中的 comtypes 或 win32com。正在使用 Python 3.6。

import json
import urllib
import boto3
from boto3.s3.transfer import TransferConfig
from botocore.exceptions import ClientError
import lxml
import comtypes.client
import io
import os
import sys
import threading
from docx import Document

def lambda_handler(event, context):

    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')

    try:
        response = s3.get_object(Bucket=bucket, Key=key)

        # Creating the Document
        f = io.BytesIO(response['Body'].read())
        document = Document(f)

        //Code for formating my document object in this hidden section.

        document.save('/tmp/'+key)
        pdfkey = key.split(".")[0]+".pdf"

        //The following function is suppose to convert my doc to pdf
        doctopdf('/tmp/'+ key,'/tmp/'+pdfkey) 

        //PDF file is then saved to s3
        s3.upload_file('/tmp/'+pdfkey,'output',pdfkey)

    except exceptions as e:
        Logging.error(e)
        raise e

def doctopdf(in_file,out_file):
    word = comtypes.client.CreateObject('Word.Application')
    doc = word.Documents.Open(in_file)
    doc.SaveAs(out_file, FileFormat=wdFormatPDF)
    doc.Close()
    word.Quit()

【问题讨论】:

  • 需要将依赖打包到部署包中。

标签: amazon-web-services aws-lambda pdf-generation


【解决方案1】:

我还遇到了将 Word 文档 (doc/docx) 转换为 PDF 或任何其他文档类型的问题。我使用 AWS Lambda 中的子进程通过 LibreOffice 和 Python 3.8 解决了这个问题(也适用于 python 3.6 和 3.7)。

基本上,此设置将通过输入事件从 S3 中选择您的文件并将文件转换为 PDF 并将转换后的文件放入相同的 S3 位置。让我们来看看设置指南。

对于此设置,我们需要可通过 Lambda 访问的 LibreOffice 可执行文件。为此,我们将使用 Lambda 层。 现在,您有两个选择:

  1. 您可以create your own AWS Lambda layer 并上传layer.tar.br.zip(您可以从shelfio GitHub repository 下载此存档)
  2. 或者,您可以直接在 Lambda 中使用 Layer ARN。
    2.1。 Layer ARN for python3.6 and 3.7
    2.2. Layer ARN for python 3.8

是时候创建 Lambda(依赖包)了。

  1. 在您的 lambda 文件夹的根目录下创建 fonts/fonts.conf,并包含以下内容(假设 libreoffice 将被提取到 /tmp/instdir 目录下):
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<dir>/tmp/instdir/share/fonts/truetype</dir>
<cachedir>/tmp/fonts-cache/</cachedir>
<config></config>
</fontconfig>
  1. 将以下代码粘贴到您的 lambda_function.py 文件中:
import os
from io import BytesIO
import tarfile
import boto3
import subprocess
import brotli

libre_office_install_dir = '/tmp/instdir'

def load_libre_office():
    if os.path.exists(libre_office_install_dir) and os.path.isdir(libre_office_install_dir):
        print('We have a cached copy of LibreOffice, skipping extraction')
    else:
        print('No cached copy of LibreOffice exists, extracting tar stream from Brotli file.')
        buffer = BytesIO()
        with open('/opt/lo.tar.br', 'rb') as brotli_file:
            decompressor = brotli.Decompressor()
            while True:
                chunk = brotli_file.read(1024)
                buffer.write(decompressor.decompress(chunk))
                if len(chunk) < 1024:
                    break
            buffer.seek(0)

        print('Extracting tar stream to /tmp for caching.')
        with tarfile.open(fileobj=buffer) as tar:
            tar.extractall('/tmp')
        print('Done caching LibreOffice!')

    return f'{libre_office_install_dir}/program/soffice.bin'


def download_from_s3(bucket, key, download_path):
    s3 = boto3.client("s3")
    s3.download_file(bucket, key, download_path)


def upload_to_s3(file_path, bucket, key):
    s3 = boto3.client("s3")
    s3.upload_file(file_path, bucket, key)


def convert_word_to_pdf(soffice_path, word_file_path, output_dir):
    conv_cmd = f"{soffice_path} --headless --norestore --invisible --nodefault --nofirststartwizard --nolockcheck --nologo --convert-to pdf:writer_pdf_Export --outdir {output_dir} {word_file_path}"
    response = subprocess.run(conv_cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if response.returncode != 0:
        response = subprocess.run(conv_cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        if response.returncode != 0:
            return False
    return True


def lambda_handler(event, context):
    bucket = event["document_bucket"]
    key = event["document_key"]
    key_prefix, base_name = os.path.split(key)
    download_path = f"/tmp/{base_name}"
    output_dir = "/tmp"

    download_from_s3(bucket, key, download_path)

    soffice_path = load_libre_office()

    is_converted = convert_word_to_pdf(soffice_path, download_path, output_dir)
    if is_converted:
        file_name, _ = os.path.splitext(base_name)
        upload_to_s3(f"{output_dir}/{file_name}.pdf", bucket, f"{key_prefix}/{file_name}.pdf")
        return {"response": "file converted to PDF and available at same S3 location of input key"}
    else:
        return {"response": "cannot convert this document to PDF"}

  1. 从 Linux 环境构建(并在安装后从您的 Linux 环境中复制 site-packages/brotlibrotlipy 依赖项,因为目标 Lambda 运行时是 AmazonLinux。

最后,你的 lambda(依赖包)的目录结构应该是这样的:

.
+-- brotli/*
+-- fonts
|   +-- fonts.conf
+-- lambda_function.py

如果您的文件 s3 URI 是 s3://my-bucket-name/dir/file.docx,您可以使用以下输入事件来调用此 Lambda 处理程序:

{
    "document_bucket: "my-bucket-name"
    "document_key": "dir/file.docx"
}

干杯!如果您遇到任何问题,请告诉我,很乐意为您提供帮助:)

【讨论】:

  • IMO 函数 convert_word_to_pdf 不使用参数 executable_path,而是使用名为 soffice_path 的 var。所以参数应该重命名为soffice_path。或者,您可以在 conv_cmd 中使用 var executable_path
【解决方案2】:

不幸的是,我没有足够的声誉来简单地支持或评论 abhinav 的答案,但所有的功劳都归功于他的这个答案。

我按照他的指示使用 python 3.8,我将 ARN 用于我所在地区特定的 Lambda 层,它似乎可以正常运行。我将 brotlipy 安装到我的 ubuntu 子系统并创建了指定的文件夹结构。

我将他的 lambda_handler 函数稍微调整为以下内容,并为 lambda 添加了一个 S3 触发器。我发现原始函数会递归地触发自身,因为该函数正在将 pdf 写回触发该函数的 s3。下面的代码写入一个名为 'pdf.output' 的单独 s3 文件夹。

def lambda_handler(event, context):
bucket = event['Records'][0]['s3']['bucket']['name']
output_bucket = 'pdf.output'
key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
# bucket = event["document_bucket"]
# key = event["document_key"]
key_prefix, base_name = os.path.split(key)
download_path = f"/tmp/{base_name}"
output_dir = "/tmp"

download_from_s3(bucket, key, download_path)

soffice_path = load_libre_office()

is_converted = convert_word_to_pdf(soffice_path, download_path, output_dir)
if is_converted:
    file_name, _ = os.path.splitext(base_name)
    upload_to_s3(f"{output_dir}/{file_name}.pdf", output_bucket, f"{key_prefix}/{file_name}.pdf")
    return {"response": "file converted to PDF and available at same S3 location of input key"}
else:
    return {"response": "cannot convert this document to PDF"}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-05
    • 1970-01-01
    • 1970-01-01
    • 2020-12-09
    • 2011-06-16
    相关资源
    最近更新 更多