Should I Segment Big PDF Before Document Analysis#

Keywords: AWS, Amazon, Textract

Overview#

在使用 Textract 的 Document Analysis 功能时, 如果输入的 PDF 的页数很多, 这时候我们会面临两种选择:

  1. 将整个 PDF 作为一个整体发给 Textract.

  2. 将 PDF 分拆成单页 PDF, 然后依次发给 Textract.

这是因为根据 Set Quotas, Textract 有单个 PDF 不能超过 500 MB / 3000 页 的限制. 根据 Default Quotas, 默认情况下最多支持 5 个并发的 Async Document Analysis Job. 而根据 Pricing Textract 是按照 Page 收费 (跟运行时长无关). 所以我们需要合理利用 Quota 和 API, 使得总处理速度最快.

下面我们通过一个实验来测试到底应该用哪个方法.

Experiment#

请阅读下列测试代码. 我们用的是一个 6 页的 W2 税表文件做测试. 分别是一次性提交, 和分拆成 6 页依次提交. 请阅读代码中的结论部分.

test_should_i_segment_big_pdf_before_document_analysis.py
  1# -*- coding: utf-8 -*-
  2
  3"""
  4Conclusion:
  5
  61. 对于同一个文件 Textract 处理的速度在不同时间可能不同. 这可能是因为异步 API 涉及到 AWS 服务端的调度, 导致不同时间的处理速度不同.
  72. 对于同一个文件, 一次性处理比分页后依次处理要快. 但是你要确保一次性处理的文件的大小不要太大.
  83. 你可以将同一个文件分拆成小文件, 然后并行调用 API 进行处理.
  9"""
 10
 11from datetime import datetime
 12from pathlib_mate import Path
 13from s3pathlib import S3Path, context
 14from boto_session_manager import BotoSesManager
 15import aws_textract.api as aws_textract
 16
 17dir_here = Path.dir_here(__file__)
 18path_fw2 = dir_here / "fw2.pdf"
 19path_page_list = [dir_here / f"fw2-{i}.pdf" for i in range(1, 6 + 1)]
 20
 21bsm = BotoSesManager(profile_name="bmt_app_dev_us_east_1")
 22context.attach_boto_session(bsm.boto_ses)
 23s3dir_root = S3Path(
 24    f"s3://{bsm.aws_account_alias}-{bsm.aws_region}-data/projects/tmp/"
 25).to_dir()
 26s3dir_input = s3dir_root.joinpath("input").to_dir()
 27s3dir_output = s3dir_root.joinpath("output").to_dir()
 28s3dir_root.delete()
 29
 30
 31def try_one_file():
 32    print(f"working on {path_fw2.basename} ...")
 33    s3path = s3dir_root.joinpath(path_fw2.basename)
 34    s3path.write_bytes(path_fw2.read_bytes())
 35    document_location, output_config = (
 36        aws_textract.better_boto.preprocess_input_output_config(
 37            input_bucket=s3path.bucket,
 38            input_key=s3path.key,
 39            input_version=None,
 40            output_bucket=s3dir_output.bucket,
 41            output_prefix=s3dir_output.key,
 42        )
 43    )
 44    start_time = datetime.now()
 45    res = bsm.textract_client.start_document_analysis(
 46        DocumentLocation=document_location,
 47        FeatureTypes=["FORMS"],
 48        OutputConfig=output_config,
 49    )
 50    job_id = res["JobId"]
 51    aws_textract.better_boto.wait_document_analysis_job_to_succeed(
 52        textract_client=bsm.textract_client,
 53        job_id=job_id,
 54        delays=1,
 55        timeout=300,
 56    )
 57    print("")
 58    end_time = datetime.now()
 59    elapsed = (end_time - start_time).total_seconds()
 60    print(f"One file elapsed: {elapsed:.6f}")
 61
 62
 63def try_many_file():
 64    for path in path_page_list:
 65        s3path = s3dir_root.joinpath(path.basename)
 66        s3path.write_bytes(path.read_bytes())
 67
 68    start_time = datetime.now()
 69    for path in path_page_list:
 70        print(f"working on {path.basename} ...")
 71        s3path = s3dir_root.joinpath(path.basename)
 72        document_location, output_config = (
 73            aws_textract.better_boto.preprocess_input_output_config(
 74                input_bucket=s3path.bucket,
 75                input_key=s3path.key,
 76                input_version=None,
 77                output_bucket=s3dir_output.bucket,
 78                output_prefix=s3dir_output.key,
 79            )
 80        )
 81        res = bsm.textract_client.start_document_analysis(
 82            DocumentLocation=document_location,
 83            FeatureTypes=["FORMS"],
 84            OutputConfig=output_config,
 85        )
 86        job_id = res["JobId"]
 87        aws_textract.better_boto.wait_document_analysis_job_to_succeed(
 88            textract_client=bsm.textract_client,
 89            job_id=job_id,
 90            delays=1,
 91            timeout=300,
 92        )
 93        print("")
 94
 95    end_time = datetime.now()
 96    elapsed = (end_time - start_time).total_seconds()
 97    print(f"Many file elapsed: {elapsed:.6f}")
 98
 99
100if __name__ == "__main__":
101    try_one_file()
102    try_many_file()
103    pass