cucumber flesh

Rを中心としたデータ分析・統計解析らへんの話題をしていくだけ

⭐️PDFの情報・文章をRでごっそり取得する

便利なRパッケージを見つけたのでメモがてら紹介しておきます。以前、Rを使ってPDF上のテキストを取得するパッケージとして{tm}パッケージを紹介しましたが、同様の機能をもった{pdftools}は以下の特徴があります。

  1. PDFがもつ各種の情報やテキストを取得できる
  2. 日本語も問題なし
  3. PDFがロックされている場合、パスワードで開ける
  4. PDFを画像として出力できる

開発者はrOpenSciの一員でもあるJeroen Oomsです。

github.com

uribo.hatenablog.com

{tm}パッケージでは日本語が含まれるPDFを扱う際にはちょっとした工夫が必要でしたが、{pdftools}では日本語の出力も問題なく行うことができてちょっと感動しました。機能としてPDFの情報を得る、ということとPDFを画像として出力する、というものがあります。

🔰 使用例

CRANに登録されているのでインストールしてきましょう。

# install.packages('pdftools')
library(pdftools)

PDFの情報を得る

{pdftools}パッケージのコアとなるのが、以下で実行する関数です。引数のpdfに指定したパスのPDFに対して該当する処理を実行します。

# PDFのパスを用意しておく。
# 例として、{zoo}パッケージに付属のPDFを用いる
path <- system.file("doc/zoo.pdf", package = "zoo")

pdf_info()では、PDFがもつ情報を取得します。ページ数や保存された日時やメタ情報などがわかります。

res.info <- pdf_info(pdf = path)
# 取得した要素
res.info %>% names()
##  [1] "version"     "pages"       "encrypted"   "linearized"  "keys"       
##  [6] "created"     "modified"    "metadata"    "locked"      "attachments"
## [11] "layout"
res.info$pages
## [1] 29
res.info$created
## [1] "2015-03-16 14:27:01 JST"
res.info$keys$Creator
## [1] "David M. Jones"

テキストマイニングなどで利用できそうなテキスト抽出の関数としてpdf_text()があります。この関数では、PDF内のテキストを文字列として返します。ページごとに一つの要素にまとまっているのが特徴です。

res <- pdf_text(path)
res[1] %>% {
    nchar(.) %>% print()
    strtrim(., 300)
}
## [1] 3174

## [1] "  zoo: An S3 Class and Methods for Indexed Totally\n                                Ordered Observations\n                      Achim Zeileis                            Gabor Grothendieck\n                   Universität Innsbruck                         GKX Associates Inc.\n                             "

PDF内で利用されているフォントを確認するpdf_fonts()関数もあります。該当するフォントがインストールされている場合、フォントファイルのパスを表示してくれるのが便利ですね。

# {formattable}で表示形式を変更
library(formattable)
pdf_fonts(path) %>% formattable(list(embedded = formatter("span", 
    style = x ~ style(color = ifelse(x, "green", 
        "red")), x ~ icontext(ifelse(x, "ok", 
        "remove"), ifelse(x, "TRUE", "FALSE")))))
name type embedded file
GOLQXE+CMB10 type1c TRUE
OGKCHM+CMBX12 type1c TRUE
MAFAWY+CMSSBX10 type1c TRUE
AXINBO+CMR10 type1c TRUE
MQVSZN+CMBX10 type1c TRUE
GDRMFP+CMSS10 type1c TRUE
FDHKEF+CMTI10 type1c TRUE
LLMLMG+CMTT10 type1c TRUE
ECIDJB+CMBXSL10 type1c TRUE
XBTYLL+CMSL10 type1c TRUE
MUVTSF+CMSSI10 type1c TRUE
HJMIPE+CMTT12 type1c TRUE
ZCUJTH+CMSLTT10 type1c TRUE
ZVTVKO+CMR8 type1c TRUE
ZVTVKO+CMR6 type1c TRUE
TZIHOI+CMR9 type1c TRUE
XFSHQU+CMTT9 type1c TRUE
Helvetica type1 FALSE /Library/Fonts/Microsoft/Arial.ttf
Helvetica-Bold type1 FALSE /Library/Fonts/Microsoft/Arial Bold.ttf
ZapfDingbats type1 FALSE /System/Library/Fonts/ZapfDingbats.ttf
QAYMJR+CMMI10 type1c TRUE
ZDZXBN+CMTI9 type1c TRUE
QKDOVZ+CMSS9 type1c TRUE
HWQZWS+CMBXTI10 type1c TRUE

pdf_toc()はPDFの情報から、見出しだけを抽出する関数です。地味に嬉しい気がします。返り値はリストクラスオブジェクトとなっており、適当な処理で目次を再現することができそうです。

res.toc <- pdf_toc(path)
res.toc %>% class()
## [1] "list"
library(purrr)
# 章だけを抽出
res.toc$children %>% map_chr(c("title"))
## [1] "Introduction"                      "The class \"zoo\" and its methods"
## [3] "Combining zoo with other packages" "Summary and outlook"              
## [5] "Reference card"
# 節はどうする??
# もうちょっと効率的な方法があるはず...
res.toc$children[[2]]$children %>% map_chr("title")
## [1] "Creation of \"zoo\" objects"                    
## [2] "Creation of \"zooreg\" objects"                 
## [3] "Plotting"                                       
## [4] "Merging and binding"                            
## [5] "Mathematical operations"                        
## [6] "Extracting and replacing the data and the index"
## [7] "Coercion to and from \"zoo\""                   
## [8] "NA handling"                                    
## [9] "Rolling functions"
# 適当に取り出すだけなら...
res.toc %>% unlist()
##                                                          title 
##                                                             "" 
##                                                 children.title 
##                                                 "Introduction" 
##                                                 children.title 
##                            "The class \"zoo\" and its methods" 
##                                        children.children.title 
##                                  "Creation of \"zoo\" objects" 
##                                        children.children.title 
##                               "Creation of \"zooreg\" objects" 
##                                        children.children.title 
##                                                     "Plotting" 
##                                        children.children.title 
##                                          "Merging and binding" 
##                                        children.children.title 
##                                      "Mathematical operations" 
##                                        children.children.title 
##              "Extracting and replacing the data and the index" 
##                                        children.children.title 
##                                 "Coercion to and from \"zoo\"" 
##                                        children.children.title 
##                                                  "NA handling" 
##                                        children.children.title 
##                                            "Rolling functions" 
##                                                 children.title 
##                            "Combining zoo with other packages" 
##                                        children.children.title 
##                 "strucchange: Empirical fluctuation processes" 
##                                        children.children.title 
##                           "tseries: Historical financial data" 
##                                        children.children.title 
##            "timeDate/fCalendar: Indexes of class \"timeDate\"" 
##                                        children.children.title 
## "The classes \"yearmon\" and \"yearqtr\": Roll your own index" 
##                                                 children.title 
##                                          "Summary and outlook" 
##                                                 children.title 
##                                               "Reference card"

またpdf_attachments()という関数もありますが、PDFに付属するファイルの情報を取得するものと思われますが、手持ちのファイルではそのようなものがなかったので未検証です。

pdf_attachments(path)
## list()

開くのにパスワードが必要なPDFでは、パスワードを指定する引数opwおよびupwがあるので、そちらにパスワードを渡します。

# 某申請書で埋め込まれていたフォント情報
pdf_fonts("hogehoge.pdf", upw = "piyopiyo") %>% 
  dplyr::mutate(name = iconv(name, from = "cp932", to = "utf8")) %>% # フォント名が文字化けしていたので直してやる
  formattable(list(embedded = formatter("span",
                                        style = x ~ style(color = ifelse(x, "green", "red")),
                                        x ~ icontext(ifelse(x, "ok", "remove"), 
                                                     ifelse(x, "TRUE", "FALSE")))))
name type embedded file
MSゴシック cid_truetype FALSE /Library/Fonts/Microsoft/MS Gothic.ttf
MS明朝 cid_truetype FALSE /Library/Fonts/Microsoft/MS Gothic.ttf
@MSゴシック cid_truetype FALSE /Library/Fonts/Microsoft/MS Gothic.ttf

PDFをビットマップとして出力する

もう一つの機能として、PDFファイルのイメージをビットマップファイルとして出力するpdf_render_page()関数があります。この関数を利用するにはlibpopplerがインストールされている必要があるようですが、私の環境では何もしない状態で実行できました。出力の拡張子.pngpng::writePNG()), .jpgjpeg::writeJPEG()), .webpwebp::write_webp())となります。

library(png)
bitmap <- pdf_render_page(path)
writePNG(bitmap, "pdf_out.png")

f:id:u_ribo:20160227194202p:plain

引数でページ数やDPIの指定など細かな調節ができます。

# 高解像度での出力。dpiの初期値は72
bitmap <- pdf_render_page(path, page = 9, 
    dpi = 250)
writePNG(bitmap, "pdf_out2.png")

Enjoy 😎

💻 実行環境

package version source
formattable 0.1.5 CRAN (R 3.2.2)
magrittr 1.5 Github (<smbache/magrittr@00a1fe3>)
pdftools 0.1 CRAN (R 3.2.3)
png 0.1-7 CRAN (R 3.1.0)
purrr 0.2.1 CRAN (R 3.2.3)
remoji 0.1.0 Github (<richfitz/remoji@dc00779>)