TL;DR
我的工作流程:
- 下载PDF
- 使用将其拆分为页面pdftk https://linux.die.net/man/1/pdftk
- 使用提取每个页面的文本pdf转文本 https://linux.die.net/man/1/pdftotext
- 对文本进行分类并添加元数据
- 以结构化格式发送给客户
我需要提取一致的文本才能从 3 跳转到 4。如果文本是乱码,我必须对其页面进行 OCR。但是,OCR 所有页面是不可能的。如何预先确定哪些页面应该进行 ORed?我试过跑pdffonts https://linux.die.net/man/1/pdffonts and pdf转html https://linux.die.net/man/1/pdftohtml在每一页上。跑起来不是很贵吗subprocess.run
每页两次?
损坏的页面是什么意思?
PDF 页面无法从其源中提取文本,可能是由于 to_unicode 转换所致。
描述
我正在构建一个依赖于每天从一千个 PDF 文件中提取文本的应用程序。每个 PDF 中的文本布局都有些结构化,因此调用pdf转文本 https://linux.die.net/man/1/pdftotext在大多数情况下,来自 python 的效果都很好。但是,来自一两个资源的某些 PDF 文件会带来带有问题字体的页面,从而导致文本出现乱码。我认为仅在有问题的页面上使用 OCR 就可以解决这个问题。所以,我的问题是如何在提取文本之前识别哪些页面可能会导致乱码.
首先,我尝试在提取乱码后使用正则表达式(\p{Cc}
或拉丁字母之外不太可能的字符),但它不起作用,因为我发现带有有效字符和数字的损坏文本,即AAAAABS12 54c] $( JJJJ Pk
,还有。
二、我尝试识别乱码通话pdffonts https://linux.die.net/man/1/pdffonts- 识别每个页面上的 to_unicode 映射的名称、编码、嵌入性和存在性并解析其输出。在我的测试中,它效果很好。但我发现还需要计算有多少字符使用了可能有问题的字体,pdf转html https://linux.die.net/man/1/pdftohtml- 显示每个文本块p
标签及其字体名称 - 在这里拯救了这一天。 @LMC 帮助我弄清楚如何做到这一点,看看answer https://stackoverflow.com/a/68433055/6328506。糟糕的是我最后打了电话subprocess.run
每个pdf页两次,超级贵。如果我能绑定这些工具会更便宜 https://github.com/cbrunet/python-poppler/issues/36.
我想知道查看 PDF 源并验证一些 CMAP 是否可能且可行(uni
是的,而不是自定义字体)(如果存在),或者在提取文本或 OCR 之前查找有问题的字体的其他启发式方法。
我的一个 PDF 文件中的乱码文本示例:
0\n1\n2\n3\n4\n2\n0\n3\n0\n5 6\n6\nÿ\n89 ÿ\n4\n\x0e\n3\nÿ\n\x0f\x10\n\x11\n\x12\nÿ\n5\nÿ\n6\n6\n\x13\n\x11\n\x11\n\x146\n2\n2\n\x15\n\x11\n\x16\n\x12\n\x15\n\x10\n\x11\n\x0e\n\x11\n\x17\n\x12\n\x18\n\x0e\n\x17\n\x19\x0e\n\x1a\n\x16\n2 \x11\n\x10\n\x1b\x12\n\x1c\n\x10\n\x10\n\x15\n\x1d29 2\n\x18\n\x10\n\x16\n89 \x0e\n\x14\n\x13\n\x14\n\x1e\n\x14\n\x1f\n5 \x11\x1f\n\x15\n\x10\n! \x1c\n89 \x1f\n5\n3\n4\n"\n1\n1\n5 \x1c\n89\n#\x15\n\x1d\x1f\n5\n5\n1\n3\n5\n$\n5\n1 5\n2\n5\n%8&&#\'#(8&)\n*+\n\'#&*,\nÿ\n(*ÿ\n-\n./0)\n1\n*\n*//#//8&)\n*ÿ\n#/2#%)\n*,\nÿ\n(*/ÿ\n/#&3#40)\n*/ÿ\n#50&*-\n.()\n%)\n*)\n/ÿ\n+\nÿ\n*#/#\n&\x19\n\x12\nÿ\n\x1cÿ\n,\x1d\n\x12\n\x1b\x10\n\x15\n\x116\nÿ\n\x15\n7\nÿ\n8\n9\n4\n6\nÿ\n%\x10\n\x15\n\x11\n\x166\nÿ\n:\x12\x10;\n2\n*,\n%#26\nÿ\n<\n$\n3\n0\n3\n+\n3\n8\n3\nÿ\n+\nÿ\n=\x15\n\x10\n6\nÿ\n>\n9\n0\n?\nÿ\n4\n3\n3\n1\n+\n8\n9\n3\n<\n@A\nB\nC\nD\nEÿ\nGH\nI\nÿ\nJ\nJ\nK\nL\nJ\nM\nJ\nN\nO\nP\nO\nQ\nI\n#\x1bÿ\n0\n1\nÿ\n\x1c\n\x10\nÿ\n*\x1a\n\x16\n\x18\nÿ\n\x1c\n\x10\nÿ\n0\n3\n0\n5\n\x0e\n/\x10\n\x15\n\x13\x16\n\x12\nÿ\n/\x10\n\x16\n\x1d\x1c\x16\n\x12\n6\nÿ\n* \x19\n\x15\n\x116\nÿ\n\x12\n\x19\n\x11\n\x19\n\x12\n\x16\nÿ\n\x15ÿ\n/*-\n\x0e\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\n(\x10\nÿ\x16\n\x1c\n\x10\n\x1bÿ\n\x1c\n\x12\nÿ\n%\x13\n\x10\n9\n\x10\nÿ\n\x1c\n\x10\nÿ\n\'\x12\n\x1a\x15\n\x10\n\x11\n\x10\nÿ\n\x1c\n\x12\nÿ\n%\x16\n\x16\n\x10\nR\n\x10\n\x1c\x16\n\x12\nÿ\n\'\x10\n\x16\n\x12\n\x18\nÿ\n\x1c\n\x12\nÿ\n-\n\x19\x11\n1\n\x12\nÿ\n\x1cÿ\n#\x11\n\x12\n\x1cÿ\n\x1c\n\x10\nÿ\n*\x18\n\x12\nR\x126\nÿ\n/\x16\n\x12\n\x0e\n& \x10\n\x12\n\x15\n\x12\nÿ\n%\x10\n\x18\x11\n\x16\n\x10\nÿ\n:\x12\x13\n\x12\n\x1c\x0e\nÿ\n*\x19\n\x11\n\x19\n\x10\n+\x10\nÿ\n\x10\nÿ\n&\x10\nR\x11\n\x16\n\x10\n+\x10\nÿ\n\x15ÿ\n/*-\n2\n2\'<\nÿ\n+\nÿ\n#S\n\x11\n\x16\n\x12\n\x17\n\x19\n\x1c \x12\n\x18\nÿ\n*\x1c\n\x1b\x15\x11\n\x16\n\x12\n\x11\n\x1d\x0e\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\nÿ\n*\x11\n\x10\n\x15 \x12\n\x1b\x10\n\x15\n\x11\n\x10\n6\nTU\nV\nWU\nXÿ\nYXÿ\nTU\nV\nW\nX\nXYZU\n[U\nT\\]X\\U\nW\nX\nVD\n^\n_\n`\nÿ\nab\nÿ\nXGb\nc\nE^\nd\nO\nP\nO\nQ\nP\ne\nO\nf\nP\nf\nJ\nf\nP\ne\ng\nGb\nh_\nEGI\niaA\nYjTk\nXlm@ YjTk\nXlmX] ]jTk@[Yj] U\nZk]U\nZU\n] X]noU\nW\nX] W@V\n\\\nX]\nÿ\n89\nÿ\n89\np ÿ\nq\n(\x10\x14\n\x12\x13\n8r\nIOV\x11\x03\x14\n(VWH\x03GRFXPHQWR\x03p\x03FySLD\x03GR\x03RULJLQDO\x03DVVLQDGR\x03GLJLWDOPHQWH\x03SRU\x03(00$18(/$\x030$5,$\x03&$/$\'2\x03\'(\x03)$5,$6\x036,/9$\x11\x033DUD\x03FRQIHULU\x03R\x03RULJLQDO\x0f\x03DFHVVH\x03R\x03VLWH\x03\x0f\x03LQIRUPH\x03R\x03SURFHVVR\x03\x13\x13\x13\x13\x16\x17\x18\x10\x1a\x18\x11\x15\x13\x15\x14\x11\x1b\x11\x13\x15\x11\x13\x13\x1a\x16\x03H\x03R\x03\nFyGLJR\x03\x17(\x14\x14\x16\x14\x13\x11\x03
以上文字摘自本文第25页document http://tjdocs.tjgo.jus.br/documentos/584556使用pdftotext。
对于该页面, pdffonts 输出:
name type encoding emb sub uni object ID
------------------------------------ ----------------- ---------------- --- --- --- ---------
[none] Type 3 Custom yes no no 13 0
DIIDPF+ArialMT CID TrueType Identity-H yes yes yes 131 0
DIIEDH+Arial CID TrueType Identity-H yes yes no 137 0
DIIEBG+TimesNewRomanPSMT CID TrueType Identity-H yes yes yes 142 0
DIIEDG+Arial CID TrueType Identity-H yes yes no 148 0
Arial TrueType WinAnsi yes no no 159 0
很容易识别出这一点[none]
命名字体有问题。到目前为止,根据我分析的数据,我的看法是使用自定义或 Identity-h 编码来标记字体,没有 to_unicode 映射或没有命名为likely有问题的。但是,正如我所说,我还发现使用 ToUnicode 表而不是自定义编码字体的情况也有问题。据我所知,还可以找到例如为损坏字体定义的单个字符,但不会影响页面的整体可读性,因此也许不需要 OCR 该页面。换句话说,如果给定页面中的字体没有进行 ToUnicode 转换,并不意味着该页面的文本完全受到影响。
我正在寻找比正则表达式乱码更好的解决方案。
我必须进行 OCR 的 PDF 页面示例
以下所有页面都包含葡萄牙语文本,但如果您尝试复制文本并粘贴到某处,您会看到普遍的乱码。
- 第 146 页http://tjdocs.tjgo.jus.br/documentos/584544 http://tjdocs.tjgo.jus.br/documentos/584544
- 第 26、80、81、82、83 和 84 页http://tjdocs.tjgo.jus.br/documentos/584556 http://tjdocs.tjgo.jus.br/documentos/584556
- 第 23 页http://tjdocs.tjgo.jus.br/documentos/584589 http://tjdocs.tjgo.jus.br/documentos/584589
到目前为止我做了什么
自从我创建了一个 bash 脚本来迭代页面并将每个页面的 pdftohtml 和 pdffonts 输出合并到单个 HTML 中以来,我避免了在页面上调用 subprocess 两次:
#!/bin/sh
# Usage: ./font_report.sh -a 1 -b 100 -c foo.pdf
while getopts "a:b:c:" arg; do
case $arg in
a) FIRST_PAGE=$OPTARG;;
b) LAST_PAGE=$OPTARG;;
c) FILENAME=$OPTARG;;
*)
echo 'Error: invalid options' >&2
exit 1
esac
done
: ${FILENAME:?Missing -c}
if ! [ -f "$FILENAME" ]; then
echo "Error: $FILENAME does not exist" >&2
exit 1
fi
echo "<html xmlns='http://www.w3.org/1999/xhtml' lang='' xml:lang=''>" ;
for page in $(seq $FIRST_PAGE $LAST_PAGE)
do
{
echo "<page number=$page>" ;
echo "<pdffonts>" ;
pdffonts -f $page -l $page $FILENAME ;
echo "</pdffonts>" ;
(
pdftohtml -f $page -l $page -s -i -fontfullname -hidden $FILENAME -stdout |
tail -n +35 | # skips head tag and its content
head -n -1 # skips html ending tag
) ;
echo "</page>"
}
done
echo "</html>"
上面的代码使我能够调用 subprocess 一次并使用解析 htmllxml
对于每个页面(考虑到<page>
标签)。但仍然需要查看文本内容才能知道文本是否损坏。