使用Python文本抓取PDF(pdfquery)

时间:2018-10-06 20:36:19

标签: python pdf pdfminer

我需要抓取一些PDF文件以提取以下文本信息:

enter image description here

我试图通过处理在Reddit上找到的示例来使用pdfquery来做到这一点(请参阅第一篇文章):https://www.reddit.com/r/Python/comments/4bnjha/scraping_pdf_files_with_python/

我想通过获取许可证编号来对其进行测试。我进入生成的“ xmltree”文件,找到第一个许可证号,并在LTTextLineHorizo​​ntal元素中获得了x0,y0,x1,y1坐标。

import pdfquery
from lxml import etree


PDF_FILE = 'C:\\TEMP\\ad-4070-20-september-2018.pdf'

pdf = pdfquery.PDFQuery(PDF_FILE)
pdf.load(4,5)

with open('xmltree.xml','wb') as f:
    f.write(etree.tostring(pdf.tree, pretty_print=True))

product_info = []
page_count = len(pdf._pages)
for pg in range(page_count):
    data = pdf.extract([
        ('with_parent', 'LTPage[pageid="{}"]'.format(pg+1)),
        ('with_formatter', None),
        ('product_name', 'LTTextLineHorizontal:in_bbox("89.904, 757.502, 265.7, 770.83")'),
        ('product_details', 'LTTextLineHorizontal:in_bbox("223, 100, 737, 1114")'),
    ])
    for ix, pn in enumerate(sorted([d for d in data['product_name'] if d.text.strip()], key=lambda x: x.get('y0'), reverse=True)):
        product_info.append({'Manufacturer': pn.text.strip(), 'page': pg, 'y_start': float(pn.get('y1')), 'y_end': float(pn.get('y1'))-150})
        # if this is not the first product on the page, update the previous product's y_end with a
        # value slightly greater than this product's y coordinate start
        if ix > 0:
            product_info[-2]['y_end'] = float(pn.get('y0'))
    # for every product found on this page, find the detail information that falls between the
    # y coordinates belonging to the product
    for product in [p for p in product_info if p['page'] == pg]:
        details = []
        for d in sorted([d for d in data['product_details'] if d.text.strip()], key=lambda x: x.get('y0'), reverse=True):
            if  product['y_start'] > float(d.get('y0')) > product['y_end']:
                details.append(d.text.strip())
        product['Details'] = ' '.join(details)
pdf.file.close()

for p in product_info:
    print('Manufacturer: {}\r\nDetail Info:{}...\r\n\r\n'.format(p['Manufacturer'], p['Details'][0:100]))

但是,当我运行它时,它不会打印任何内容。没有错误,XML文件生成良好,而且我直接从XML文件获取坐标,因此应该没有问题。我在做什么错了?

2 个答案:

答案 0 :(得分:0)

要从PDF文件中提取文本,我最喜欢的工具是pdftotext

使用-layout选项,您基本上会获得纯文本,这相对容易使用Python进行操作。

以下示例:

"""Extract text from PDF files.

Requires pdftotext from the poppler utilities.
On unix/linux install them using your favorite package manager.

Binaries for ms-windows can be found at;
1) http://blog.alivate.com.au/poppler-windows/
2) https://sourceforge.net/projects/poppler-win32/
"""

import subprocess


def pdftotext(pdf, page=None):
    """Retrieve all text from a PDF file.

    Arguments:
        pdf Path of the file to read.
        page: Number of the page to read. If None, read all the pages.

    Returns:
        A list of lines of text.
    """
    if page is None:
        args = ['pdftotext', '-layout', '-q', pdf, '-']
    else:
        args = ['pdftotext', '-f', str(page), '-l', str(page), '-layout',
                '-q', pdf, '-']
    try:
        txt = subprocess.check_output(args, universal_newlines=True)
        lines = txt.splitlines()
    except subprocess.CalledProcessError:
        lines = []
    return lines

答案 1 :(得分:0)

我刚刚从您的Reddit链接中运行了代码,效果很好。虽然我没有确切的PDF文档,但我相信您的bbox参数不正确。在特定情况下,您使用

('product_name', 'LTTextLineHorizontal:in_bbox("89.904, 757.502, 265.7, 770.83")'),

但您应该使用

('product_name', 'LTTextLineHorizontal:in_bbox("88, 756, 267, 772")'),

('product_name', 'LTTextLineHorizontal:overlaps_bbox("89.904, 757.502, 265.7, 770.83")'),

因为“ in_bbox”要求文本确实适合此框,而“ overlaps_bbox”需要文本仅与该框重叠。与“ product_details”相同。 请注意,Reddit链接中脚本的作者使用了第一个选项。