77Lifeworkベータ版

77Lifeworkベータ版

ITツール開発・インフラ構築をメインとして、その他投資等雑記用のブログです。ブログの内容が少しでも皆様の参考となれば嬉しいです。

【初心者向け】GASでWebサイトをスクレイピングする方法(1)

はじめに

こんにちは。今回はGAS(Google Apps Script)でWebサイトのスクレイピングをする方法を書いていきます。
GASを実行する方法、スクレイピング対象ページの解析方法、実際のスクレイピングスクリプトについて細かく説明していますので、ご覧いただけると嬉しいです。
今回はGASでのスクレイピングの例として、複数の家電量販店のWebサイトでニンテンドーswitchの在庫状況を確認し、購入できる状態だった場合に通知するというプログラムをつくってみました。

処理の流れ

大まかな流れとしては下のような感じです。
スクリプトを実行
・各家電量販店のニンテンドーswitch販売ページを開き、htmlを取得
・htmlを解析し、在庫があって購入できる状態かどうかを判定
・判定結果をGoogleスプレッドシートに書き込む



今回スクレイピング対象としているのは以下2つのサイトです。
・ヤマダウェブコム(https://www.yamada-denkiweb.com/)
ビックカメラ.com(https://www.biccamera.com/bc/main/)


判定結果については、在庫があったら「○」、在庫がなければ「×」がスプレッドシートに書き込まれるようにします。
下のようなイメージです。
f:id:J-back:20210313182231p:plain:w600



スクリプトエディタを開く

まずはGoogleドライブを開いて、新しいスプレッドシートを作成しましょう。
f:id:J-back:20210302001601p:plain:w600



作成したスプレッドシートを開き、「ツール」タブの「スクリプトエディタ」を開きます。
f:id:J-back:20210302001820p:plain:w600



するとスクリプトを編集できる画面が開き、ここでプログラムを組んで実行させることができます。
f:id:J-back:20210302002225p:plain:w600



必要なライブラリ(Parser)の追加

Parserについては以下の記事で解説していますので、こちらを見てみてください。
www.77-lifework.com



スクレイピング対象ページの解析

プログラム作成するにあたって、スクレイピングする対象のページの構造を見て、判定に使えそうな部分を探しておきます。
対象のページを開き、htmlの内容を確認するため、例としてヤマダウェブコムのswitch本体の販売ページを開いてみます。
f:id:J-back:20210302004112p:plain:w600



ここで、表示されているページのhtmlソースを表示してみましょう。
Google chromeの場合はF12キーを押すと画面右側にhtmlが表示されます。
このhtmlを解析することでswitch本体が購入できる状態か売り切れかを判定することになります。
f:id:J-back:20210302004317p:plain:w600



購入可能かどうかを判定する箇所を絞り込むために、購入できる状態と売り切れの状態でのhtmlの変化を見てみましょう。
この記事を書いている時点では、ニンテンドーswitch本体は売り切れの状態ですので、例としてニンテンドーswitch liteのページを見てみます。
すると、こちらは購入できる状態のようです。
f:id:J-back:20210302005208p:plain:w600



商品が購入できる状態だと右上に「カートに入れる」というボタンがあり、購入ステップに進めるようになっていますが、在庫切れなどの場合には右上に「売り切れました」と表示されていますね。
この部分のhtmlを解析することで購入できる状態かどうか判定できそうです。

このボタンについてのhtmlを詳しく見てみましょう。
まず、購入できない(売り切れ)の状態のhtmlソースを表示してみます。
下の画像の赤枠で囲った部分を押します。
f:id:J-back:20210302011225p:plain:w600



するとページを構成している部分を選択し、そこに対応するhtmlを表示できるモードになります。
f:id:J-back:20210302011446p:plain:w600



「売り切れました」という部分をクリックすると、その部分のhtmlが表示されました。
f:id:J-back:20210302011705p:plain:w600



続いて、在庫があって購入できる状態のswitch liteのページで、「カートに入れる」のボタン部分のhtmlを表示します。
f:id:J-back:20210302011951p:plain:w600



これらのhtmlを比べてみると、「売り切れました」と「カートに入れる」が表示されている部分はどちらも「side_button」というクラスのdivタグに囲まれていることが分かります。


したがって、この部分のhtmlの内容を判定条件として、購入できる状態かどうかを判断することができます。

試しに下のようなテストコードを書いて実行してみます。

function testFunction() {
  
  let URL1 = "https://ymall.jp/kaden/1178230015/?q=Nintendo+switch";
  let URL2 = "https://ymall.jp/kaden/1178232019/?q=Nintendo+switch";
  
  let fromText = '<div class="side_button">';
  let toText = '</div>';  
  
  let html1 = UrlFetchApp.fetch(URL1).getContentText("UTF-8");
  let html2 = UrlFetchApp.fetch(URL2).getContentText("UTF-8");
  
  let state1 = Parser.data(html1)
  .from(fromText)
  .to(toText)
  .build();
  
  let state2 = Parser.data(html2)
  .from(fromText)
  .to(toText)
  .build();
  
  Logger.log(state1);
  Logger.log(state2);
  
}


このコードですが、

let html1 = UrlFetchApp.fetch(URL1).getContentText("UTF-8");

の部分でURLにアクセスし、htmlを取得しています。

let state1 = Parser.data(html1)
  .from(fromText)
  .to(toText)
  .build();

の部分で取得したhtmlから指定した部分のみを抽出しています。



上のコードを実行すると、以下のような実行結果になります。
f:id:J-back:20210303010248p:plain:w600



赤枠で囲った部分が売り切れの場合のページ(URL1)から取得したhtmlの「売り切れました」と表示されている部分を抽出したもので、
青枠が購入可能な状態のページ(URL2)から取得したhtmlの「カートに入れる」と表示されている部分を抽出したものになります。


そこで、以下のようにコードを書いてみます。

function testFunction() {
  let URL1 = "https://ymall.jp/kaden/1178230015/?q=Nintendo+switch";
  let URL2 = "https://ymall.jp/kaden/1178232019/?q=Nintendo+switch";
  
  
  let fromText = '<div class="side_button">';
  let toText = '</div>';
  
  let html1 = UrlFetchApp.fetch(URL1).getContentText("UTF-8");
  let html2 = UrlFetchApp.fetch(URL2).getContentText("UTF-8");
  
  let state1 = Parser.data(html1)
  .from(fromText)
  .to(toText)
  .build();
  
  let state2 = Parser.data(html2)
  .from(fromText)
  .to(toText)
  .build();
  
  let available = "カートに入れる";
  let unavailable = "売り切れました";
  
  if (state1.search(available) != -1){
    Logger.log("購入可能です");
  
  } else if (state1.search(unavailable) != -1){
    Logger.log("売り切れです");
  
  } else {
    Logger.log("判定できません");
  
  }
  
}
 

追加した記述についてですが、

let available = "カートに入れる";
let unavailable = "売り切れました";

の部分で、「side_button」クラスのdivタグに囲まれている文字列を指定しておき、

if (state1.search(available) != -1)

によって、「side_button」クラスのdivタグに囲まれている文字列が「カートに入れる」だった場合には
「購入可能です」と出力させています。
文字列1.search(文字列2)は、文字列1の中に文字列2が含まれている場合に、一致した位置を返します。
一致するものがない場合に-1を返すので、この値が-1であるかどうかで対象の文字列が含まれているかどうかを判定しています。


同様に、

if (state1.search(unavailable) != -1)

によって、売り切れかどうか判定しています。
上記のどちらにもあてはまらない場合(サイト内の文字列が変更になった場合)などは「判定できません」と出力するようにしました。


商品が売り切れの場合に上記コードを実行すると、
f:id:J-back:20210313185436p:plain:w600

と結果が出力されます。

おわりに

ちょっと長くなってしまったので、今回はここまでにします。
ヤマダウェブコムを対象に、ニンテンドーswitchの商品ページをスクレイピングし、売り切れかどうか判定するところまでできました。

この続きの
ビックカメラ.comの商品ページスクレイピング
・結果をスプレッドシートに書き出す

の項目は次回書きます。
ここまで読んでいただきありがとうございました。