使用不正确的结构 OCR 从 PDF 中提取数据
Extract Data from PDF with Incorrect Structural OCR
我经常收到发票 pdf。
我从这些 pdf 中提取数据以进行各种操作和存储。
这是一个示例部分:
第一步是使用Adobe的OCR。
然后,我使用 tika 来解析 pdf。
在 Python:
from tika import parser
parsedPDF = parser.from_file("the_file.pdf")
这是预期的输出:
...
001 6 0 6 EA FSC450-WBKR FUTSAL, ADULT, WHT/BLK/RED BULK \n\n
002 6 0 6 EA SS50-P SOCCER PURPLE/BLUE/WHITE BULK \n\n
...
行由换行符分隔,您在 pdf 中看到的行被解析为完整行(见下文)。
这是实际输出:
001 6 0 6 \n\n
002 6 0 6 \n\n
003 13 0 13 \n\n
004 3 0 3 \n\n
EA FSC450-WBKR FUTSAL, ADULT, WHT/BLK/RED BULK \n\n
EA SS50-P SOCCER PURPLE/BLUE/WHITE BULK \n\n
...
OCR 创建了一个结构,您在 pdf 上看到的行被分成两部分[*注意]。拆分发生在 "Shipped" 和 "Unit" 标题之间。
对于项目 002,如果我从“#”标题拖动到 "Packaging" 标题,它首先选择第一部分下方的数据,然后跳转到第二部分的顶部。
这个问题有好的解决办法吗?
有没有一种方法可以为 OCR 定义结构(例如,它读取一行作为一行?)
[*注意]:实际上是文字被垂直包裹(与通常看到的水平文字包裹相比)。
与其尝试重铸数据,不如使用现有数据。您得到两组线,第一组包含数据行的左半部分,第二组包含右半部分。 itertools.groupby
非常适合按某些分组标准拆分行。在这种情况下,您可以看出左半行都以数字开头,而右半行不是。
将它们分成大小相等的两个组后,使用 Python 的内置方法 zip
将它们重新组合在一起。然后一连串的split()
就可以帮你解析每一行的内容——看下面代码中的注释:
from itertools import groupby
lines = """
001 6 0 6
002 6 0 6
003 13 0 13
004 3 0 3
EA FSC450-WBKR FUTSAL, ADULT, WHT/BLK/RED BULK
EA SS50-P SOCCER PURPLE/BLUE/WHITE BULK
EA SS30-G SOCCER BALL GREEN/WHITE #3 BULK
EA VQ2000-RGW COMPOSITE VB ROYAL/GOLD/WHITE BULK
""".splitlines()
# filter out empty lines
lines = filter(None, lines)
# use groupby to walk the list, and get the lines that start with
# numbers vs those that don't - from your description, there should be
# two groups
groups = []
for _, grouplines in groupby(lines, key=lambda ll : ll[0].isdigit()):
groups.append(list(grouplines))
# validate the input - should be two groups of line, each the same length
assert len(groups) == 2
assert len(groups[0]) == len(groups[1])
# use zip to walk the two groups together, and create list of consolidated data
consolidated = [left + right for left,right in zip(groups[0], groups[1])]
# now break these strings up into their various pieces, using a succession of split()s
parsed_lines = []
for cons_line in consolidated:
left_items = cons_line.split(None, 4)
right_items = left_items.pop(-1).rsplit(None,1)
right_items, qty_type = right_items
um, desc = right_items.split(None, 1)
parsed_lines.append(list(map(int,left_items) + [um, desc, qty_type]))
# dump out the parsed lines
for data in parsed_lines:
print(data)
给出:
[1, 6, 0, 6, 'EA', 'FSC450-WBKR FUTSAL, ADULT, WHT/BLK/RED', 'BULK']
[2, 6, 0, 6, 'EA', 'SS50-P SOCCER PURPLE/BLUE/WHITE', 'BULK']
[3, 13, 0, 13, 'EA', 'SS30-G SOCCER BALL GREEN/WHITE #3', 'BULK']
[4, 3, 0, 3, 'EA', 'VQ2000-RGW COMPOSITE VB ROYAL/GOLD/WHITE', 'BULK']
我经常收到发票 pdf。 我从这些 pdf 中提取数据以进行各种操作和存储。
这是一个示例部分:
第一步是使用Adobe的OCR。 然后,我使用 tika 来解析 pdf。 在 Python:
from tika import parser
parsedPDF = parser.from_file("the_file.pdf")
这是预期的输出:
...
001 6 0 6 EA FSC450-WBKR FUTSAL, ADULT, WHT/BLK/RED BULK \n\n
002 6 0 6 EA SS50-P SOCCER PURPLE/BLUE/WHITE BULK \n\n
...
行由换行符分隔,您在 pdf 中看到的行被解析为完整行(见下文)。
这是实际输出:
001 6 0 6 \n\n
002 6 0 6 \n\n
003 13 0 13 \n\n
004 3 0 3 \n\n
EA FSC450-WBKR FUTSAL, ADULT, WHT/BLK/RED BULK \n\n
EA SS50-P SOCCER PURPLE/BLUE/WHITE BULK \n\n
...
OCR 创建了一个结构,您在 pdf 上看到的行被分成两部分[*注意]。拆分发生在 "Shipped" 和 "Unit" 标题之间。
对于项目 002,如果我从“#”标题拖动到 "Packaging" 标题,它首先选择第一部分下方的数据,然后跳转到第二部分的顶部。
这个问题有好的解决办法吗? 有没有一种方法可以为 OCR 定义结构(例如,它读取一行作为一行?)
[*注意]:实际上是文字被垂直包裹(与通常看到的水平文字包裹相比)。
与其尝试重铸数据,不如使用现有数据。您得到两组线,第一组包含数据行的左半部分,第二组包含右半部分。 itertools.groupby
非常适合按某些分组标准拆分行。在这种情况下,您可以看出左半行都以数字开头,而右半行不是。
将它们分成大小相等的两个组后,使用 Python 的内置方法 zip
将它们重新组合在一起。然后一连串的split()
就可以帮你解析每一行的内容——看下面代码中的注释:
from itertools import groupby
lines = """
001 6 0 6
002 6 0 6
003 13 0 13
004 3 0 3
EA FSC450-WBKR FUTSAL, ADULT, WHT/BLK/RED BULK
EA SS50-P SOCCER PURPLE/BLUE/WHITE BULK
EA SS30-G SOCCER BALL GREEN/WHITE #3 BULK
EA VQ2000-RGW COMPOSITE VB ROYAL/GOLD/WHITE BULK
""".splitlines()
# filter out empty lines
lines = filter(None, lines)
# use groupby to walk the list, and get the lines that start with
# numbers vs those that don't - from your description, there should be
# two groups
groups = []
for _, grouplines in groupby(lines, key=lambda ll : ll[0].isdigit()):
groups.append(list(grouplines))
# validate the input - should be two groups of line, each the same length
assert len(groups) == 2
assert len(groups[0]) == len(groups[1])
# use zip to walk the two groups together, and create list of consolidated data
consolidated = [left + right for left,right in zip(groups[0], groups[1])]
# now break these strings up into their various pieces, using a succession of split()s
parsed_lines = []
for cons_line in consolidated:
left_items = cons_line.split(None, 4)
right_items = left_items.pop(-1).rsplit(None,1)
right_items, qty_type = right_items
um, desc = right_items.split(None, 1)
parsed_lines.append(list(map(int,left_items) + [um, desc, qty_type]))
# dump out the parsed lines
for data in parsed_lines:
print(data)
给出:
[1, 6, 0, 6, 'EA', 'FSC450-WBKR FUTSAL, ADULT, WHT/BLK/RED', 'BULK']
[2, 6, 0, 6, 'EA', 'SS50-P SOCCER PURPLE/BLUE/WHITE', 'BULK']
[3, 13, 0, 13, 'EA', 'SS30-G SOCCER BALL GREEN/WHITE #3', 'BULK']
[4, 3, 0, 3, 'EA', 'VQ2000-RGW COMPOSITE VB ROYAL/GOLD/WHITE', 'BULK']