おれのにっき

何か問題でも

生ハムに メロンをのせて API puppeteerで スクレイピング

f:id:windyakin:20270714142643j:plain

おっと一句詠んでしまった

社会人になってから自由に使えるお金が増えて色々散財しているんですが、最近は薄い本を買い漁っていまして、もっぱら某アイドルアニメの全年齢系のものを買い集めています。

徐々にその量も増えてきていよいよ本棚に入らなくなってきたんですが、量が増えると何を持っていて何を持っていないのかがわからなくなってしまうので、台帳的なもので管理しようということで管理をはじめました。最初は Rails のプロジェクトにして… とか色々考えたんですが、そこまでフットワーク軽くなくて「完成が いつになるやら 法隆寺」とまた一句詠んでしまったところで、一旦 Google スプレッドシート上にまとめることにしました。

f:id:windyakin:20180501230902p:plain

本の管理はきちんとできていればリストをみるだけで持ってたか持ってなかったか一目瞭然で便利なのですが、そもそも管理を始める前に懸念していたこととして「そのうち入力が面倒で更新しなくなるんじゃないか」ってことでした。実際1冊あたりの情報項目自体はそんなに多くはないですが、イベントとかがあるとそれをまとめて更新しないといけなくなるので、まあ10冊とかでも結構な重労働だったりするわけです。

でもまあしかしながら幸いにも最近は某果物系の名前がついた委託販売サイトで買うことが多いので、入力したい内容のデータはインターネット上に公開されています。となればやりたくなるのはウェブページ上からそれらの情報を台帳に自動で取ってくることです。

Google Apps Script だけでやろうとした

前述のとおり、今回台帳は Google スプレッドシート上に作ったので、その上で何か自動処理を走らせようとしたときに一番手頃な手段が Google Apps Script(以下、GAS) という Google が公開している自動処理のための開発環境をつかうことです。 GAS の構文は JavaScript と同じで、そこに Google のサービスを使うための独自実装が載っているという状態です。公式ドキュメントもそれなりに充実はしていて、 Google のサービス上で完結するような簡単な操作であればそれなりに使うことができます。ですが、実装についてはブラウザ上で動く独自IDEを使うことが前提なので、動作の確認やデバッグするのが面倒だったりして、とても複雑なデータ処理をさせるのには向いてません。

特に GAS だけでやろうとしてつまづいたのが HTML の解析で、 GAS 標準の関数ではウェブサイトのソースコードを取ってくるところまではできても、そこから求めている情報が書かれている部分を探し出すのは至難の業です。GAS はあくまで「JavaScript を元にしているだけ」の言語なので DOM 操作をするための関数(例えば getElementById)たちは軒並み使えませし、XML Service は一箇所でも閉じタグが足りない HTML をパースすればエラーになってそこで処理が終了します。

GAS だけではスプレッドシートからデータを取得したり、入力したりといった操作自体は問題なくできますが、そこから情報の取得をするところは GAS だけで完結させることは難しいと判断しました。

スクレイピングだけ別の力を借りる

GAS だけでうまく実装ができなかった原因はHTMLの解析(スクレイピング)を行うための API が貧弱だったためです。そもそもそういった操作は GAS の使用目的としては想定されてないので仕方ないでしょう。

一方で GAS はきちんと正規化された XMLJSON であればパースしてそのデータを使うことができます。つまり「スクレイピングを行う」のではなく「API にアクセスして情報をもらう」ことは問題なくできます。つまり GAS から指定したウェブページをスクレイピングして JSON の結果を返す API プロキシのようなものがあれば連携することができるはずです。

puppeteer で API をつくって連携させる

f:id:windyakin:20180430150415p:plain

今回は以前から試してみたかった puppeteer という Node.js から Google Chrome を操作するAPIを提供してくれるライブラリをつかってスクレイピングを試してみたかったのでこれを使って実装しました。

なお実装したものは以下で公開しています。

github.com

使い方は app.js とかを読んだほうが早いかもしれませんが、某果物系の通販サイト*1では1つ1つに対して product_id という ID を振って一意に管理されるようになっているので、その ID を用いて GET /melon/:id というエンドポイントでアクセスすれば、情報がJSONで取得できるようになっています。

今回 puppeteer にをつかって一番よかったところは、年齢制限のある商品ページを表示しようとした場合に飛ばされるダイアログページを通過させるための実装が簡単に行えたところです。ブラウザ上に表示された DOM 要素をクリックしたことにする動作をたったの数行で実装することができました。 ٩( ᐛ)و

もちろんデメリットもあって puppeteer はブラウザの操作なので例えば画像コンテンツやアセット系のコンテンツが大量にあるページは1ページの表示に時間がかかるので、それだけ取得にかかる時間が遅延します。1件あたり取得からレスポンスまで1秒〜2秒ぐらいかかってしまうのでとても本番運用には向いているとは思えません。

さて素敵な API ができましたが、この API 単体だと情報を取ってくることしかできないのでこれとスプレッドシートを連携するために GAS を用います。手前味噌ですが GAS でスプレッドシートからURLしか書かれていない行を取ってきて、 UrlFetchApp.fetchAPI にアクセスし、得られた JSON の情報を各セルに埋めていきます。これぐらいなら処理自体も複雑ではなく、わりと簡単に書くことができました。

まとめ

スプレッドシートをつかって管理できるようになりましたが、横長の表だとスマートフォンなどで確認したり検索したりするには少し難しいなと感じます。やはり便利に使うのであれば管理台帳はきちんとウェブアプリ化する必要がありそう。その時はこのアプリはプロキシ的に使うとかSidekiqで非同期ジョブにするとかで負荷分散は必ず必要そうです。あと今のところキャッシュなどもないので、気が向いたら対応させるかもしれません。

  • puppeteer スクレイピングはブラウザ操作なので比較的簡単にできる
  • 趣味プロレベルならまあまあの速度 実運用だと流石になにか別の対策方法とかを考える必要がありそう
  • Node.js のテストを今回も書かなかった そろそろきちんと書きたい

*1:ちなみに某動物系の通販サイトには対応していません。URL の規則がわからなかったのと個人的には今のところ必要なかったからです。