GASでGmailの売上情報をスクレイピング2-農家のGAS(4)

前回の第三回で、3店舗分の売上情報メールをGASでスクレイピングし、スプレッドシートへの転記にするところまでは完成して運用していました。

ただ、その後出荷する店舗数が大幅に増え、前回までのプログラムでは対応出来なくなったこと。

そしてもっとスプレッドシートでの管理を詳細にやりたくなった為、改良版を作成することにしました。

改良版の概要

今回改良する内容としては、次の通り。

  • 出荷する店舗数を大幅に増やす
  • 店舗数が今後増減しても、プログラムの修正を簡単にできるようにする

前回までのプログラムは、出荷店舗数が3店舗だけでしたので、売上情報メールでの記載順も簡単に解析できました。

でも、一気に10店舗ほど増やす場合、解析が面倒です・・

それに今後店舗が無くなったら増えたりもするでしょうから、どんな順番で記載されてもスクレイピングできるようにしておかないと、後々困ることになりそうです。

ということで、出荷店舗数を増やす対応だけでなく、今後のことも見据えての修正をします。

改良版のGAS

まずは、完成したプログラムが以下の通り。

  1. function myFunction() {
  2.   // 検索条件に該当するスレッド一覧を取得
  3.   var threads = GmailApp.search('subject:ストア売上情報 -時 -label:処理済み');
  4.   // 12時・17時・22時の売上情報メールと、処理済みラベル付きは除外
  5.   // ↓スレッドを一つずつ取り出す
  6.   threads.forEach(function(thread) { //※1
  7.     // ↓スレッド内のメール一覧を取得
  8.     var messages = thread.getMessages();
  9.     // ↓メールを一つずつ取り出す
  10.     messages.forEach(function(message) { //※2
  11.       var body = message.getPlainBody(); // メール本文を取得
  12.       var sub = message.getSubject(); // 件名を取得
  13.       var sub = sub.toString().replace("ストア売上情報",""); //件名から日付だけを残す
  14.       // ↓書き込むシートを取得
  15.       var sheet = SpreadsheetApp.getActive().getSheetByName('在庫管理');
  16.       // ↓確認の為、メール本文のみ表示
  17.       console.log(body)
  18.       var tenpomei = ["店舗A", "店舗B", "店舗C", "店舗D", "店舗E", "店舗F", "店舗G", "店舗H", "店舗I"]
  19.       var tenpo = {}; //tenpomei配列の各店舗が、メール本文の何文字目か。存在しない場合は-1
  20.       var zentenposuu = tenpomei.length; //店舗名配列内に、何店舗入っているかの数
  21.       var tenposuu=0; //今回メール内に、何店舗入っているかの数
  22.       var kakuuriage = {}; //各店舗の売上情報
  23.       //ここから各店舗売上情報文書内
  24.       var yasaimei = ["野菜1", "野菜2","野菜3", "その他"];
  25.       var yasai = {}; //yasaimei配列の各野菜が、売上情報の何文字目か。存在しない場合は-1
  26.       var zenyasaisuu = yasaimei.length; //野菜名配列内に、いくつの野菜が入っているかの数
  27.       var yasaisuu=0; //今回売上情報内に、何野菜入っているかの数
  28.       var kakuyasai = {}; //各野菜の情報
  29.       var kingakuiti = {}; //kakuyasai配列内で、"金額"が何文字目か。
  30.       var suuryou = {}; //kakuyasai配列内で、実際の数量
  31.       var kingaku = {}; //kakuyasai配列内で、実際の金額
  32.       var syouhin = 0;
  33.       var zengaku = 0; //その日の全売上額
  34.       var uriage = {}; //売上情報を最後にまとめた配列
  35.       var tenpomei2 = [[],[]] //tenpomei1次配列を、メールの文章順に並べ替えた2次元配列
  36.       //店舗の数だけ処理を行う
  37.       for(let i=0; i<zentenposuu; i++){ //※3
  38.         tenpo[i] = body.indexOf(tenpomei[i]); //tenpomei配列の各店舗が、メール本文の何文字目か。存在しない場合は-1
  39.         tenpomei2[i] = [tenpomei[i],tenpo[i]] //[店舗名][何文字目]のtenpomei2[i]2次元配列を作成
  40.         if(tenpo[i] !== -1){
  41.           tenposuu = tenposuu+1
  42.           } //今回メール内に、何店舗の売上情報があるかカウントアップ
  43.       } //※3
  44.       tenpomei2.sort((a, b) => {return a[1] - b[1];} ); //昇順で2次元配列の2列目で並べ替え(店舗の順番をメールの文章通りに並べ替えしている)
  45.       console.log(tenpomei2)
  46.       for(let i=zentenposuu-1; i>=zentenposuu-tenposuu; i--){ //*4 //※3forで、全店舗の存在の有無を確認した後に処理。
  47.         if(tenpomei2[i][1] !== -1){
  48.           kakuuriage[i] = body.slice(tenpomei2[i][1]) //抽出した本文から、店舗ごとの売上情報を抽出する
  49.           body = body.substring(0,tenpomei2[i][1]) //本文から抽出した分を文末から順番に削除する。これをやる為に、この※4forはカウントダウンで行っている
  50.         }
  51.       } //*4
  52.       //配列内の野菜の数だけ処理を行う
  53.       for(let j=zentenposuu-1; j>=zentenposuu-tenposuu; j--){ //*c jが、野菜名配列内に、いくつの野菜が入っているかの数。総数の最後から、順番に減らしていく
  54.         yasaisuu = 0 //一周処理が終わったので、次のカウントをするために、0にリセット
  55.         for(let i=0; i<zenyasaisuu; i++){ //*a 全野菜数だけ繰り返す
  56.           yasai[i] = kakuuriage[j].indexOf(yasaimei[i]); //今回売上情報内に、"野菜◯"が入っているかどうか
  57.           if(yasai[i] !== -1){ //野菜◯が入っているなら
  58.             yasaisuu = yasaisuu+1 //今回売上情報内に入っている野菜数をカウントアップ
  59.           }
  60.         } //*a
  61.         for(let t=yasaisuu-1; t>=0; t--){ //*yc 今回売上情報内に入っている野菜数のカウントが終わったので、総数の最後から減らしていく
  62.           syouhin = kakuuriage[j].lastIndexOf("商品") //"商品"がkakuuriage[j]内の後ろから数えて何文字目を返す
  63.           kakuyasai[t] = kakuuriage[j].substring(syouhin,999) //kakuyasai[t]に、"商品"より後ろの文字を抽出して代入
  64.           kakuuriage[j] = kakuuriage[j].substring(0,syouhin) //kakuuriage[j]から、代入した分の文字を削除("商品"より前の文字を代入)
  65.         } //*yc
  66.         for(let i=0; i<zenyasaisuu; i++){ //*ya 全野菜数分繰り返す
  67.           if(yasai[i] !== -1){ //*yb 各野菜◯が存在するなら
  68.             for(let t=0; t<yasaisuu; t++){ //*ye 存在する野菜◯の数だけ繰り返す
  69.               if(kakuyasai[t].indexOf(yasaimei[i]) !== -1){ //*yd 野菜◯の◯に入る数字がわからないので、配列の最初から順番に確認する。
  70.                 kingakuiti[t] = kakuyasai[t].lastIndexOf("金額") //金額の文字が、後ろから数えて何文字目か抽出
  71.                 kingaku[t] = kakuyasai[t].substring(kingakuiti[t],999) //"金額"より後ろの文字を抽出して代入
  72.                 kakuyasai[t] = kakuyasai[t].substring(0,kingakuiti[t]) //代入した分の文字を削除("金額"より前の文字を代入)
  73.                 kingaku[t] = kingaku[t].replace(/[^0-9]/gi, ''); //kingaku[t]の中から、半角数字のみ抽出する(金額が何桁かわからないので)
  74.                 suuryou[t] = kakuyasai[t].replace(/[^0-9]/gi, ''); //kakuyasai[t]の中から、半角数字のみ抽出する(数量が何桁かわからないので)
  75.                 zengaku = zengaku+Number(kingaku[t])
  76.                 console.log(tenpomei2[j][0]+":"+yasaimei[i]+"| 数量:"+suuryou[t]+" 金額:"+kingaku[t])
  77.                 var uriage = [sub,tenpomei2[j][0],yasaimei[i],kingaku[t],suuryou[t]] //売上情報を配列に代入
  78.                 sheet.appendRow(uriage); //最終行に売上情報を転記
  79.               } //*yd
  80.             } //*ye
  81.           } //*yb
  82.         } //*ya
  83.       } //*c
  84.       var lastRow = sheet.getLastRow(); //スプレッドシートの最終行を取得
  85.       sheet.getRange(lastRow, 7).setValue(zengaku);//最終行のG列に、その日の全売上額を記載
  86.     }); //※2
  87.     thread.markRead(); //スレッドを既読にする
  88.     // スレッドに処理済みラベルを付ける
  89.     var label = GmailApp.getUserLabelByName('処理済み');
  90.     thread.addLabel(label);
  91.     }); //※1
  92.   }

概略は前回とほとんど変わりません。

詳細な解説は前回しているので省略。

今回改良した部分だけ解説していきます。

店舗数を増やし、売上情報メールの記載順に並べ替える


  1.       var tenpomei = ["店舗A", "店舗B", "店舗C", "店舗D", "店舗E", "店舗F", "店舗G", "店舗H", "店舗I"]
  2.       var tenpo = {}; //tenpomei配列の各店舗が、メール本文の何文字目か。存在しない場合は-1

前回から大幅に出荷店舗を増やしています。

とりあえず、3→9店舗へ。これは今後増減しても、この"tenpomei"の中を修正すれば良いです(詳細は後述)

また、21行目で今回の売上情報メールに各店舗の売上実績があるか無いかをチェックしています。

その上で、次の内容。


  1.       var tenpomei2 = [[],[]] //tenpomei1次配列を、メールの文章順に並べ替えた2次元配列



  1.       tenpomei2.sort((a, b) => {return a[1] - b[1];} ); //昇順で2次元配列の2列目で並べ替え(店舗の順番をメールの文章通りに並べ替えしている)

37行目
“tenpomei2″という2次元配列を新たに宣言

48行目
20行目の"tenpomei"配列は、Aから順番にしてあるだけだが、実際の売上情報メールの記載順(21行目で判定)に並べ替えます。
それをやっているのがsortですね。

以降の内容は、ほとんど変わりません。

今回の内容に合わせて若干修正したぐらいで、考え方は一緒です。

あとは、その日の売上合計額も82行目で追加したぐらいですね。

出荷店舗数が増えた分、売上額もどう変わったか調べていきたいので。

実際にスクレイピングした結果

売上情報メールが、以下のように届いたとします。
(縦長すぎたので、横に並べています)

ちなみに、わざと店舗の記載順はバラバラになるようにしています。

これを、先程のGASで処理した結果がこちら。

前回同様、メール記載順の逆からリスト化しています。

そして、合計額も追加。

Aから順番に記載されなくても、このようにちゃんとスクレイピングしてくれました。

これで、今後何店舗増えようが対応可能です!

ようやく、実用的になってきました(^^)

でも、ここからもう一歩踏み込んでこそ役に立ちます。

そのあたりは次回で解説!!