Go 原生解析 pdf 库与 Apache Tika 的比较

1、概述

目前项目采用 go 语言进行编写,有对 pdf 进行抽取文字的需求,目前采用了 Apache 的 Tika 作为 pdf 的解析服务。出于多尝试的目的,考察一下 go 在解析 pdf 方面的能力。

2、调研的库

初始锁定的目标有:

https://github.com/rsc/pdf
https://github.com/yob/pdfreader
https://github.com/hhrutter/pdfcpu
https://github.com/ledongthuc/pdf
https://github.com/unidoc/unidoc
https://github.com/sajari/docconv
https://github.com/jung-kurt/gofpdf
https://github.com/signintech/gopdf
https://github.com/mikeshimura/goreport

在调研过程中,发现绝大多数的用 go 语言编写的与 pdf 相关库均是 pdf 的生成器,能够进行解析 pdf 的库如下:

https://github.com/rsc/pdf
https://github.com/sajari/docconv
https://github.com/unidoc/unidoc
https://github.com/ledongthuc/pdf
https://github.com/hhrutter/pdfcpu
https://github.com/yob/pdfreader

在进一步的分析过程中,ledongthuc/pdf 实际是 rsc/pdf 的在封装,pdfcpu 则采用的 cmd 命令行模式,pdfreader 采用的则是对 pdf 抽取成新文档的模式,参见:https://code.google.com/archive/p/pdfreader/downloads; sajari/docconv 则在其 issue 中申明目前真该尝试解析 pdf ,参见:https://github.com/sajari/docconv/issues/38
综合下来,最终需要对比的库为如下:

https://github.com/unidoc/unidoc
https://github.com/ledongthuc/pdf

3、对比

下面将从抽取的准确性、解析速度方面进行对比。
解析对比的 pdf 文件介入如下:


测试 pdf 1

测试 pdf 2

全英文字母解析测试

tika

采用 github.com/google/go-tika/tika 库进行,tika 的解析结果如下:

Examples

Multiple examples are provided in our example repository. Many features for

processing PDF files with documented examples on our website.

Contact us if you need any specific examples.

Vendoring

For reliability, we recommend using specific versions and the vendoring

capability of golang. Check out the Releases section to see the tagged releases.

Licensing Information

This library (UniDoc) has a dual license, a commercial one suitable for closed

source projects and an AGPL license that can be used in open source software.

Depending on your needs, you must choose one of them and follow its policies.

A detail of the policies and agreements for each license type are available in

the LICENSE.COMMERCIAL and LICENSE.AGPL files.

In brief, purchasing a license is mandatory as soon as you develop activities

distributing the UniDoc software inside your product or deploying it on a network

without disclosing the source code of your own applications under the AGPL

license. These activities include:

https://unidoc.io/examples
https://www.ctolib.com/unidoc-unidoc.html
https://www.ctolib.com/unidoc-unidoc.html
https://www.ctolib.com/unidoc/unidoc/blob/master/LICENSE.COMMERCIAL
https://www.ctolib.com/unidoc/unidoc/blob/master/LICENSE.AGPL


 offering services as an application service provider or over-network

application programming interface (API)

 creating/manipulating documents for users in a web/server/cloud

application

 shipping UniDoc with a closed source product

Please see pricing to purchase a commercial license or contact sales

at sales@unidoc.io for more info.

http://unidoc.io/pricing
mailto:sales@unidoc.io

ledongthuc/pdf

解析代码如下:

func extractText(path string) (string, error) {
    f, r, err := pdf.Open(path)
    // remember close filuilder bytee
    defer f.Close()
    if err != nil {
        return "", err
    }
    var buf bytes.Buffer
    b, err := r.GetPlainText()
    if err != nil {
        return "", err
    }
    buf.ReadFrom(b)
    return buf.String(), nil
}

上述是不含格式的纯抽取文本的代码,抽取结果如下:

ExamplesMultipleexamplesareprovidedinourexamplerepository.ManyfeaturesforprocessingPDFfileswithdocumentedexamplesonourwebsite.Contactusifyouneedanyspecificexamples.VendoringForreliability,werecommendusingspecificversionsandthevendoringcapabilityofgolang.CheckouttheReleasessectiontoseethetaggedreleases.LicensingInformationThislibrary(UniDoc)hasaduallicense,acommercialonesuitableforclosedsourceprojectsandanAGPLlicensethatcanbeusedinopensourcesoftware.Dependingonyourneeds,youmustchooseoneofthemandfollowitspolicies.AdetailofthepoliciesandagreementsforeachlicensetypeareavailableintheLICENSE.COMMERCIALandLICENSE.AGPLfiles.Inbrief,purchasingalicenseismandatoryassoonasyoudevelopactivitiesdistributingtheUniDocsoftwareinsideyourproductordeployingitonanetworkwithoutdisclosingthesourcecodeofyourownapplicationsundertheAGPLlicense.Theseactivitiesinclude:offeringservicesasanapplicationserviceproviderorover-networkapplicationprogramminginterface(API)creating/manipulatingdocumentsforusersinaweb/server/cloudapplicationshippingUniDocwithaclosedsourceproductPleaseseepricingtopurchaseacommerciallicenseorcontactsalesatsales@unidoc.ioformoreinfo.

如上可以看出,ledongthuc/pdf 在纯文本抽取的情况下,不包含空格和换行符,在抽取效果上不如 tika;
ledongthuc/pdf 还提供了一种包含 pdf 的格式信息,如字体、字号、文字位置等相关信息的抽取方法,
代码如下:

func extractText2(path string) (string, error) {
    f, err := pdf.Open(path)
    if err != nil {
        return "", err
    }

    pages := f.NumPage()
    var content string
    for i := 1; i <= pages; i++ {
        p := f.Page(i)
        if err != nil {
            return "", err
        }
        content += fmt.Sprintf("%+v", p.Content())
    }
    return content, nil
}

其解析结果如下:

{Text:[{Font:SymbolMT FontSize:9.950000000000001 X:108 Y:750.65 W:0 S:} {Font:SymbolMT FontSize:9.950000000000001 X:108 Y:750.65 W:0 S:x} {Font:FreeSans FontSize:12 X:126 Y:750.65 W:0 S:} {Font:FreeSans FontSize:12 X:126 Y:750.65 W:0 S:R} {Font:FreeSans FontSize:12 X:132.3594 Y:750.65 W:0 S:} {Font:FreeSans FontSize:12 X:132.3594 Y:750.65 W:0 S:I} {Font:FreeSans FontSize:12 X:135.7188 Y:750.65 W:0 S:} {Font:FreeSans FontSize:12 X:135.7188 Y:750.65 W:0 S:I} {Font:FreeSans FontSize:12 X:139.07819999999998 Y:750.65 W:0 S:} {Font:FreeSans FontSize:12 X:139.07819999999998 Y:750.65 W:0 S:H} {Font:FreeSans FontSize:12 X:145.43759999999997 Y:750.65 W:0 S:} {Font:FreeSans FontSize:12 X:145.43759999999997 Y:750.65 W:0 S:U} {Font:FreeSans FontSize:12 X:149.39699999999996 Y:750.65 W:0 S:} {Font:FreeSans FontSize:12 X:149.39699999999996 Y:750.65 W:0 S:L} {Font:FreeSans FontSize:12 X:152.15639999999996 Y:750.65 W:0 S:} {Font:FreeSans FontSize:12 X:152.15639999999996 Y:750.65 W:0 S:Q}...

p.Content 返回的数据结构如下:

type Content struct {
    Text []Text
    Rect []Rect
}

type Text struct {
    Font     string  // the font used
    FontSize float64 // the font size, in points (1/72 of an inch)
    X        float64 // the X coordinate, in points, increasing left to right
    Y        float64 // the Y coordinate, in points, increasing bottom to top
    W        float64 // the width of the text, in points
    S        string  // the actual UTF-8 text
}

Text 中的 S 才是对应的文本,不过其是按照字符进行拆解的,从抽取的结果上看,其抽取结果不准确,输出的头几个 Text 结构体中均未出现 Example 的完整表达。

unidoc

unidoc 的相关文档参见:https://unidoc.io/examples/text/extract-text-in-pdf
其实际抽取效果如下:

(
[
D
P
S
O
H
V0
X
O
W
L
S
O
H   H
[
D
......

从抽取结果上来看,完全不满足使用需求。

包含中文的问题抽取

tika

tika 的效果如下:

路由设置�-�beego:�简约�&�强⼤并存的�Go�应⽤框架

https://beego.me/docs/mvc/controller/router.md 1/10

到�GitHub�上改进本⻚⾯�(https://github.com/beego/beedoc/blob/master/zh-CN/mvc/controller/router.md)

⾃定义搜索

路由设置

什么是路由设置呢?前⾯介绍的�MVC�结构执⾏时,介绍过�beego�存在三种⽅式的路由:固定路由、正则路由、
⾃动路由,接下来详细的讲解如何使⽤这三种路由。

基础路由

从�beego�1.2�版本开始⽀持了基本的�RESTful�函数式路由,应⽤中的⼤多数路由都会定义在� routers/router.go
⽂件中。最简单的�beego�路由由�URI�和闭包函数组成。

基本�GET�路由

beego.Get("/",func(ctx�*context.Context){�

�����ctx.Output.Body([]byte(�hello�world�))�

})

基本�POST�路由

beego.Post(�/alice�,func(ctx�*context.Context){�

�����ctx.Output.Body([]byte(�bob�))�

})

注册⼀个可以响应任何�HTTP�的路由

beego.Any(�/foo�,func(ctx�*context.Context){�

�����ctx.Output.Body([]byte(�bar�))�

})

所有的⽀持的基础函数如下所⽰

https://github.com/beego/beedoc/blob/master/zh-CN/mvc/controller/router.md


2018/11/23 路由设置�-�beego:�简约�&�强⼤并存的�Go�应⽤框架

基本的中文字符已经抽取出来,部分特殊字符显示上存在问题

ledongthuc/pdf

由于上述全英文的对比过程中,包含字体、字号的全解析方法效果不好,故只比对纯文本的抽取:

¹¶CFFHPG    Êõ&(P¨¹ÌIUUQTFFHPNFEPDTNWDDPOUSPMMFSSPVUFSNEy(JU)VC¥÷      IUUQTJUIVCDPNCFFHPCFFEPDCMPCNBTUFS[I$/NWDDPOUSPMMFSSPVUFSNE
Ëö
GVODìDUY<ö½SPVUFSTSPVUFSHP"Ët¤o&CFFHP¹¶¶63*U$Ù2÷Ã7tý÷(&5¹¶CFFHP(FU  
        DPOUFYU$POUFYU
\DUY0VUQVU#PEZ  <>CZUF  
                        IFMMPXPSME


^
ý÷1045¹¶CFFHP1PTU   
                     BMJDF
GVOD    DUY
        DPOUFYU$POUFYU
\DUY0VUQVU#PEZ  <>CZUF  
                        CPC


^Çcë¸
o    )551&¹¶CFFHP"OZ    
                     GPP
GVOD    DUY
        DPOUFYU$POUFYU
\DUY0VUQVU#PEZ  <>CZUF  
                        CBS


^
CFFHP'JMUFS'VODPG   Êõ&(P¨¹ÌIUUQTFFHPNFEPDTNWDDPOUSPMMFSSPVUFSNECFFHP(FU    SPVUFS
CFFHP'JMUFS'VODFS
CFFHP'JMUFS'VOD
CFFHP'JMUFS'VODFS
CFFHP'JMUFS'VODFS
CFFHP'JMUFS'VODPVUFS
CFFHP'JMUFS'VODPVUFS
CFFHP'JMUFS'VOD
ºËö&IBOEMFS
u7yCFFHPËNFSWFS¨
½cëÓ:&u7QD/FX4FSWFS 
T3FHJTUFS$PEFD  KTPO/FX$PEFD    

从上述抽取结果来看,在包含中文的文本提取过程中,ledongthuc/pdf 基本不可用。

纯英文的抽取时间对比

经过上述比较之后,只考虑 ledongthuc/pdf 与 tika 在纯英文抽取方面的对比:

库/server 第一次抽取 第二次抽取 第三次抽取 第四次抽取 第五次抽取
tika 408ms 40ms 35ms 33ms 40ms
ledongthuc/pdf 4ms 2ms 2ms 4ms 2ms

从上述时间对比上看,tika 尽在初次解析的时候慢,但后来基本都稳定在 30-40 ms,然后 ledongthuc/pdf 基本都稳定在 2-4 ms, 两者之间有 10 倍的性能差距。

总结

从上述的解析效果来看,tika 具有巨大的优势,中英文皆可以,然而 go 语言的相关库却在解析效果上不尽人意,且不支持中文的解析。
但若对于纯英文的解析,ledongthuc/pdf 有着较大的性能优势,却是在忽略空格,换行符等的情况下。考虑到解析方法可以进一步优化的下,可以考虑修改对空格等的支持。
ledongthuc/pdf 在 github 上的 demo 解析效果非常好,预计有提升的空间。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,566评论 25 707
  • 进步这个词天天被我们念叨,从我们早上的晨会开始,以前叫个口号都没有什么声音,现在我们开始教他们跳舞,虽然跳的不是很...
    曹清兰阅读 129评论 0 0
  • 当我孤独的时候就这样抱着你,一辈子不放弃,当我寂寞的时候就这样想着你 ,一生都只为你——珍惜。 ———《当我孤独...
    念江子阅读 399评论 0 4
  • 昨晚八点,从医院看小侄回来,走到小区大门附近,远远就听到一个女人尖锐的声音:“你就说能不能过下去,过不下去...
    欢颜的回忆阅读 108评论 0 0
  • 风吹过 树叶显的枯凉 跑道上 很多人在热谈 女孩干练的卷起了头发 随着裁判员一声令下 首冲前方 400米跑完后 女...
    语兮阅读 328评论 0 1