Google Apps Script (GAS) でHTML上の検索結果の数字を取ってくる実験

MAC

書いた人:ぬーいー

 

こんにちは。

久々にちょっとした技術について。

とは言っても、自分も勉強中の「Google Apps Script(GAS)」についてです。

 

Google Apps Script (GAS) とは?

Excelからスプレッドシートに移行する人については、Google版のマクロ(厳密にはVBAですが)という感覚でいればOKです。

しかし、慣れてくると、VBAに比べて扱えるサービスの多さと敷居の低さに驚くかもしれません。

すごく個人的な意見を言うと「VBAの方が配列が扱いやすくて好き」なのですが、しかしGoogle Apps ScriptにはVBAに比べると非常に大きなメリットがあります。

 

それは、Googleのサーバー上で動いてくれるので、「トリガー」という機能と併用することによって、ほぼ完全自動で動かせることです。

PCの電源をつけていなくてもOK。

他の仕事をしてても動いてくれますし、逆に休日放置しておいても動いてくれます。

夏休みでも正月でも、完全放置で自動で動くというのは実はExcel+VBA環境では結構難しいんですよね。

(一番必要そうなPowerBIなんかでも、少なくとも自分が触っていたリリース当初は単品の機能では持っていなかったはずです)

 

今回はその自動で動いてくれる特性を活かして、特定のサイトの定点観測をしてくれるスクリプトを実験で組んでみました。

さすがに全てを見ることは見る側にも面倒なので、特定キーワードの検索結果件数だけを見るようにしてみます。

 

参考にしたサイトはこちら。

 

Google Apps Script を利用してRSSやHTMLを読み取る – 白牙隊A2屯所

Google Apps Script を利用してRSSやHTMLを読み取る - 白牙隊A2屯所
GoogleAppsScriptsはサーバーサイドのJavaScript、と呼ばれています。その魅力を象徴するUrlFetchApp.fetchというものを使ってみました。RSSやURLを使ってその情報を活用してスプレッドシートなどのGoogleのサービスと連動させる事ができます。今回はスプレッドシートを中心にやってみ...

 

ぼどレポ: Google スプレッドシートを超簡易DBとして使う その1

Google スプレッドシートを超簡易DBとして使う その1
Google Apps Script, 技術文書

 

Google Apps Script で検索結果件数を取得してみる実験

使うもの

Google スプレッドシート

Google Sheets: Sign-in
Access Google Sheets with a personal Google account or Google Workspace account (for business use).

今回は結果をわかりやすく表示する目的もあり、使います。

他の形式で吐き出すこともできますが、調査対象を簡易マスタにまとめて使いますので今回のスクリプトには必須です。

 

Google Apps Script

スプレッドシートの個別編集画面の「ツール」から「スクリプトエディタ」という手順で起動するのが一番早いです。

 

Googleスプレッドシートの構造

以下の通り。

[1]「data」シート

image

見出しだけつけておきましょう。

今回は

「A列」:対象キーワード名

「B列」:店の名前

「C列」:記録する件数

「D列」:記録する日付

を入れて、毎日少しずつデータを貯める想定でいます。

 

[2]「master」シート

170926

毎日記録したい内容をまとめた、簡易マスタシートです。

一応、ある程度柔軟に対応できるようにするため、細かく作ってみました。

以下のように設定しています。

「A列」:対象キーワード名

「B列」:店の名前

「C列」~「E列」:対象のURLを分割したもの。後述。

「F列・G列」:取得する要素の目印。後述。

 

なお、今回は実験なので簡易マスタとして1シートにまとめてしまっていますが、B・C・E・F・G列についてはサイトごとにほぼ共通ですので、

ここを分けて店マスタ的なシートを一つ作った方がよりスマートでメンテナンスもし易いです。

 

今回は某PCパーツ系ECサイトにアクセスし、個人的な趣味でキーワードを作って監視してみることにしていますが、先方サイトへの悪影響も怖いのでURLについてはご想像にお任せします。

PCの型番とかで設定したかったんですが、さすがに件数少なすぎて楽しくなかったのです。。。

 

分割したURL(C~E列)について

対象のURLを一列にそのまま記載しても良いのですが、現在のECサイト・カタログサイトの検索結果ページは以下のようなURL構造になっていることが多いです。

 

パターン1:https://[サイトURL]/[キーワード]/[その他]

パターン2:https://[サイトURL]/list.php?keyword=[キーワード]&[その他]

※list.phpはresult.phpでも他のファイル名でも何でも良い

 

この場合、サイト内で[キーワード]部分以外は共通であることが多く、共通部分とキーワード部分を分けた方がスプレッドシートのマスタ作成が楽だったのと、先述の店マスタを分けた場合のやり易さからURLを分割しています。

C列:キーワードより前の検索結果URL共通部分

D列:キーワード部分

E列:キーワードより後の検索結果URL共通部分

と、今回は設定。

なお、E列部分が存在しない場合の処理は今回考えていないため、必要があれば別途つけてください。

 

取得する要素の目印(F・G列)について

F列:入力したい数字の直前にあるソースコード

G列:入力したい数字の直後にあるソースコード

を入力します。

 

もう少し詳しくお話をすると、今回のスクリプトは、

<1>GASによってHTMLソースをテキストで取得

<2>取得したい件数部分だけを抜き取る

という手順を踏みます。

 

そこで、例えば

 

<div class=”result”>検索結果<span class=”resultcount”>230</span><span class=”kensu”>件</span></div>

 

というソースから、「230」という件数だけを抜き出したい場合は、「slice」によって、「<div class=”result”>検索結果<span class=”resultcount”>」「</span><span class=”kensu”>件</span></div>」の間の要素を抜き出す、という探し方をします。

上記のF・G列については、例えば上記の例でいうと、

F列:<div class=”result”>検索結果<span class=”resultcount”>

G列:</span><span class=”kensu”>件</span></div>

と入力するのが良いでしょう。

余談ですが、sliceはEXCELでいう「MID」関数とほぼ同じ機能です。

EXCEL使いには、MIDとSEARCHの二つの関数を使って対象のテキストから必要箇所を抜き出すイメージをしてもらうと分かりやすいと思います。

 

 

Google Apps Script で検索結果件数を取得するコード

 

以下、「var bookurl_work = “https://docs.google.com/spreadsheets/●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●”;」の部分は、ご自身のスプレッドシートのURLに変更してください。

また、記録シート側の列数を変えるなどのカスタマイズ時には「range_data = sheet_data.getRange(lastRow_data,1,lastRow_data,4);」の最後の4も見直してください。

変数の役割とか整理しながら書いたので、かなり冗長です。やろうと思えば多分現行の2/3くらいにはまとめられるはず。

 

以下のスクリプトを、「トリガー」の日タイマーで毎朝4~5時頃に動かしています。

function getText_fromHtml(){
	//変数宣言(スプレッドシート側) masterが取得したいデータを記載したマスタ、dataが取得したいデータを取っていくデータシート。
	var bookurl_work = "https://docs.google.com/spreadsheets/●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●";
	var sheetName_master = "master";
	var sheetName_data = "data";
	var book_work = SpreadsheetApp.openByUrl(bookurl_work);
	var sheet_master = book_work.getSheetByName(sheetName_master);
	var sheet_data = book_work.getSheetByName(sheetName_data);

	//変数宣言(スクリプトを回すために使う変数)
	var lastColumn_master = sheet_master.getLastColumn();
	var lastRow_data = sheet_data.getLastRow() + 1;
	var range_data = sheet_data.getRange(lastRow_data,1,lastRow_data,4);
	var loopCount = sheet_master.getLastRow()-1;
	var range_master = sheet_master.getRange(1,1,sheet_master.getLastRow(),lastColumn_master);

	//変数宣言(シート入力用の変数関係)
	var cell_productName = "";
	var text_productName = "";
    var cell_storeName =  "";
    var text_storeName = "";
	var url_gethtml = "";
    var cell_url = "";
    var cell_beforetext = "";
    var cell_aftertext = "";

	//変数宣言(今日の日付)
	var date_today = new Date();

	//変数宣言(HTML取得関係)
	var opt = {"contentType":"text/html;","method":"get"};
	var data_html = "";
	var content_html ="";
	var postText = "";

  //繰り返し開始箇所
  for (var i=2; i&lt;=loopCount+1; i++) {
    //html取得
	cell_productName = range_master.getCell(i,1);
	text_productName = cell_productName.getValue();
    cell_storeName =  range_master.getCell(i,2);
    text_storeName = cell_storeName.getValue();
    for (var j=3;j&lt;=5;j++){
      cell_url = range_master.getCell(i,j);
      url_gethtml = url_gethtml+cell_url.getValue();
    }
    data_html = UrlFetchApp.fetch(url_gethtml ,opt);
    content_html = data_html.getContentText();
    Logger.log(content_html);
    cell_beforetext = range_master.getCell(i,6);
    cell_aftertext = range_master.getCell(i,7);
    postText = getStringSlice(content_html, cell_beforetext.getValue(),cell_aftertext.getValue());
    range_data.getCell(1, 1).setValue(text_productName);
    range_data.getCell(1, 2).setValue(text_storeName);
    range_data.getCell(1, 3).setValue(postText);
    range_data.getCell(1, 4).setValue(date_today);
    lastRow_data = sheet_data.getLastRow() + 1;
    url_gethtml = "";
    range_data = sheet_data.getRange(lastRow_data,1,lastRow_data,4);
  }
}

function getStringSlice(content, startStr, endStr){
  var indexStart = content.indexOf(startStr);
  if(indexStart == -1){
    return "";
  } else {
    indexStart += startStr.length
    return content.slice(indexStart, content.indexOf(endStr, indexStart));
  }
}

 

現行のコードの問題点

いくつかのサイトで回してみたのですが、記事を書いている時点で以下の問題点が分かっています。

・検索結果件数が複数記述されているサイトへの未対応

検索結果一覧の上下両方に結果数を出すデザインにはまだ対応していません

 

・検索結果件数の前後の要素が簡素すぎる場合への対応

例えば<div>230件</div>など。前後の改行なども含めて指定してやる必要があります。

多分取得したソースコードから改行とtab代わりのスペース、tabを除去して検索した方が楽そう。

 

・javascriptで検索結果を上書きするタイプのサイトへの対応

最近はひと昔前のSEOの影響で静的ページ上に件数を書くサイトが多いのですが、まだまだ存在します。

今回はそもそも対応するつもりがなかったのでやっていませんが、プライベートでやるなら以下がよさそう。

 

JavaScript実行後のHTMLをGASで取得する – Qiita

JavaScript実行後のHTMLをGASで取得する - Qiita
はじめにGoogle Apps Script(GAS)でJavaScript実行後のHTMLソースを取得(Webスクレイピング)するのに一手間かかったのでメモ。GASだと以下のコードでHTMLが…

 

・UTF-8以外の文字コードを使用している場合の未対応

WEBを検索すれば文字コード対応の方法はいくらでも出てくるので、やりたい方はそちらを参考にしてください。

 

回せば回すほど問題点が出てくるとは思うのですが、たくさん回せば回すほど取得先のサイトへの負担になりますので、基本的には最低限にしたいですね。

なお、「UrlFetchApp.fetch」という機能は、Googleの制限で1アカウント20,000回/日しか使えないそうです。

ファイル単位ではなくアカウント単位でかかってきますので、多くのサイトを監視したい、とか、多くのバリエーションを作ってみたい、などの場合は要注意です。

 

まとめ

やりすぎると迷惑千万なコードですが、個人的な利用でちょこっと使う分には結構楽しいです。

応用すればするほど色々な情報が取れるようになりますので、ぜひお試しあれ。

 

データは取得してまだあまり経っていないので、少し溜まってきたらまた何かの形でこちらに書きます。

 

……Excelにも逆輸入できないですかねえ、全自動。。。

GAS関連記事
Google Apps Script (GAS) でスクレイピング~気象庁のページにある過去の気象データをスプレッドシートに書き込む実験~

コメント