『7回目の出直し🌻』

好きなことを自分のペースで、のんびり更新

記事内の未展開のアフィリエイトタグを探すプログラム。Google Apps Scriptで作り直し

前回、PHPで作った「はてなブログの記事内に書いたアフィリエイトタグで商品が消えてしまったもの」を探すプログラムを、Google Spread Sheetを使って焼き直ししてみました。

PHPで適当に作ったものよりは、簡単に試せる形になったと思います。

GASとは何か?

GASとは、Google Apps Scriptの略です。
GASは、Googleのスプレッドシート(Googleが作ったExcelといえば分かりやすいですかね)の中でプログラムを書いて動かす仕組みです。Excelマクロみたいなものと思ってもらえればよいです。

もっと雑に説明すると、ExcelにはVBA、GoogleスプレッドシートにはGASです。

GASはJavaScriptをベースとした言語なので、JavaScriptに似ています。JavaScriptに慣れていると習得も理解も早いかもしれません。 僕自身、GASの存在は知っていましたが、普段はPHPやJavascriptでプログラミングをしてしまうので、今回初めて使いました。

GASを使ってやること

プログラムを作る言語が変わっても、やることはPHPのときと同じです。

  • sitemap_index.xmlを読み、sitemap.xmlのURLを取り出し、
  • sitemap.xmlを読み、記事のURLを取り出し
  • 記事URLへアクセスし、HTMLを取り出し
  • HTMLを解析し、怪しいものを探す

PHPのときと異なり、もらう画面をスプレッドシーをスプレッドシートで作ることができるのと、プレッドシートに書き込めるので、利用者にとので、利用者にとっては分かりやすく使いやすいと思います。ここはスプレッドシートでやる利点です。

ブックとスクリプトをセットにしたものを簡単に配る方法がわからなかったので、

  • 新規に空のブックを作り、
  • スクリプトをコピーして、
  • 実行する

という面倒な手順を書いておきます。

いいやり方があるはずなのですが、まだ習得できていないので今回は超基本的な形でいきます。

GAS版の使いかた

この先、Googleのアカウントが必要ですので、用意しておいてください。

まず、設定するところまで

新規ブック開くまで

https://www.google.com/intl/ja_jp/sheets/about/
を開きます

Spreadsheetを使ってみるをクリック
f:id:kanaxx43:20210521225746p:plain

Spreadシートのメニュー、新規作成ボタンをクリック
f:id:kanaxx43:20210520223215p:plain

新規のシートが開いた状態
f:id:kanaxx43:20210520223256p:plain

ブックに名前を付け、こんな感じにセルに書いておきます。 f:id:kanaxx43:20210520223712p:plain (動作には関係ないので、書かなくても大丈夫ですけど)

記入したところは、A1、C1、D1、E1とA4です。

スクリプトをセット

スクリプトエディターを開き、スクリプトをコピーします

ツールバーにある、ツール>スクリプトエディタを選択し f:id:kanaxx43:20210520223906p:plain

スクリプトエディタを開きます。これが初期状態です。
f:id:kanaxx43:20210520223951p:plain

2つの作業を行います。
f:id:kanaxx43:20210520224248p:plain

  • 1の近くを触って適当な名前に変えておく
  • 2のエリアを触って、次のスクリプトをコピーする

最終的にこんな感じになるはずです。
f:id:kanaxx43:20210520224612p:plain

Ctrl+Sで保存しておく。

コード(Google Apps Script)
const sitemapNamespace = "http://www.sitemaps.org/schemas/sitemap/0.9";
const cellOfSitemapIndex = "A2";
const startCellOfSitemap = "A5";
const startCellOfBlogEntry = "C2";
const targetWorksheetName = "シート1";

function myFunction() {
  cleanSheet();
  prepare();
  checkBlogEntry();
}

function cleanSheet(){
  var sheet = SpreadsheetApp.getActive().getSheetByName(targetWorksheetName);
  var r =0;
  var sitemapRange = sheet.getRange(startCellOfSitemap);
  while( !sitemapRange.isBlank()) {
    sitemapRange = sitemapRange.offset(1,0);
  }
  var delNotation = startCellOfSitemap+':'+sitemapRange.getA1Notation();
  console.log(delNotation);
  sheet.getRange(delNotation).clearContent();

  var entryRange = sheet.getRange(startCellOfBlogEntry);
  r=0;
  while( !entryRange.offset(r,0).isBlank()) {
    entryRange = entryRange.offset(1,0);
  }
  entryRange = entryRange.offset(0,3);
  delNotation = startCellOfBlogEntry+':' + entryRange.getA1Notation();
  console.log(delNotation);
  sheet.getRange(delNotation).clearContent();

  
}
function prepare(){
  var sheet = SpreadsheetApp.getActive().getSheetByName(targetWorksheetName);
  var sitemapIndexUrl = sheet.getRange(cellOfSitemapIndex).getValue();
  console.log(sitemapIndexUrl);

  var sitemapIndexXml = UrlFetchApp.fetch(sitemapIndexUrl).getContentText();
  var sitemapIndexDoc = XmlService.parse(sitemapIndexXml);
  var xmlProtocol = XmlService.getNamespace(sitemapNamespace);
  var sitemaps = sitemapIndexDoc.getRootElement().getChildren('sitemap', xmlProtocol);

  console.info('サイトマップインデックスからサイトマップURLを取る')
  var sitemapLocations = [];
  sitemaps.forEach(function(sitemap, s){
    var loc = sitemap.getChild('loc', xmlProtocol).getText();
    console.log(loc);
    sitemapLocations.push(loc);
    sheet.getRange(startCellOfSitemap).offset(s, 0).setValue(loc)
  });
  
  console.info('サイトマップからブログエントリーのURLを取る')
  var entryLocations = [];
  sitemapLocations.forEach(function(sitemapLoc, s){
    console.log('%s/%s %s',s+1, sitemapLocations.length, sitemapLoc);
    var sitemapXml = UrlFetchApp.fetch(sitemapLoc).getContentText();
    var sitemapDoc = XmlService.parse(sitemapXml);
    var urls = sitemapDoc.getRootElement().getChildren('url', xmlProtocol);

    var rng = sheet.getRange(startCellOfBlogEntry);
    for(var url of urls){
      var entryLoc = url.getChild('loc', xmlProtocol).getText();
      var lastmod = url.getChild('lastmod', xmlProtocol).getText();

      rng.offset(entryLocations.length, 0).setValue(entryLoc);
      rng.offset(entryLocations.length, 1).setValue(lastmod);
      entryLocations.push(entryLoc);
    }
  });
}

function checkBlogEntry(){
  var sheet = SpreadsheetApp.getActive().getSheetByName(targetWorksheetName);

  console.info('ブログエントリーからHTMLを取って調べる')
  
  var entryRange = sheet.getRange(startCellOfBlogEntry);

  //URLの数を数えるためだけのコード(しかもログに進捗出すためだけ)
  var entryCount = 0;
  while( !entryRange.isBlank()) {
    entryCount++;
    entryRange = entryRange.offset(1,0);
  }

  entryRange = sheet.getRange(startCellOfBlogEntry);
  var r=0;
  while( !entryRange.isBlank()) {
    var url = entryRange.getValue();
    console.log('%s/%s %s', ++r, entryCount, url);

    var html = UrlFetchApp.fetch(url).getContentText();
    var amazon = html.match(/\[asin:[^\]]*\]/g);
    var rakuten = html.match(/\[rakuten:[^\]]*\]/g);

    //setValueがRange返すので、チェーンできちゃう
    entryRange.offset(0,2).setValue(amazon).offset(0,1).setValue(rakuten);
    entryRange = entryRange.offset(1,0);
    Utilities.sleep(1000);
  }
}

コードはここに置いてあります。
https://github.com/kanaxx/hatenablog-omocha/blob/main/check-ng-ads.gs

これで準備完了です。

実行する

作ったスクリプトを実行します。途中で「スプレッドシート」が「自分のGoogleアカウント」のデータにアクセスする確認が出ますので、許可を与えてください。

サイトマップのURLをセットする

f:id:kanaxx43:20210520225003p:plain スプレッドシート(Excelのような見た目のほう)のA2のセルに、自分のサイトマップインデックスのURLを入れます。
文字列が長くて、A2に収まらなくても大丈夫です。

スクリプト実行

もう一度、ツール>スクリプトエディタへ行き、myFunctionになっていることを確認して、実行をクリック f:id:kanaxx43:20210520225040p:plain

初回実行時は、承認フローへ行きます。

承認の流れ

承認手順の中で行われることは、

  • このプログラム(名前がブログチェックになっているのはブック名)が、
  • 自分のGoogleアカウント内に保存されているスプレッドシートにアクセスすることと
  • 外部のURL(ブログの情報を取得するため)にアクセスすること

です。

動かそうとしているプログラムが、自分のスプレッドシートを読み取りをしようとしているけど大丈夫なの?っていう確認ですね。

確認ダイアログ

f:id:kanaxx43:20210520230258p:plain

アカウント選択

f:id:kanaxx43:20210520230301p:plain ブックが使うアカウントを選択します。最初にログインしてブックを保存したアカウントを選択します

エラー画面

f:id:kanaxx43:20210520230305p:plain
ちょっとした警告がでます。詳細を押して

f:id:kanaxx43:20210520230309p:plain
安全でないページへ移動を押します。

最後の確認画面

f:id:kanaxx43:20210520230312p:plain 土のプログラムが、どのアカウントの何にアクセスするのか、最終確認です。
これをOKすると「自分が作ったスプレッドシート+スクリプト」が「自分のGoogleアカウントのリソース」にアクセスできるようになります。

承認手順はここで終わりです。

1アプリにつき1回許可をすると、以降の実行では承認の手順は表示されずにすぐにスクリプトが実行できます

実行を確認

承認が終わると、スクリプトが動き出します。
実行中は、実行ログが1行ずつ増えていき、実行完了までいけば終わりです。 f:id:kanaxx43:20210520230551p:plain

Exceeded maximum execution timeが出る場合

調査するURLの数が多いと、Exceeded maximum execution timeエラーがでることがあります。GASのスクリプトは、開始して終了するまでに6分以内という制約があり、6分以内に終わらない場合は、Exceeded maximum execution timeで強制終了になります。この制約ばかりはどうにもなりません。 6分間で調査できたものは、スプレッドシートに残っています。

これをきちんと解決するには、中断したところから再開して続きを実行できるように作り変える必要があります。後日、考えます。*1

確認

スプレッドシートに戻ると、調査の結果が書き込まれています f:id:kanaxx43:20210520230802p:plain

見やすくするために、セルの調整するとこんな感じですね f:id:kanaxx43:20210520230755p:plain

  • A2に入れたサイトマップインデックスの内容で、サイトマップ一覧を取り出し
  • A5を基準に、下の方向にサイトマップURLが並び、
  • C2を起点に、下の方向にブログ記事のURLが並んでいきます。これはサイトマップファイルから自動で取り出したものです。
  • D2にはC2の記事の最終更新日
  • E2にはC2の記事に残っているAmazonのタグ
  • F2にはC2の記事に残っているRakutenのタグ

E列とF列が空っぽであれば、未展開のタグが残っていないということです。

逆に何か出ているときは、その記事に未展開のタグらしきもの*2がある場合です。

あとは、管理画面から記事を探して修正すればOKです。 スクリプトを再実行して、未展開のタグが0になることを確認しましょう。

まとめ

はてなブログの記事内のタグを調査するプログラムを、誰でも簡単に(?)動かせるようにしてみました。 はじめてGASに向き合ったので、いけてないとことがあるかもしれませんがご容赦ください。

スクリプト+ブック(入力フォームシート付)を簡単に渡す方法を知っているひとがいたら教えてください。

参考にした記事

https://monoblog.jp/archives/2349

*1:5/24追記:
たぶんこれでOKなはず。
https://github.com/kanaxx/hatenablog-omocha/blob/main/check-ng-ads-v2.gs

*2:タグではないけどタグに似た構造の文字がある場合は検出されてしまう