BOOK☆WALKERのデイリーランキングをスクレイピングで取得する。

今回はBOOK☆WALKERのデイリーランキングを取得し、毎日Googleスプレッドシートに自動追記するGASを紹介します。

#完成像
![book1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726763/dc821d3e-8857-6a20-4bf3-065212a3db75.jpeg)
毎日ランキングが右に追記されていくようにします。

#データのソース
ランキングはこちらの[URL](https://bookwalker.jp/rank/?mode=daily)から見れます。
URL（`https://bookwalker.jp/rank/?mode=daily` ）の末尾`?mode=daily`でデイリーランキングを指定しています。この部分を `?mode=weekly`にすると週間ランキングに、 `?mode=monthly`にすると月間ランキングにアクセスできます。

#方法
今回はソースコードからランキング（と評価・レビュー数）を取得します。BOOK☆WALKERのランキングのソースコードは少し変わっています。前半部分にJSONファイルで必要な情報を書いていて、そこからJSで各作品の情報を引っ張ってきています（多分）。
簡単に言えば、ランキングの情報が書いてあるJSONを取得すればいいだけですね。

```javascript:GAS
  let url = "https://bookwalker.jp/rank/?mode=daily";
  let html = UrlFetchApp.fetch(url).getContentText('UTF-8');
```

GASのUrlFetchAppクラス（[fetchメソッド](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetch(String))）を使います。
`.getContentText()`でソースコード（HTML）を文字列として取得します。

ソースコードの中でも、必要なのはJSONの部分だけです。
そのため、余計な前後の部分を省く必要があります。
まずは、Parserライブラリを追加しましょう（やり方は[こちら](https://auto-worker.com/blog/?p=2460)を参照）。
必要なJSONは、ソースコードの中の`<script type="application/ld+json">`と`</script>`というタグの間にあります。

```javascript:GAS
 let list = Parser
    .data(html)
    .from('<script type="application/ld+json">')
    .to('</script>')
    .build();
  let jsonData = JSON.parse(list)
```
`.from()`と`.to()`で切り取る部分を指定します。
`list`は文字列なので、扱いやすいように`JSON.parse(list)`でJSON形式に直してあげましょう。
これで変数`jsonData`にJSON形式のランキング情報を格納できました。
それでは、JSONの形式を詳しく見ていきましょう。

```JSON:JSON
    {
        "@context": "http://schema.org",
        "@type": "Product",
        "name": "僕のヒーローアカデミア 33",
        "image": "https://c.bookwalker.jp/5759094/t_700x780.jpg",
        "description": "ヒーローへの信頼が揺らいでいる現状では生徒は不安を抱え、故に私たち教員は全力で彼らを守らなければならない！　雄英とは安心していつでも戻ってこられる場所。さあ！　君たちの声を…想いを…彼に届けるのさ！　“Plus Ultra”!!",
        "url": "https://bookwalker.jp/deab48af8f-6549-4000-8b56-9dcb2b18459d/",
        "category": "マンガ",
        "brand": {
            "@type": "Brand",
            "name": "集英社"
        },
        "offers": {
            "@type": "Offer",
            "price": 460,
            "priceCurrency": "JPY",
            "itemCondition": "http://schema.org/NewCondition",
            "availability": "http://schema.org/InStock",
            "seller": {
                "@type": "Organization",
                "name": "BOOK☆WALKER",
                "logo": "https://bookwalker.jp/henry/pc/img/common/bw-logo.png",
                "url": "https://bookwalker.jp/",
                "sameAs": "https://twitter.com/BOOK_WALKER"
            },
         "aggregateRating": {
            "@type": "AggregateRating",
            "ratingValue": 4.9,
            "reviewCount": 108,
            "bestRating": 5,
            "worstRating": 0
        }
    },
```

今回取得するのは作品名（`name`）、評価（`ratingValue`）、レビュー数（`reviewCount`）を取得します。
ちなみに、今回の場合はそれぞれの項目が何を指しているのか、[こちら](https://schema.org/AggregateRating)で確認できます。

それでは、以上の3項目を配列に入れましょう。

```javascript:GAS
  let outputList = [] //配列を生成
  let jsonLength = jsonData.length //JSONのデータ個数を取得
  for (let i = 0; i < jsonLength; i++) { //for文を回す回数をJSONのデータ個数にする
    let name = jsonData[i].name  //i個目の作品の名前
    let ratingValue = jsonData[i].aggregateRating.ratingValue　//i個目の作品の評価
    let reviewCount = jsonData[i].aggregateRating.reviewCount  //i個目の作品のレビュー数
    outputList.push([name, ratingValue, reviewCount])
  }
```
しかし、<b>このままではエラーが起きます。<b>
なぜなら、すべての作品に評価とレビュー数があるとは限らないからです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726763/550b2423-0cb9-163d-0c9c-5b7ce58976e4.png)

なので、このままだと評価・レビュー数がない作品を取得しようとすると以下のようなエラーが出ます。
>TypeError: Cannot read property 'ratingValue' of undefined

そのため、エラーが発生した場合は、当該作品の評価を飛ばす必要があります。

さらに見出しを追加したコードは以下になります。

```javascript:GAS

  let date = new Date();
  let today = Utilities.formatDate(date, 'JST', 'yyyy-MM-dd kk:mm:ss') //今日の日付を取得

  let outputList = [[today, '評価', 'レビュー数']] //配列を生成し、配列の見出し(日付、評価、レビュー数)を挿入
  let jsonLength = jsonData.length
  for (let i = 0; i < jsonLength; i++) {
    let name = jsonData[i].name
    try {
      ratingValue = jsonData[i].aggregateRating.ratingValue
    } catch { //エラーが発生した場合は
      ratingValue = "" //評価を空白に
    }
    try {
      reviewCount = jsonData[i].aggregateRating.reviewCount
    } catch { //エラーが発生した場合は
      reviewCount = ""  //レビュー数を空白に
    }
    outputList.push([name, ratingValue, reviewCount])
  }

//console.log(outputList)で確認すると↓
//[ [ '2022-02-06 21:53:48', '評価', 'レビュー数' ],
//  [ '僕のヒーローアカデミア 33', 4.9, 108 ],
//  [ 'ザ・ファブル　Ｔｈｅ　ｓｅｃｏｎｄ　ｃｏｎｔａｃｔ（２）', 4.8, 54 ],
//  [ '【電子版】B\'s-LOG COMIC 2022 Feb. Vol.109', 5, 3 ],
//  [ 'ラストギアス　（６）', 4.3, 22 ],
//  [ 'その着せ替え人形は恋をする 7巻', 4.8, 842 ],
//　（以下省略）
```
ちなみに、変数を定義する際に`let`を外してグローバル変数としています。

これで、必要な情報は配列に挿入できましたので、あとはスプレッドシートに追加するだけです。

最終列の1つ右の列に毎日どんどんデータを追記していきたいです。

```javascript:GAS
  let daily = SpreadsheetApp.openById("スプレッドシートID").getSheetByName('シート名')
  let lastCol = daily.getLastColumn() //最終列を取得
  daily.getRange(1, lastCol + 1, 101, 3).setValues(outputList) //最終列の右の列に101行（データ数(100)＋見出し）、3列（作品名、評価、レビュー数）のデータを書く
```
以上になります。完成したコードは以下です。



```javascript:GAS
function bookwalkerDaily() {
  let url = "https://bookwalker.jp/rank/?mode=daily";
  let html = UrlFetchApp.fetch(url).getContentText('UTF-8');
 
  let list = Parser
    .data(html)
    .from('<script type="application/ld+json">')
    .to('</script>')
    .build();
  let jsonData = JSON.parse(list)
 
  let date = new Date();
  let today = Utilities.formatDate(date, 'JST', 'yyyy-MM-dd kk:mm:ss')
 
  let outputList = [[today, '評価', 'レビュー数']]
  let jsonLength = jsonData.length
  for (let i = 0; i < jsonLength; i++) {
    let name = jsonData[i].name
    try {
      ratingValue = jsonData[i].aggregateRating.ratingValue
    } catch {
      ratingValue = ""
    }
    try {
      reviewCount = jsonData[i].aggregateRating.reviewCount
    } catch {
      reviewCount = ""
    }
    outputList.push([name, ratingValue, reviewCount])
  }
  let daily = SpreadsheetApp.openById("スプレッドシートID").getSheetByName('シート名')
  let lastCol = daily.getLastColumn()
  daily.getRange(1, lastCol + 1, 101, 3).setValues(outputList)
}
```

完成像の写真にある左端の順位は手動で1から100まで追加してください。

今回は以上となります！