[Ver.2] dアニメストアのランキングをスクレイピングで取得する

#はじめに
前回、[こちら](https://qiita.com/Kaz_prog/items/1bdb4172fa70adc37449)の記事で、dアニメストアのランキングを自動取得する方法を紹介しました。
ですが、前回の方法はお世辞にもいいやり方とは言えません。
なぜなら、Googleスプレッドシートの他にもHerokuやSpreadsheet APIを使っていたりと管理が大変だからです。
今回は、Googleスプレッドシート（とGAS）で完結させる方法をご紹介します。
#完成像
![dani.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726763/69fad50c-a01c-67e7-f65c-6a5ed056c088.jpeg)
今回はランキングに加えてマイリスト登録数とお気に入り登録数も取得します。
#データのソース
[前回](https://qiita.com/Kaz_prog/items/1bdb4172fa70adc37449)に引き続き、JSONファイルを取得します。詳しいデータ構造については[この部分](https://qiita.com/Kaz_prog/items/1bdb4172fa70adc37449#d%E3%82%A2%E3%83%8B%E3%83%A1%E3%82%B9%E3%83%88%E3%82%A2%E3%81%AE%E6%A7%8B%E9%80%A0%E3%82%92%E8%A9%B3%E3%81%97%E3%81%8F%E8%A6%8B%E3%82%8B)をご覧ください。
前回は作品のタイトル名``workTitle``しか取得しませんでしたが、今回はマイリスト登録数``myListCount``とお気に入り登録数``favoriteCount``も取ってきます。

```json:JSON
  {
                "workId": "25182",
                "workInfo": {
                    "workTitle": "ありふれた職業で世界最強 2nd season",
                    "link": "https://anime.dmkt-sp.jp/animestore/ci_pc?workId=25182",
                    "mainKeyVisualPath": "https://cs1.anime.dmkt-sp.jp/anime_kv/img/25/18/2/25182_1_1.png?1638419407000",
                    "mainKeyVisualAlt": "ありふれた職業で世界最強 2nd season_1",
                    "scenes": [
                        {
                            "（省略）"
                        }
                    ],
                    "workIcons": [],
                    "myListCount": 36310,
                    "favoriteCount": 109694,
                    "workTypeList": [
                        "anime"
                    ],
                    "vodType": "svod",
                    "ageLimitType": "0",
                    "workIconInfoList": [
                        "（省略）"
                    ]
                },
                "rankingInfo": {
                    "rank": 1,
                    "arrowIcon": "rankUp"
                }
            },
```
例えばこの場合、作品名（``workTitle``）「ありふれた職業で世界最強 2nd season」は、　``rankingInfo``の``rank``から順位が1位ということがわかります。
さらに、マイリスト登録数（``myListCount``）は36310、お気に入り登録数（``favoriteCount``）は109694ということまで読み取れます。
あとは、これをスプレッドシートに落とし込むだけです。
#方法
前回の記事では、PythonでJSONファイルを取得しSpreadsheet APIでスプレッドシートに書き込む仕様でしたが、今回はGASでJSONを取り直接スプレッドシートに書き込みます。
使うのは、 GASのUrlFetchAppクラスの[fetchメソッド](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetch(String))。
簡単に説明すると、JSONを取ってきて文字列で返してくれます。

```javascript:GAS
 let response = UrlFetchApp.fetch("https://anime.dmkt-sp.jp/animestore/rest/WS000103?rankingType=01").getContentText();

// console.log(response)で確認すると↓（見にくいので適宜改行しています）
//Logging output too large. Truncating output.
//{"resultCd":"00","version":"1.5","selfLink":"https://anime.dmkt-sp.jp/animestore/rest/WS000103?rankingType=01",
//"data":{"maxCount":4520,"count":300,"workList":[{"workId":"25182","workInfo":
//{"workTitle":"ありふれた職業で世界最強 2nd season","link":
//"https://anime.dmkt-sp.jp/animestore/ci_pc?workId=25182",
//"mainKeyVisualPath":"https://cs1.anime.dmkt-sp.jp/anime_kv/img/25/18/2/25182_1_6.png?1638419407000","mainKeyVisualAlt":
//"ありふれた職業で世界最強 2nd season_6","workIcons":[],"myListCount":36839,"favoriteCount":110594,"workTypeList":
//["anime"],"vodType":"svod","ageLimitType":"0","workIconInfoList":
//["1","1","0","0","0","1","0","0","0","0","0","0","0","0","1","0","0","0","1"]},"rankingInfo":
//{"rank":1,"arrowIcon":"rankStay"}},{"workId":"25202","workInfo":{"workTitle":"その着せ替え人形は恋をする",
//"link":"https://anime.dmkt-sp.jp/animestore/ci_pc?workId=25202",
//"mainKeyVisualPath":"https://cs1.anime.dmkt-sp.jp/anime_kv/img/25/20/2/25202_1_6.png?1639560672000",
//"mainKeyVisualAlt":"その着せ替え人形は恋をする_6","workIcons":[],"myListCount":50013,"favoriteCount":133348,"workTypeList":["anime"],"vodType":"svod","ageLimitType":"0","workIconInfoList":["1","1","0","0","0",
//       （以下省略）
// データ量が多くて一部しか表示できていませんが、データ自体はきちんと取れていますね。

```
ですが、文字列で返されても扱いにくいです。
そのため、二度手間感はありますが、文字列を扱いやすいJSONに変換します。

```javascript:GAS
 let jsonData = JSON.parse(response);

// console.log(jsonData)で確認すると↓
//{ resultCd: '00',
//  version: '1.5',
//  selfLink: 'https://anime.dmkt-sp.jp/animestore/rest/WS000103?rankingType=01',
//  data: 
//   { maxCount: 4520,
//     count: 300,
//     workList: 
//      [ [Object],
//        [Object],
//        [Object],
//        [Object],
//　　　　（省略）
//この段階では、各作品のデータは[Object]内に格納されたままなので、次の手順で取り出す必要があります。
```

これで変数``jsonData``にランキングのJSONを格納できました。
dアニメストアのランキングJSONはかなり複雑です。
[前回](https://qiita.com/Kaz_prog/items/1bdb4172fa70adc37449#json%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E8%A9%B3%E3%81%97%E3%81%8F%E8%A6%8B%E3%82%8B)説明した通り、作品それぞれのデータは``data``という階層の中の``workList``という階層に入っています。
（ややこしいですが、上下関係としては ``data`` > ``workList`` > ``作品データ`` という感じです。）
JavaScriptでJSONを扱うとき、奥の階層へアクセスするにはドットでつなげます。

```javascript:GAS
let list = jsonData.data.workList

// console.log(list)で確認すると↓
//Logging output too large. Truncating output. [ { workId: '25182',
//    workInfo: 
//     { workTitle: 'ありふれた職業で世界最強 2nd season',
//       link: 'https://anime.dmkt-sp.jp/animestore/ci_pc?workId=25182',
//       mainKeyVisualPath: 'https://cs1.anime.dmkt-sp.jp/anime_kv/img/25/18/2/25182_1_6.png?1638419407000',
//       mainKeyVisualAlt: 'ありふれた職業で世界最強 2nd season_6',
//       workIcons: [],
//       myListCount: 36846,
//       favoriteCount: 110599,
//       workTypeList: [Object],
//       vodType: 'svod',
//       ageLimitType: '0',
//       workIconInfoList: [Object] },
//    rankingInfo: { rank: 1, arrowIcon: 'rankStay' } },
//  { workId: '25202',
//    workInfo: 
//     { workTitle: 'その着せ替え人形は恋をする',
//       link: 'https://anime.dmkt-sp.jp/animestore/ci_pc?workId=25202',
//       mainKeyVisualPath: 'https://cs1.anime.dmkt-sp.jp/anime_kv/img/25/20/2/25202_1_6.png?1639560672000',
//       mainKeyVisualAlt: 'その着せ替え人形は恋をする_6',
//       workIcons: [],
//       myListCount: 50029,
//       favoriteCount: 133362,
//       workTypeList: [Object],
//       （以下省略）
// データ量が多くて一部しか表示できていませんが、データ自体はきちんと取れていますね。
```

この場合、``jsonData``の中にある``data``の中にある``workList``を変数``list``に格納しています。

では、次に配列を用意して作品データを書き込んでいきましょう。

```javascript:GAS
  let outputList = []  //配列を用意
  let listLength = list.length  //for文を回す回数を、先ほどのJSON``list``の長さ（データ個数）から取得
  for (let i = 0; i < listLength; i++) { //カウントは0から始まります。
    let rank = list[i].rankingInfo.rank  //listの中のi個目のランク順位
    let title = list[i].workInfo.workTitle  //listの中のi個目の作品タイトル
    let myList = list[i].workInfo.myListCount  //listの中のi個目の作品のマイリスト登録数
    let favorite = list[i].workInfo.favoriteCount  //listの中のi個目の作品のお気に入り登録数
    outputList.push([rank, title, myList, favorite])  //配列に挿入
  }

// 全ての作品を配列に挿入し終わった後でconsole.log(outputList)で確認すると↓
//Logging output too large. Truncating output. [ [ 1, 'ありふれた職業で世界最強 2nd season', 36854, 110610 ],
//  [ 2, 'その着せ替え人形は恋をする', 50045, 133397 ],
//  [ 3, 'からかい上手の高木さん３', 19666, 55448 ],
//  [ 4, '天才王子の赤字国家再生術', 27624, 74302 ],
//  [ 5, '失格紋の最強賢者', 35218, 99288 ],
//  [ 6, '終末のハーレム', 17993, 47997 ],
//       （以下省略）
// データ量が多くて一部しか表示できていませんが、データ自体はきちんと取れていますね。
```

このままでは配列の中のそれぞれの数字が何を指すかわかりませんので、↑のコードの``let outputList = []``の部分を以下のように置き換えます。

```javascript:GAS
 let outputList = [['順位', '作品名', 'マイリスト', 'お気に入り']]
```

さらに、ランキングの取得時間といつのデータか日時を加えます。

```javascript:GAS
  let date = new Date();
  let today = Utilities.formatDate(date, 'JST', 'yyyy-MM-dd kk:mm:ss') //今日の時間
  date.setDate(date.getDate() - 1) //変数dateを一日戻し、
  let yesterday = Utilities.formatDate(date, 'JST', 'yyyy-MM-dd')  //昨日の日時として定義
```

配列の中に取得日時を加えたいため、``let outputList = [['順位', '作品名', 'マイリスト', 'お気に入り']]``の部分に日時を加えます。（dアニメストアのランキングは、昼12時に前日分のランキングを表示）

```javascript:GAS
  let outputList = [[yesterday, '(' + today + '取得)', '', ''], ['順位', '作品名', 'マイリスト', 'お気に入り']]
```

よって、ここまでのコードを合わせると以下のようになります。

```javascript:GAS
function danimeDaily() {
  let response = UrlFetchApp.fetch("https://anime.dmkt-sp.jp/animestore/rest/WS000103?rankingType=01").getContentText();
  let jsonData = JSON.parse(response);
  let list = jsonData.data.workList
  let date = new Date();
  let today = Utilities.formatDate(date, 'JST', 'yyyy-MM-dd kk:mm:ss')
  date.setDate(date.getDate() - 1);
  let yesterday = Utilities.formatDate(date, 'JST', 'yyyy-MM-dd')
  let outputList = [[yesterday, '(' + today + '取得)', '', ''], ['順位', '作品名', 'マイリスト', 'お気に入り']]
  let listLength = list.length
  for (let i = 0; i < listLength; i++) {
    let rank = list[i].rankingInfo.rank
    let title = list[i].workInfo.workTitle
    let myList = list[i].workInfo.myListCount
    let favorite = list[i].workInfo.favoriteCount
    outputList.push([rank, title, myList, favorite])
  }
}
```

あとは、この配列をスプレッドシートに書き込むだけ。

まずは、スプレッドシートを取得（以下、スプレッドシートIDとシート名を自身のものに書き換えてください。）。

```javascript:GAS
const daily = SpreadsheetApp.openById("スプレッドシートID").getSheetByName('シート名')
```

毎日取得してどんどん右にデータを追記していきたいので、最終列を取りその横に配列のデータを貼り付けます。

```javascript:GAS
let lastCol = daily.getLastColumn()  //最終列を取得
daily.getRange(1, lastCol + 1, 302, 4).setValues(outputList) //最終列の右の列1行目に、縦302（データ数(300)＋日時＋目次）、横4（順位＋作品名＋マイリスト登録数＋お気に入り登録数）の配列を貼り付け
```

最終的にコードの全容は以下のようになります。

```javascript:GAS
const daily = SpreadsheetApp.openById("スプレッドシートID").getSheetByName('シート名')

function danimeDaily() {
  let response = UrlFetchApp.fetch("https://anime.dmkt-sp.jp/animestore/rest/WS000103?rankingType=01").getContentText();
  let jsonData = JSON.parse(response);
  let list = jsonData.data.workList
  let date = new Date();
  let today = Utilities.formatDate(date, 'JST', 'yyyy-MM-dd kk:mm:ss')
  date.setDate(date.getDate() - 1);
  let yesterday = Utilities.formatDate(date, 'JST', 'yyyy-MM-dd')
  let outputList = [[yesterday, '(' + today + '取得)', '', ''], ['順位', '作品名', 'マイリスト', 'お気に入り']]
  let listLength = list.length
  for (let i = 0; i < listLength; i++) { //
    let rank = list[i].rankingInfo.rank
    let title = list[i].workInfo.workTitle
    let myList = list[i].workInfo.myListCount
    let favorite = list[i].workInfo.favoriteCount
    outputList.push([rank, title, myList, favorite])
  }
  let lastCol = daily.getLastColumn()
  daily.getRange(1, lastCol + 1, 302, 4).setValues(outputList)
}

```

以上のコードを実行すると、次のようなアウトプットになります。
![danime2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726763/ab0857ed-a21f-d053-fe7a-fb6eb9c4fca7.jpeg)

あとは、関数``danimeDaily``を毎日13時以降に実行させるようトリガーをセットします。
（dアニメストアは前日のランキングを当日昼12時に更新するため）
トリガーをセットすることで、毎日実行されてどんどんデータが追加されていくわけです。

今回は以上になります！