AdWords Scriptによる日次レポートメール送信

# 概要
Google AdWordsにおいて、広告の掲載状況を日次でレポートメールとして送信します。このスクリプトコードは、MCCアカウントレベルでのスクリプトとして実装していますので、MCCアカウントをお持ちでない方はそのままではご利用になれません。

## レポートの内容
昨日の

- インプレッション数
- クリック数
- 費用
- コンバージョン数
- クリック率
- クリック単価
- コンバージョン率
- コンバージョン単価
- 売上金額
- ROAS

を、検索広告とディスプレイ広告に分けて送信します。Ver1では、検索広告とディスプレイ広告の判断にラベルを利用していましたが、Ver2においては、ラベルは使わずに、AdWordsのキャンペーン情報を用いて判別しています。

また、昨日分のものだけでなく、今月1日からの通算のデータも送信されます。

# スクリプトコード
## 注意点
送信対象となるAdWordsアカウントに、ラベル「日次レポート送信」を付与します。このラベルが付与されているアカウントそれぞれに対して処理を行います。Ver1では、お客様IDをソースコード中に直接埋め込む必要がありましたが、ラベル管理できるようになったため、管理画面での作業で完結します。

送信先メールアドレスを指定してください。現在のスクリプトにおいて、「ここを送信先メールアドレスに変更する」となっている箇所を変更することになります。


## コード
```js:日次レポートメール.js
function main() {
  procAccountWithLabel('日次レポート送信', function(account) {
    var stat = {
      "YESTERDAY": {
        "Search": getReportData(account, "YESTERDAY", "Search Network"),
        "Display": getReportData(account, "YESTERDAY", "Display Network")
      },
      "THIS_MONTH": {
        "Search": getReportData(account, "THIS_MONTH", "Search Network"),
        "Display": getReportData(account, "THIS_MONTH", "Display Network")
      }
    };
    var view_data = view(account, stat);
    sendmail({
      "to": "ここを送信先メールアドレスに変更する",
      "subject": view_data["subject"],
      "htmlBody": view_data["body"]
    });
  });
}

function sendmail(context) {
  MailApp.sendEmail(context);
  Logger.log("メールを送信しました。送信先:" + context["to"] + ', 件名：' + context['subject']);
}

function getReportData(account, during, network) {
  var results = {"Impressions": 0, "Clicks": 0, "Cost": 0, "Conversions": 0, "ConversionValue": 0};
  var rows = AdWordsApp.report(
    "SELECT AdNetworkType1, Impressions, Clicks, Cost, ConvertedClicks, ConversionValue "
    + "FROM CAMPAIGN_PERFORMANCE_REPORT "
    + "DURING " + during
  ).rows();
  while (rows.hasNext()) {
    var row = rows.next();
    if (row["AdNetworkType1"] === network) {
      try {
        results["Impressions"] += toInteger(row["Impressions"]);
        results["Clicks"] += toInteger(row["Clicks"]);
        results["Cost"] += toInteger(row["Cost"]);
        results["Conversions"] += toInteger(row["ConvertedClicks"]);
        results["ConversionValue"] += toInteger(row["ConversionValue"]);
      } catch (ex) {}
    }
  }
  return results;

}


function view(account, data) {
  var row = function (title, value, is_top) {
    var th_style = 'border-bottom: #e3e3e3 1px solid; border-left: #e3e3e3 1px solid; text-align: left; background: #f7f7f7; padding: 10px; font-weight: normal;';
    var td_style = 'border-bottom: #e3e3e3 1px solid; border-right: #e3e3e3 1px solid; text-align: right; padding: 10px;';
    if (is_top) {
      th_style += 'border-top: #be1309 4px solid;';
      td_style += 'border-top: #b3b3b3 4px solid;';
    }
    return '<tr><th width="180" style="' + th_style + '">' + title + '</th><td width="120" align="right" style="' + td_style + '">' + value + '</td></tr>';
  };
  var table = function (title, dataset) {
    var body = [];
    body.push("<td width='420'>");
    body.push("<section>");
    body.push('<h3>■' + title + '</h3>');
    body.push('<table style="border-collapse: collapse;">');
    body.push(row('インプレッション数', formatter(dataset['Impressions'], '000,000') + '回', true));
    body.push(row('クリック数',formatter(dataset['Clicks'], '000,000') + '回'));
    body.push(row('費用', formatter(dataset['Cost'], '000,000') + '円'));
    body.push(row('コンバージョン数', formatter(dataset['Conversions'], '000,000') + '回'));
    body.push(row('クリック率', formatter(getRatio(dataset['Clicks'], dataset['Impressions'], true), '000,000.00') + '%'));
    body.push(row('クリック単価', formatter(getRatio(dataset['Cost'], dataset['Clicks'], false), '000,000.00') + '円'));
    body.push(row('コンバージョン率', formatter(getRatio(dataset['Conversions'], dataset['Clicks'], true), '000,000.00') + '%'));
    body.push(row('コンバージョン単価', formatter(getRatio(dataset['Cost'], dataset['ConvertedClicks'], false), '000,000.00') + '円'));
    body.push(row('売上金額', formatter(dataset['ConversionValue'], '000,000') + '円'));
    body.push(row('ROAS', formatter(getRatio(dataset['ConversionValue'], dataset['Cost'], true), '000,000.00') + '%'));
    body.push('</table>');
    body.push("</section>");
    body.push("</td>");
    return body.join('');
  };
  var d = new Date();
  d.setDate(d.getDate() - 1);
  var subject = account.getName() + '(' + account.getCustomerId() + ') の広告配信レポート(' + Utilities.formatDate(d, 'JST', 'yyyy年MM月dd日') + ')';
  var body = [];
  body.push('<h1>' + subject + '</h1>');
  body.push('<h2>昨日のデータ</h2>');
  body.push("<table>");
  body.push("<tr>");
  body.push( table('検索広告', data['YESTERDAY']['Search']) );
  body.push( table('ディスプレイ広告', data['YESTERDAY']['Display']) );
  body.push("</tr>");
  body.push("</table>");
  body.push("<hr style='clear: both;' />");
  body.push('<h2>今月のデータ</h2>');
  body.push("<table>");
  body.push("<tr>");
  body.push( table('検索広告', data['THIS_MONTH']['Search']) );
  body.push( table('ディスプレイ広告', data['THIS_MONTH']['Display']) );
  body.push("</tr>");
  body.push("</table>");

  return { "subject": subject, "body": body.join('\n') };
}

function procAccountWithLabel(labelName, callback) {
  var accountIterator = MccApp.accounts().withCondition("LabelNames CONTAINS '" + labelName + "'").get();
  var accounts = [];
  while (accountIterator.hasNext()) {
    var account = accountIterator.next();
    MccApp.select(account);
    callback(account);
  }
}


function toInteger(value) {
  try {
    return parseInt(value.split(",").join(""), 10)
  } catch (ex) {
    Logger.log(value);
    return 0;
  }
}
 

function getRatio(bunshi, bunbo, is_percent) {
  if (bunbo == 0) { return "--" ; }
  if (is_percent) {
    return 100 * bunshi / bunbo;
  } else {
    return bunshi / bunbo;
  }
}

function formatter(value, type) {
  value = value + '';
  if (value == '--') return value;
  if (type == '000,000') {
    if (value.indexOf('.') > 0) {
      value = value.split('.')[0];
    }
    return value.replace( /(\d)(?=(\d\d\d)+(?!\d))/g, '$1,' );
  } else if (type == '000,000.00') {
    var values = value.split('.');
    var result = '';
    result = values[0].replace( /(\d)(?=(\d\d\d)+(?!\d))/g, '$1,' );
    result += '.';
    if (values.length == 1) { values.push('00'); }
    result += (values[1] + '00').substr(0, 2);
    return result;  
  }
  return value;
}

```