9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

sue445Advent Calendar 2016

Day 3

Tカード個人情報提供先新着bot

Posted at

2年前にプチ話題になったアレです

image

過去の記事

2年前に書いた時から若干アップデートがあるのでそれについても書きたいと思います。

ボットでやっていること

  1. 1時間に1回、提供先企業一覧のPDFをダウンロードできるページにいく
  1. PDFをダウンロードして提供先企業一覧を取得
  • スクレイピングだけでいけると思ったらPDFで提供されていたのでparseするのが大変だったw
  1. 新着があればボットでつぶやく

ってだけです。

ちなみにPDFをダウンロードしてスクレイピングするロジックは2年間で一切手を加えていませんが、だいたい動いているようです。

使っている技術

  • ruby
  • Padrino Framework
    • 全部入りのRails と シンプルな Sinatra の中間くらいのフレームワーク
    • リリース当時はスクレイピングとボットのみだったのでpadrinoはおろかsinatraすら不要だったのですが、スキーマのmigrationを自前で実装するのが嫌だったのでpadrinoを採用
    • 後からwebを追加したくなったので(後述)、結果的にはpadrino使ったのは正解だった
  • Heroku

HTMLのスクレイピング

Mechanize を使ってます。

  • メリット
    • 軽い
    • Ruby単体で動く
  • デメリット
    • htmlのbodyのparseしかしないので、jsでDOMが操作されている場合には使えない

Ajaxがふんだんに使われたページをスクレイピングするには phantomjs + Capybara + poltergeist の組み合わせが鉄板だと思います。(が、phantomjsはデバッグしづらいのが。。。)

参考: Rails4 + Capybara + PhantomJS (poltergeist) なテスト環境 - (゚∀゚)o彡 sasata299's blog

実際のソースコードで解説します

lib/workers/pdf_crawl_worker.rb
  def download_ccc_pdf(dest_pdf_file)
    # mechanizeのインスタンスを初期化
    agent = Mechanize.new

    # http://qa.tsite.jp/faq/show/25129 を開く
    agent.get("http://qa.tsite.jp/faq/show/25129")

    # <a href="/attachment_file/〜.pdf"> のようなリンクを探す
    download_link = agent.page.link_with(href: %r(/attachment_file/.+\.pdf))

    # リンクが見つからなければエラー
    raise "Not found download_link" unless download_link

    # リンク先のPDFをダウンロードしてファイルに保存する
    pdf_content = agent.get_file(download_link.href)
    File.open(dest_pdf_file, "wb") do |file|
      file.write(pdf_content)
    end
  end

pdfのスクレイピング

pdf-reader というgemを使ってます。

pdfをrubyから読むためのgemはいくつか使ってみたのですが、今回はこのgemじゃないとうまくテキストで取得出来ませんでした。(cccのpdfはExcelをpdfに変換してるみたいなのですが、他のgemだとセルの中のテキストが列単位でしか取得できない。pdf-readerだとpdfとしてレンダリングされる時の実際の座標もある程度考慮してくれる模様)

ダウンロードしたpdfをテキストで読み込む

lib/workers/pdf_crawl_worker.rb
  def read_pdf(pdf_file)
    pdf_content = ""

    reader = PDF::Reader.new(pdf_file)
    reader.pages.each do |page|
      pdf_content << page.text
    end

    pdf_content
  end

これ自体は特別なことをしていないんですが、そのままだと下記のようにpdf内の日付がうまく取得できませんでした

1 TSUTAYA・蔦屋書店                      2014/10/2提携先:TSUTAYAフランチャイズチェーン加盟企業
2 JX日鉱日石エネルギー株式会社                   2014/10/2提携先:ENEOS
3 株式会社アプラス                          2014/10/2提携サービス:Tカードプラス, Tカードプラスα ,TSUTAYAWカード
4 株式会社Misumi                        2014/10/2提携先:BOOKSmisumi,Misumiグループ(ガス・水)
5 JR九州ドラッグイレブン株式会社                  2014/10/2提携先:ドラッグイレブン

モンキーパッチで文字描画の位置を無理矢理変えて対応してます。

lib/pdf-reader.rb
class PDF::Reader::PageLayout
  # fix rate: 1.05 -> 1.5
  def col_count
    @col_count ||= ((@page_width  / @mean_glyph_width) * 1.5).floor
  end
end

pdfを文字列で取得でした後は正規表現でparseしてます

lib/workers/pdf_crawl_worker.rb
  def parse_ccc_pdf(pdf_file)
    companies = []
    read_pdf(pdf_file).each_line do |line|
      line = line.strip

      matched_data = %r(
        ^(?<no>[0-9]+)\s*
        (?<company_name>.+)\s*
        (?<receipted_date>[0-9]{4}/[0-9]{1,2}/[0-9]{1,2})
        (?<destination_name>.+)$)x.match(line)
      next unless matched_data

      companies << Company.new(
        no:               matched_data[:no].to_i,
        company_name:     matched_data[:company_name].strip,
        receipted_date:   matched_data[:receipted_date].strip,
        destination_name: matched_data[:destination_name].strip,
      )
    end

    companies
  end

ソースコード中の正規表現の

%r(
^(?<no>[0-9]+)\s*
(?<company_name>.+)\s*
(?<receipted_date>[0-9]{4}/[0-9]{1,2}/[0-9]{1,2})
(?<destination_name>.+)$)x.match(line)

/^([0-9]+)\s*(.+)\s*([0-9]{4}\/[0-9]{1,2}\/[0-9]{1,2})(.+)$/ =~ line

と同等ですが下記のような工夫があります

クローラ

作った当初は

  • Heroku schedulerの無料枠は1日1回実行のみ。(1時間に1回実行は有料)
  • インスタンスを24時間フル稼働していても無料

ということもあり sidekiq-cron のプロセスを常駐して30分に1回実行していたのですが、2015年4月頃 1 に新しい料金体系が発表され

  • 24時間インスタンスを起動し続けると有料になる
  • その代わりHeroku schedulerが無料でも1時間に1回動かせるようになった

ということになり、Heroku Schedulerで1時間に1回動かすようにしています。

heroku_scheduler.png

元々workerで実行してた頃の名残で、rake task内でworkerのインスタンスを perform しています。

lib/tasks/crawl_pdf.rake
desc "crawl pdf"
task :crawl_pdf => :environment do
  PdfCrawlWorker.new.perform
end

web画面

特にどこにもアナウンスしていなかったですが、レコードを見るのにローカルから padrino console するだけだと不便だったのでwebで見れるようにしています。

自分が見れればいい感あったので画面は雑です

  1. 自分が対応した日付より推測 https://github.com/sue445/ccc_privacy_crawler/pull/25

9
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?