问题描述
TL;DR
我的工作流程:
我需要提取一致的文本才能从3跳转到4。如果文本出现乱码,我必须对其页面进行OCR。但是,OCR 所有页面都不成问题。如何事先确定哪些页面应该进行 OCR?我尝试在每个页面上运行 pdffonts 和 pdftohtml。一个页面运行两次 subprocess.run
是不是很贵?
页面损坏是什么意思?
无法从其来源提取文本的 PDF 页面,可能是由于 to_unicode 转换。
说明
我正在构建一个依赖于每天从一千个 PDF 文件中提取文本的应用程序。每个 PDF 中的文本布局都有些结构化,因此在大多数情况下从 python 调用 pdftotext 效果很好。但是,来自一两个资源的某些 PDF 文件会带来带有问题字体的页面,从而导致文本出现乱码。我认为只在有问题的页面上使用 OCR 可以解决这个问题。所以,我的问题是如何在提取文本之前识别哪些页面可能会导致乱码。
首先,我尝试在提取后使用正则表达式(\p{Cc}
或拉丁字母表之外的不太可能的字符)识别乱码文本,但它不起作用,因为我发现带有有效字符和数字的损坏文本,即 { {1}},以及。
其次,我尝试识别每个页面上调用 pdffonts 的乱码文本 - 识别名称、编码、嵌入性和 to_unicode 映射的存在 - 并解析其输出。在我的测试中,它运行良好。但我发现还需要计算有多少字符使用了可能有问题的字体,pdftohtml - 在 AAAAABS12 54c] $( JJJJ Pk
标签中显示每个文本块及其字体名称 - 在这里节省了一天。 @LMC 帮助我弄清楚如何去做,看看 answer。不好的部分是我最终为每个 pdf 页面调用了两次 p
,这是非常昂贵的。 It would be cheaper if I could just bind those tools。
我想知道查看 PDF 源代码并验证某些 CMAP(subprocess.run
是,不是自定义字体)(如果存在)或其他启发式方法在提取文本之前查找有问题的字体是否可行且可行或OCR。
uni
以上文本是使用 pdftotext 从本 document 的第 25 页中提取的。
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
很容易将 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
命名的字体识别为有问题。到目前为止,鉴于我分析的数据,我的看法是使用自定义或身份 h 编码标记字体,没有 to_unicode 映射或没有命名为可能有问题的字体。但是,正如我所说,我也发现使用 ToUnicode 表而不是自定义编码字体的情况也有问题。据我所知,也可以找到,例如,为损坏的字体定义的单个字符,但不影响页面的整体可读性,因此可能不需要对该页面进行 OCR。换句话说,如果给定页面中的字体没有ToUnicode转换,并不意味着该页面的文本完全受到影响。
我正在寻找比正则表达式乱码更好的解决方案。
我必须进行 OCR 的 PDF 页面示例
以下所有页面都包含葡萄牙语文本,但如果您尝试复制文本并粘贴到某处,您会看到普遍的胡言乱语。
- 第 146 页,共 http://tjdocs.tjgo.jus.br/documentos/584544
- http://tjdocs.tjgo.jus.br/documentos/584556 的第 26、80、81、82、83 和 84 页
- 第 23 页,共 http://tjdocs.tjgo.jus.br/documentos/584589
到目前为止我做了什么
因为我创建了一个 bash 脚本来迭代页面并将每个页面的 pdftohtml 和 pdffonts 输出合并到一个 HTML 中,所以我避免在一个页面上调用两次子进程:
[none]
上面的代码使我能够调用一次子进程并使用 #!/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>"
为每个页面解析 html(考虑 lxml
标记)。但是还是需要看文字内容才知道是不是文字坏了。
解决方法
基于pdftotext
的快速bash函数
这是一个完整的(重写)功能扫描坏页:
#!/bin/bash
findBadPages() {
local line opts progress=true usage="Usage: ${FUNCNAME[0]} [-f first page]"
usage+=' [-l last page] [-m min bad/page] [-q (quiet)]'
local -a pdftxt=(pdftotext -layout - -)
local -ia badpages=()
local -i page=1 limit=10 OPTIND
while getopts "ql:f:m:" opt;do
case $opt in
f ) pdftxt+=(-f $OPTARG); page=$OPTARG ;;
l ) pdftxt+=(-l $OPTARG) ;;
m ) limit=$OPTARG ;;
q ) progress=false ;;
* ) printf >&2 '%s ERROR: Unknown option!\n%s\n' \
"${FUNCNAME[0]}" "$usage";return -1 ;;
esac
done
shift $((OPTIND-1))
while IFS= read -r line; do
[ "$line" = $'\f' ] && page+=1 && $progress && printf %d\\r $page
((${#line} > 1 )) && badpages[page]+=${#line}
done < <(
tr -d '0-9a-zA-Z\047"()[]{}<>,-./+?!$&@#:;%$=_ºÁÃÇÔàáâãçéêíóôõú– ' < <(
"${pdftxt[@]}" <"$1"
))
for page in ${!badpages[@]} ;do
(( ${badpages[page]} > limit )) && {
$progress && printf "There are %d strange characters in page %d\n" \
${badpages[page]} $page || echo $page ;}
done
}
那么现在:
findBadPages DJE_3254_I_18062021\(1\).pdf
There are 2237 strange characters in page 23
There are 258 strange characters in page 24
There are 20 strange characters in page 32
findBadPages -m 100 -f 40 -l 100 DJE_3254_I_18062021.pdf
There are 623 strange characters in page 80
There are 1068 strange characters in page 81
There are 1258 strange characters in page 82
There are 1269 strange characters in page 83
There are 1245 strange characters in page 84
There are 256 strange characters in page 85
findBadPages DJE_3254_III_18062021.pdf
There are 11 strange characters in page 125
There are 635 strange characters in page 145
findBadPages -qm100 DJE_3254_III_18062021.pdf
145
findBadPages -h
/bin/bash: illegal option -- h
findBadPages ERROR: Unknown option!
Usage: findBadPages [-f first page] [-l last page] [-m min bad/page] [-q (quiet)]
用法:
findBadPages [-f INTEGER] [-l INTEGER] [-m INTEGER] [-q] <pdf file>
哪里
-
-f
让您指定第一页。 -
-l
用于最后一页。 -
-m
表示每页找到的最少 奇怪 个字符来打印状态。 -
-q
标志在进程中禁止显示页码,然后只显示坏页 编号。
注意:
tr -d
使用的字符串:0-9a-zA-Z\047"()[]{}<>,-./:;%$=_ºÁÃÇÔàáâãçéêíóôõú–
是通过对 PDF 文件中使用的字符进行排序而构建的!他们无法匹配另一种语言!也许在未来的某些用途中,可能需要添加一些重音字符或其他遗漏的可打印字符。
@mkl 可能会说使用单词字典搜索的方法
我尝试了不同的方法来查看在您最小的第三个示例中检测两个坏页的一些简单方法,并怀疑它很容易被具有好的和坏文本的页面击败,因此这几乎可以肯定不是一个完整的答案需要更多的传球来完善。
我们必须接受您提出的问题,即每个 PDF 的质量未知,但需要进行处理。所以接受我们希望大多数页面都很好的事实,我们盲目地度过了爆发阶段。
一个非常常见的单词结构可以包含 3 个字母的音节“est” 因此,如果我们搜索突发文件,我们会发现 Page 23 和 Page 24 中缺少这些文件,因此它们很可能是损坏的候选对象。
同样,对于 855 页的文件,您说 Page 146 是一个问题(通过我之前的仅损坏页面的搜索方法确认,包含���,只是那个已损坏)但现在我们可以很容易地看到前 40 页
页面当然也需要OCR(包括那些只有图像的)
第 4、5、8、9、10、35 页(35 是一个很奇怪的页面?BG 图像?)
但是我们得到了 2 个第 19、33 页的误报(确实有文本但没有 est 或 EST)
和 20,32,38 有 EST 所以搜索需要不区分大小写
SO 使用不区分大小写的搜索无需修改我们应该得到 95% 的置信度(40 次中有 2 次错误)需要 OCR 但我没有深入测试为什么小写 est
只返回 275从总数 855 中删除,除非稍后有非常高百分比的图像需要 OCR。
我之前曾建议通过查找 ??????? 更快地搜索第三个 6054 页面文件。这给了我们一个更不稳定的使用结果,但确实显示损坏是从 25 到 85 的所有文本页面
那会导致什么?
实际上很少有人使用???
损坏的页面通常包含 ???或 ���
在葡萄牙语中,损坏的页面不太可能包含 /I est
部分损坏的页面可能包含用于 OCR 的大图像,并且est
与否
一些损坏的页面将不是上述情况
,由于这也是(或主要是)性能问题,我建议将您的代码修改为更多的多线程解决方案或简单地使用 GNU Parallel
关于它的好文章 -> link
,尝试使用其他模块以正确提取文本;我建议使用 PyPDF2。
这是一个可以解决问题的函数:
import PyPDF2
def extract_text(filename,page_number):
# Returns the content of a given page
pdf_file_object = open(filename,'rb')
pdf_reader = PyPDF2.PdfFileReader(pdf_file_object)
# page_number - 1 below because in python,page 1 is considered as page 0
page_object = pdf_reader.getPage(page_number - 1)
text = page_object.extractText()
pdf_file_object.close()
return text
顺便说一下,PyPDF2 不是 Python 中的预装模块。要安装它,请安装 pip(尽管这点很可能已经完成)并通过命令行运行“pip install PyPDF2”。