Google App Script 自動化

【GAS】楽天市場注文リストの自動生成

はじめに

なるちゃん

ねえ、おとん、

楽天市場のお買い物マラソンでたくさん注文したけど、何を注文したか忘れちゃうんだけど。

いくら購入したのかいちいち調べるのも面倒なんだけど。

そお?おとんも結構忘れるよ。

でも、注文リストをスプレッドシートに自動的追加してるから忘れても大丈夫なの。

購入金額も記録しているから、合計はすぐ出るんだよ。

関谷さん
なるちゃん

そんなことできるの?

ちょっとなるの環境でも設定してくれる?

ということで、楽天市場でお買い物をした時に、購入日、購入品、購入価格等の一覧を自動的に作る仕組みを作りました。

対象読者

ここで紹介する内容は、以下の条件を満たす方が対象となります。

対象読者

  • 楽天市場を使っている
  • 楽天市場の注文内容確認メールをGmailで受け取っている
  • 楽天市場の注文内容をスプレッドシートに自動追加したい

注文リストの自動生成

まず、Gmailから該当メールを抽出する部分、処理済みメールにラベルを付ける部分は、これまで見てきたカレンダーに登録する処理と同様です。

主な違いは以下の2点です。

  • Gmailでメールを抽出する際の検索文字が、楽天市場からの注文確認メール
  • メール本文から注文内容を抽出方法が楽天市場購入確認メールに特化している
  • メールから抽出した内容をスプレッドシートの最終行に追記する

では、詳細を見ていきましょう。

処理概要

楽天市場で商品を購入すると、以下のような利用明細をメールで受け取ることができます。

そして、ここで紹介する処理を実装すると、以下のようにスプレッドシートの最下行に注文内容を自動追加するようになります。

スプレッドシートに利用内容を登録するためには、以下の処理を考えます。

  • 楽天市場からの注文確認メールで未処理のメールを抽出
  • 注文確認メールから、商品、値段、数量、送料、使用ポイント額、使用クーポン額、支払額、獲得ポイント、ショップ名を抽出
  • スプレッドシートの最終行に②で得た情報を追記
  • 注文確認メールに処理済みラベルを付ける

そして、この処理を定期的に行わせます。

未処理メールの抽出

まず、楽天市場からの注文確認メールで、未処理のメールの抽出は以下の検索文字列で実現します。

ここでのメールの抽出条件の構文は、Gmailの抽出条件に設定する構文と同じです。

var SEARCH_QUERY = 'is:unread -label:' + LABEL + ' from:order@rakuten.co.jp 【楽天市場】注文内容ご確認(自動配信メール)';

処理済みを識別するために、処理完了後に該当メールのラベルに「自動処理済」を付けます。

そのため、抽出条件は以下の4つとなります。

  • 未読であること(is:unread)
  • 「自動処理済」のラベルが付いていないこと(-label:自動処理済)
  • 楽天市場のメールであること(from:order@rakuten.co.jp)
  • 楽天市場の注文確認メールであること(subject:【楽天市場】注文内容ご確認(自動配信メール))

Gmailへのアクセス方法については以下の記事を参照ください。

注文内容抽出

注文確認メールをシンプルテキストで取り出すと以下のようになります。(一部抜粋)

ここから、 商品、値段、数量、送料、使用ポイント額、使用クーポン額、支払額、獲得ポイント、ショップ名を抽出します。

---------------------------------------------------------------------
本メールはお客様のご注文情報が楽天市場のサーバに到達した時点で送信
される、自動配信メールです。ショップによる注文の確認
をもって売買契約成立となります。
---------------------------------------------------------------------


この度は楽天市場内のショップ「Happy Smiles」を
ご利用いただきまして、誠にありがとうございます。

---------------------------------------------------------------------

[ショップ名]   Happy Smiles (39ショップ)
※39(サンキュー)ショップの表示は購入時点での情報を元に表示しています。
==========
[お届け先]   関谷 様
           〒000-00000 群馬県
           (TEL) 000-0000-0000
[商品]
ソニー SONY用互換AVケーブル(S端子付き) VMC-15FS C ソニー SONY AVケーブル(S端子付き) VMC-15FS C互換品(R1802-029)
価格  750(円) x 1(個) = 750(円) ※10%税込
獲得ポイント14 ポイント2倍
*********************************************************************
送料計      0(円) 
お支払い金額     750(円)
---------------------------------------------------------------------
今回のお買い物で62ポイント獲得予定
※その他、ポイントに関する注意事項はメールの下部をご確認ください。
---------------------------------------------------------------------

[受注番号] 331668-20211108-02608804
[日時]     2021-11-08 11:36:08
[注文者]   関谷 (セキヤ) 様
           〒000-0000 群馬県
           (TEL) 000-0000-0000

[お支払い方法] クレジットカード 一括払い
[ポイント利用] なし
[配送方法] メール便(ヤマト運輸)
[お届け日時]

注文内容の抽出部分は以下のコードように処理します。

基本的には、抽出するためのキーワードを検索し、値を抽出することを繰り返します。

綺麗にまとまりませんでしたが、文字列処理なのでしょうがないかと。

      // 注文メールの内容確認
      var rows = body.split("\n");
      for (var i in rows) {
        var row = rows[i];

        // 情報抽出
        if (row.indexOf(SHOP_NAME) >= 0) {
          shopName = row.replace(SHOP_NAME, '');
        } 
        else if(row.match(/^\[商品\]/)){
          flgItem = 1;
        }
        else if(row.match(/^----------/)){
          flgItem = 1;
        }
        else if(row.match(/^価格/)){
          var strs = row.split(' ');
          price = strs[2].replace('(円)', '');
          count = strs[4].replace('(個)', '');

          // 価格情報取得段階でスプレッドシートに登録する
          libAddOrderToList.registOderInfoToSpreadSheet(
            'test', '', startTime, '楽天市場', itemName, price, count, shopName);
        }
        else if(shipping == null && row.indexOf(SHIPPING) >= 0){
          shipping = row.replace(SHIPPING, '').replace('(円)', '').replace('円', '');
        }
        else if(point_used == null && row.indexOf(POINT) >= 0){
          point_used = row.replace(POINT, '').replace('(円)', '').replace('(円)', '');
        }
        else if(coupon_used == null && row.indexOf(COUPON) >= 0){
          coupon_used = row.replace(COUPON, '').replace('(円)', '').replace('(円)', '');
        }
        else if(coupon_used == null && row.indexOf(RA_COUPON) >= 0){
          flgRaCoupon = 1;
        }
        else if(row.match(/今回のお買い物で.*ポイント獲得予定/)){
          point_get = row.replace('今回のお買い物で', '').replace('ポイント獲得予定', '');
        }
        else if(row.match(/今回のお買い物で獲得するポイント/)){
          point_get = row.replace('今回のお買い物で獲得するポイント', '');
        }
        else if(row.match(/合計.*\(円\)/)){
          payment = row.replace('合計', '').replace('(円)', '');
        }
        else if(payment == null && row.indexOf(PAYMENT) >= 0){
          payment = row.replace(PAYMENT, '').replace('(円)', '').replace('(円)', '');
        }

        if(flgItem == 1){
          flgItem++;
        }
        else if(flgItem == 2){
          itemName = row;
          flgItem = 0;  // 商品名最初の1行のみ抽出する
        }

        if(flgRaCoupon == 1 || flgRaCoupon == 2){
          flgRaCoupon++;
        }
        else if(flgRaCoupon == 3){
          // -10(円) x 1(枚) = 10(円)
          var strs = row.split(' ');
          coupon_used = '-' + strs[8].replace('(円)', ''); 
          Logger.log('coupon_used=' + coupon_used);
          flgRaCoupon = 0;       
        }
      }

スプレッドシート追記

スプレッドシートへの追記は、libAddOrderToListライブラリを作成して呼び出しています。

// 価格情報取得段階でスプレッドシートに登録する(商品、価格、数量、ショップ名登録)
libAddOrderToList.registOderInfoToSpreadSheet(
    'test', '', startTime, '楽天市場', itemName, price, count, shopName);

// 追加情報の登録処理(送料、使用ポイント、使用クーポン、支払額、獲得ポイント)
libAddOrderToList.registOderInfoToSpreadSheetSub(
    'test', '', shipping, point_used, coupon_used, payment, point_get);

ここでは、スプレッドシート名を「test」としました。※お好みで変更してください。

libAddOrderToListライブラリについてはこちらの記事で説明しています。

全てのコード

/*
 * 楽天市場の注文内容をスプレッドシートに登録する
 * 
 * 2021/09/15 Created by N.Sekiya
 */

// GMail検索文字列(楽天市場からの注文確認メール)※未読でラベルなしが条件
var SEARCH_QUERY = 'is:unread -label:' + LABEL + ' from:order@rakuten.co.jp 【楽天市場】注文内容ご確認(自動配信メール)';

// 検索文字列
var SHOP_NAME = '[ショップ名]   ';
var SHIPPING = '送料計';
var POINT = 'ポイント利用 ';
var COUPON = 'クーポン利用 ';
var RA_COUPON = 'ラ・クーポン利用';
var PAYMENT = 'お支払い金額';

function rakutenAddOrderList() {

  var threads = GmailApp.search(SEARCH_QUERY, 0, 1);
  if (threads.length === 0) {
    Logger.log('メールが見つかりません');
    return;
  }

  for(var k in threads){
    var thread = threads[k];
    var messages = thread.getMessages();
    for (var j in messages){
      var message = messages[j];
      var body = message.getPlainBody();   // HtmlメールからPlain textの本文を取得する
      var startTime = message.getDate();
      //Logger.log("body=" + body);

      var itemName = null;
      var shopName = null;
      var price = null;
      var count = null;
      var payment = null;
      var shipping = null;
      var point_used = null;
      var point_get = null;
      var coupon_used = null;
      var flgItem = 0;
      var flgRaCoupon = 0;

      // 注文メールの内容確認
      var rows = body.split("\n");
      for (var i in rows) {
        var row = rows[i];

        // 情報抽出
        if (row.indexOf(SHOP_NAME) >= 0) {
          shopName = row.replace(SHOP_NAME, '');
        } 
        else if(row.match(/^\[商品\]/)){
          flgItem = 1;
        }
        else if(row.match(/^----------/)){
          flgItem = 1;
        }
        else if(row.match(/^価格/)){
          var strs = row.split(' ');
          price = strs[2].replace('(円)', '');
          count = strs[4].replace('(個)', '');

          // 価格情報取得段階でスプレッドシートに登録する
          libAddOrderToList.registOderInfoToSpreadSheet(
            'test', '', startTime, '楽天市場', itemName, price, count, shopName);
        }
        else if(shipping == null && row.indexOf(SHIPPING) >= 0){
          shipping = row.replace(SHIPPING, '').replace('(円)', '').replace('円', '');
        }
        else if(point_used == null && row.indexOf(POINT) >= 0){
          point_used = row.replace(POINT, '').replace('(円)', '').replace('(円)', '');
        }
        else if(coupon_used == null && row.indexOf(COUPON) >= 0){
          coupon_used = row.replace(COUPON, '').replace('(円)', '').replace('(円)', '');
        }
        else if(coupon_used == null && row.indexOf(RA_COUPON) >= 0){
          flgRaCoupon = 1;
        }
        else if(row.match(/今回のお買い物で.*ポイント獲得予定/)){
          point_get = row.replace('今回のお買い物で', '').replace('ポイント獲得予定', '');
        }
        else if(row.match(/今回のお買い物で獲得するポイント/)){
          point_get = row.replace('今回のお買い物で獲得するポイント', '');
        }
        else if(row.match(/合計.*\(円\)/)){
          payment = row.replace('合計', '').replace('(円)', '');
        }
        else if(payment == null && row.indexOf(PAYMENT) >= 0){
          payment = row.replace(PAYMENT, '').replace('(円)', '').replace('(円)', '');
        }

        if(flgItem == 1){
          flgItem++;
        }
        else if(flgItem == 2){
          itemName = row;
          flgItem = 0;  // 商品名最初の1行のみ抽出する
        }

        if(flgRaCoupon == 1 || flgRaCoupon == 2){
          flgRaCoupon++;
        }
        else if(flgRaCoupon == 3){
          // -10(円) x 1(枚) = 10(円)
          var strs = row.split(' ');
          coupon_used = '-' + strs[8].replace('(円)', ''); 
          Logger.log('coupon_used=' + coupon_used);
          flgRaCoupon = 0;       
        }
      }

      // 追加情報をスプレッドシートに登録
      if(shipping == null) shipping = 0;
      if(point_used == null) point_used = 0;
      if(coupon_used == null) coupon_used = 0;
      libAddOrderToList.registOderInfoToSpreadSheetSub(
        'test', '', shipping, point_used, coupon_used, payment, point_get);

      // 処理済みメールとしてラベルを付ける
      libCtrlMail.putLabel(thread);
    }
  }
}

使用しているライブラリは以下2点です。

  • メール処理:libCtrlMail
  • スプレッドシート処理:libAddOrderToList

libCtrlMailについてはこちらを参照ください。

libAddOrderToListについてはこちらを参照ください。

繰返し実行

正しく動作することを確認したら、繰り返し実行できるよう設定します。

繰り返し実行には、トリガーの設定を行います。設定方法については、以下の記事を参照ください。

繰り返し実行する間隔は、急ぐ必要のない処理なので5分おきにします。

最後に

まず、手作業でデータ処理を色々するのはホントに嫌です。

単純作業はプログラムにやらせて、結果だけが得られればいいと心の底から思っています。

そのため、少しでも手間が省ければと思い、自動化処理を作り続けています。

正しく動くようになると、楽しくなるものです。

では、今日も良い一日を。

-Google App Script, 自動化
-, , , , ,