Madogiwa Blog

主に技術系の学習メモに使っていきます。

Ruby: `pdf-reader`gemを使ってPDFをパースしてゴニョゴニョする

国が公開している資料が割とPDFで公開されていることが多く、このようなデータをアプリケーション内で使いたいと思ったときにPDFを頑張ってゴニョゴニョする必要があると思うのですが。。。

そういうときにpdf-readergemがシンプルにつかえて便利だったので使い方とかをMEMOしておきます。

github.com

pdf-readerの使い方

pdf-readerの使い方は簡単で、以下のようにgemをinstallするか、

 gem install pdf-reader

以下をGemfileに追記してbundle installします。

gem 'pdf-reader'

あとは以下のような形で本文にアクセス出来ます。

reader = PDF::Reader.new("somefile.pdf")

reader.pages.each do |page|
  puts page.text # 本文
end

readerをカスタマイズする

自分は以下のような形でカスタマイズして使ってみました。

単純にpdfをパースすると、空白文字や空行が大量にあったり、メタ情報が上部に毎回表示されたりするので、CustomReader::Page#formatted_recordsで、それらを除去して扱いやすいようにしています。

require 'pdf-reader'

class BaseReader
  def initialize(file_path:)
    @client = PDF::Reader.new(file_path)
  end

  attr_reader :client

  def pages
    client.pages.map { |page| Page.new(page) }
  end

  class Page
    def initialize(page)
      @page = page
    end

    attr_reader :page

    def text
      page.text
    end
  end
end


class CustomReader < BaseReader
  def pages
    client.pages.map { |page| Page.new(page) }
  end

  class Page < BaseReader::Page
    START_RECORD_INDEX = 18
    END_RECORD_INDEX = -2
    CLUMN_DELIMITER = "\t"

    def formatted_records
      # NOTE: 以下を行い整形
      # * 2文字以上の空白文字を列の区切りとみなし区切り文字に変換
      # * 空白な行を削除
      # * 各ページのheader部分の行を削除
      text.gsub(/\x20{2,}/, CLUMN_DELIMITER).split(/\R/).reject(&:empty?)[START_RECORD_INDEX..END_RECORD_INDEX]
    end
  end
end

またBaseReaderでclinetをインスタンス変数で持つようにしたので、client.pagesにアクセスできる、かつ、それが本文を返すtextメソッドを持つオブジェクトの配列であれば任意のクライアントに変更できるので、別のgemを使いたくなったときにも変更しやすいのかなと・・・!

おわりに

pdfのパースやる前はかなり大変なのかなと思っていたのですが、pdf-readerシンプルに本文を取り出せるので便利ですね✨

しかし、pdf内の表のセルの中で改行が入っていたりすると、同一セル内でも別の行と判断されフォーマットが激しく崩れるのでpdfのデータをいい感じにパースするのは厳しい。。。😢

CSV等の扱いやすいデータ形式で提供されておらず、傾向だけとか、一部データだけでも取り出したいようなケースではpdfをパースしてゴニョゴニョするのは良さそうですね👍

参考

qiita.com