share facebook facebook twitter menu hatena pocket slack

2019.10.15 TUE

定期的にサーバーサイドで自動スクレイピングしたい

松本 勝年

WRITTEN BY 松本 勝年

あるサイトのナビゲーションの内容が変わったら知りたいと思う事があったので、採用した手段と調べた事を残しておきます。

今回の要件

  • サーバーサイドで実行したい
  • 1日1回実行したい
  • 1URLのみを対象とする
  • ナビゲーションの内容と項目数を知りたい
  • お金はかけたくない
  • 手軽に済ませたい
  • 結果を通知したい

結論

いくらか調べたのですが、結局

  • GASを時間ベースのトリガーで実行して
  • URLをFetchして取得したHTML文字列を
  • 正規表現で必要な部分のみ抽出し
  • Gmailで結果を送信する

ことにしました。

候補手段

頭に浮かんだのは下の手段でした

  • スクレイピングサービス
  • AWS Lambda
  • GAS

スクレイピングサービス

https://www.octoparse.jp/

はじめは、こちらのサービスの無料プランで試そうとしたのですが、14日間の無料トライアルの後、自動的に有料プランに移行されるらしく、今回の要件に合わないのでやめました。

解約もすんなりいかずに精神的に消耗したので、サービスを探すのはやめました。

AWS Lambda

できるのはわかってますが、お手軽に済ませたいのでスキップしました。

GAS

トリガーがあり、Gmailも簡単に送れて良さそうなので、今回はGASを使う事にしました。

GASで特定の要素を取得する候補手段

HTMLを取得する部分と、Gmailを送信する部分はサンプル通りに1行書けば良いだけでした。
HTMLから必要な要素をいかにして取り出すかで、少し考える事になりました。

XMLをパースして1階層ずつたどっていく

https://web.plus-idea.net/2018/04/google-apps-script-xmlservice-parse/

rootDoc.getChild('Body', nsSoapenv).getChild('loginResponse', ...

GASの標準機能でセオリーっぽいのですが、1階層ずつ指定していくのは嫌なので採用しない事にしました。

IMPORTXML

https://support.google.com/docs/answer/3093342

spreadsheetのimportxml関数で必要な要素を取得した上で、GASで残りの処理をするというアイデアですが、どうせならGASだけで完結させたいので採用しない事にしました。

querySelectorAllのような機能

ブラウザのJavaScriptなら、selectorを使えば欲しい要素が簡単に取得できるのですが、GASでそれを実現する機能は用意されていないようでした。

querySelectorAllのPolyfillをGASにもってくる

https://gist.github.com/chrisjlee/8960575

IE7程度のdocumentオブジェクトが利用できる事が前提のようなので、GASに流用するのは無理でした。

getElementsByClassNameのGASによる実装例

https://sites.google.com/site/scriptsexamples/learn-by-example/parsing-html

対象のXMLが壊れているらしく XmlService.parse(html);の行でエラーになりました。
このサイトに対してはXMLのパースが必要な手段は通用しなさそうです。

独自のgetElementByClassNameライブラリ

https://sites.google.com/site/scriptsexamples/learn-by-example/parsing-html

こちらもXMLのパースが必要なので、今回は利用できなさそうです。

独自のParserライブラリ

https://www.kotanin0.work/entry/2019/01/06/200000

場合によっては有用かもしれないのですが、今回やりたい事程度なら記述量もさほど変わらないので正規表現を使った文字列抽出でいいやという気になりました。

実装例

function myFunction() {
  var html = UrlFetchApp.fetch(url).getContentText();

  var reg = /<div class="box">([\s\S]*?)<\/div>/g;
  var text = html.match(reg);

  MailApp.sendEmail(mailAddr, mailTitle, text.join("\n"));
}

まとめ

迷う事なく今回の手段を講じていれば、手軽に実現できたと言えるのではないかと思います。
ただ、必要な要素の抽出が泥臭い感じになって残念感があるので、よりスマートな手段を見つけられたらいいなと思います。

元記事はこちら

定期的にサーバーサイドで自動スクレイピングしたい

松本 勝年

松本 勝年

フロントエンドに興味があるエンジニア

cloudpack

cloudpackは、Amazon EC2やAmazon S3をはじめとするAWSの各種プロダクトを利用する際の、導入・設計から運用保守を含んだフルマネージドのサービスを提供し、バックアップや24時間365日の監視/障害対応、技術的な問い合わせに対するサポートなどを行っております。
AWS上のインフラ構築およびAWSを活用したシステム開発など、案件のご相談はcloudpack.jpよりご連絡ください。