📦{tm}パッケージで日本語のPDFからテキストを抽出する
男なら誰しも一度は「俺に落とせない女はいない」、的なことを言ってみたいと思うわけですが、どうやらそんなセリフを言う機会がありそうもないので、「俺に落とせないデータはない」くらいにスケールダウンかつハッカー感を出していければと思います。
というわけでタイトルにある通り、日本語のPDFからテキストを抽出する方法です。テキストマイニングに特化した{tm}
パッケージを使います。
まず、通常の{tm}
の挙動を見てから、日本語PDFへの応用例を示します。
library(tm)
🔧 tm::readPDF() の基本動作
PDFからR上にテキストを落とすreadPDF()
の基本的な使い方です。
対象にするのは、欲しいなー誰か買ってくれないかなーと狙っている "Zero Inflated Models and Generalized Linear Mixed Models with R"の目次PDFにします。動作を確認したい人は適宜ダウンロードしてきてください(デスクトップ上にContents.pdfというファイル名でおきました)。
readPDF()
を動作させるためには次のシステムが必要です。パスが表示されるかを確認しましょう。
Sys.which(c("pdfinfo", "pdftotext"))
## pdfinfo pdftotext
## "/usr/local/bin/pdfinfo" "/usr/local/bin/pdftotext"
# tm_res() という関数を用意する tm_res <- readPDF(control = list(text = "-layout")) # tm_res()は次の引数名をもつ tm_res %>% args() %>% as.list() %>% names()
## [1] "elem" "language" "id" ""
engineに指定する引数は初期値のxpdfで良いと思われるので、特に指定する必要はありません。その他のシステムを利用したい時には変更してください。xpdfの他に、Rpoppler、ghostscript、Rcampdfおよびカスタマイズしたシステムが利用できます。
# elem引数にリスト形式でファイルまでのパス、language引数を指定し、関数を実行する tm_res %<>% .(elem = list(uri = "/Users/user/Desktop/Contents.pdf"), language = "en")
tm_res %>% class()
## [1] "PlainTextDocument" "TextDocument"
readPDF()
の返り値はこのようなクラスオブジェクトになっています。また、
tm_res %>% str(max.level = 2)
## List of 2
## $ content: chr [1:247] "VIII Contents" "" "Contents" "" ...
## $ meta :List of 7
## ..$ author : chr "Highstat"
## ..$ datetimestamp: POSIXlt[1:1], format: "2012-01-29 21:14:55"
## ..$ description : chr ""
## ..$ heading : chr "FrontMatterF"
## ..$ id : chr "Contents.pdf"
## ..$ language : chr "en"
## ..$ origin : chr "Word"
## ..- attr(*, "class")= chr "TextDocumentMeta"
## - attr(*, "class")= chr [1:2] "PlainTextDocument" "TextDocument"
のように、テキストそのものはtm_res$contentに格納されています。tm_res$contentは文字列クラスオブジェクトです。すべてを表示させると長いので、先頭のいくつかのみを表示させます。
tm_res$content[1:10]
## [1] "VIII Contents"
## [2] ""
## [3] "Contents"
## [4] ""
## [5] "PREFACE\t\r ........................................................................................................................................................\tV\r \t\r "
## [6] "CONTENTS\t\r ..................................................................................................................................................\tV\r III\t\r "
## [7] "1\t\r INTRODUCTION\t\r TO\t\r BAYESIAN\t\r STATISTICS,\t\r MCMC\t\r TECHNIQUES,\t\r AND\t\r WINBUGS\t\r ........................................\t\r 1 \t\r "
## [8] ""
## [9] " 1.1\t\r PROBABILITIES\t\r AND\t\r BAYES’\t\r THEOREM\t\r .............................................................................................................\t\r 1\t\r "
## [10] " 1.2\t\r LIKELIHOOD\t\r FUNCTIONS\t\r . ...............................................................................................................................\t\r 3\t\r "
というようにPDFから文字をR上に抽出できました。あとの処理は通常のRでのテキストマイニングの手法を適用すればおkです。
🇯🇵 日本語PDFでも文字抽出
さて、同様の手法を日本語が含まれるPDFに対して実行すると、アルファベットや数字は抽出することができますが、肝心の日本語が抽出できません。そこで、
[http://akkunchoi.github.io/xpdf-japanese.html:embed:cite]
このページにあるように、必要なツールをインストールしたのち、xpdfの内容に修正を加えておきます。これで準備は整いました。日本語PDFの例として、
総務省|政策統括官(統計基準担当)|統計に用いる標準地域コード
にある 「41 佐賀県~47 沖縄県(PDF:145KB)」を対象にします。ダウンロードしたファイルは000323624.pdfという名称です。
tm_res <- readPDF(control = list(text = "-layout"))
tm_res %<>% .(elem = list(uri = "/Users/user/Desktop/000323624.pdf"), language = "ja")
結果です。例によって一部を取り出してみます。
tm_res$content[1:10]
## [1] "41 佐 賀 県 346 み や き 町 みやきちょう"
## [2] ""
## [3] "200 市 部 360 削 除(旧小城郡)"
## [4] " 361 削 除"
## [5] "201 佐 賀 市 さがし 362 削 除"
## [6] " 363 削 除"
## [7] "202 唐 津 市 からつし 364 削 除"
## [8] ""
## [9] "203 鳥 栖 市 とすし"
## [10] ""
というわけで日本語のPDFでもテキストを落とすことができて、小さな小さな自尊心が保たれました。
🍵 おまけ
せっかくなので市区郡町村のデータフレームを作成してみました。
library(tidyr) library(dplyr)
res_city <- tm_res$content %>% grep("\\d{3}", ., value = TRUE) %>% grep("削|除", ., invert = TRUE, value = TRUE) %>% grep("[市区郡町村]", ., value = TRUE) %>% gsub(".+[0-9] ", "", .) res_city %<>% data_frame(city = .) %>% tidyr::extract(col = city, into = c("name", "rubi"), regex = "(.+[市区郡町村]+)+([[:print:]]+)", remove = TRUE) %>% dplyr::mutate(name = gsub("[[:space:]]", "", name)) %>% dplyr::filter(!is.na(name))