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

###更新
[2022/02/06追記] 今回の記事のやり方よりも、もっとスマートな方法で実装しました！
新しい記事は[こちら](https://qiita.com/Kaz_prog/items/e9aac1fb8deb2b08a8b2)から。


#はじめに
今回は[dアニメストアのランキング](https://anime.dmkt-sp.jp/animestore/CR/CR00000013 "dアニメストア デイリーランキング")をスクレイピングで取得する方法を書きたいと思います。スクレイピングをする際には、必ずサイトの利用規約等でスクレイピングが禁止されていないか確認しましょう。dアニメストアにはそのような規約はありませんでした。

#実装すること
dアニメストアのデイリーランキングを毎日取得してGoogleスプレッドシートに出力します。

#dアニメストアのサイト構造
[dアニメストアのランキング](https://anime.dmkt-sp.jp/animestore/CR/CR00000013 "dアニメストア デイリーランキング")は動的サイトです。阿部寛のようなシンプルなサイトとは違い、ゴリゴリにJavaScriptが使われています。さらに、ランキングは無限スクロールになっています。Twitterのように、下にスクロールすればするほど新しい情報がでてきます。このようなサイトをスクレイピングするのは少し厄介です。

今回はHeroku Schedulerという機能を使って毎日同じ時間にスクレイピングを実行させます。そして、スクレイピングした情報をGoogleスプレッドシートに出力します。
#定期実行スクレイピングの方法
スクレイピングを定期的に実行する場合、一番簡単なのはGAS (Google App Script)を使う方法です。[このサイト](https://auto-worker.com/blog/?p=2460#toc_id_2_3)のように、調べればいくらでも方法が出てきます。GASでスクレイピングをするときはParserというライブラリを使います。ですが、Parserライブラリは動的サイトに対応していません。Parserに加えてPhantomJSという外部のAPIを使えばできることはできるそうなのですが、高確率で失敗します。どうやら、GASでParserを使ってスクレイピングするとうまくいかないことが多いそう。

では、どうしましょう。参考にしたのは[この動画](https://www.youtube.com/watch?v=Pu3gmdWsLYc)(英語)です。結論から言うと、サイトの通信からXMLHttpRequestを使ってJSONを取得します。今回は、なるべくわかりやすい方法で実装していきます。すべての工程を書くとかなり長くなりますので、既にネットに情報が溢れている部分は割愛しています。

#dアニメストアの構造を詳しく見る。
[dアニメストアのランキング](https://anime.dmkt-sp.jp/animestore/CR/CR00000013 "dアニメストア デイリーランキング")へアクセスします。Chromeを使っている場合、右クリックを押すと「検証」というボタンが出てきますのでクリックしましょう。
![図4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726763/24768b9c-a347-ba4d-4986-790335e62060.jpeg)


画像の①、②を順にクリックしたら、ブラウザの更新ボタンを押してリロードしましょう。すると下半分にいろいろ情報がでてきます。その中の、「WS000103?ranking～」（画像③）をクリックすると情報がでてきます。画像の緑枠のを見ると、JSONファイルが受け渡されていることがわかります。

次に、③を右クリックして、「Open in new tab」(新しいタブで開く)をクリックしましょう。
![図2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726763/caee705f-6def-f22d-faa8-4894cb1526c2.jpeg)
すると↓のようなページがでてきます。
![図3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726763/2392dbbb-794c-384d-7bd6-bac5e15cb10a.jpeg)
よく見てみると、アニメのタイトルのような文字列がありますね。dアニメストアのランキングは、このJSONファイルをもとに出力されているのです。

ということは、私たちもこのURL(JSONファイル)を取得できれば、ランキングの情報が得られるわけです。

このページのURLを詳しく見てみましょう。
``https://anime.dmkt-sp.jp/animestore/rest/WS000103?rankingType=01&needScene=1&mainSceneSize=6&length=20&mainKeyVisualSize=1&_=1625381124923``

``rankingType=01``はランキングの種類を表しています。ちなみに、``01``を``02``に変えれば、週間ランキングを取得できます。
それ以降の文字列は、閲覧しているデバイスの画面サイズの情報です。無視して削除します。

#JSONファイルを詳しく見る
先ほどのJSONを少し整形・省略して見やすくしたものが以下になります。
![図5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726763/fde175aa-59e8-7292-f1e6-a7b87e01f9fe.jpeg)
``"workTitle"``という部分に作品名がありますね。これを取得したいと思います。
ちなみに、このJSONファイルでは作品が上からランキング順に並んでいるので、特にランク順を表す``"rank"``(画像範囲外)は取得しません。


今回はPythonを使います。灰色の枠のように場所を指定することで、作品名を取得できます。
③[i]を任意の番号に変えれば、特定の順位の作品名を取得できます。
（一位なら[0]、二位なら[1]である点に注意。）

#Pythonのファイル
今回は、配列に作品名を、ランキング順に格納します。

```Python:danime.py
import json, requests 

#ランキングを取得する
url = requests.get("https://anime.dmkt-sp.jp/animestore/rest/WS000103?rankingType=01")
text = url.text #ランキングのデータをテキストとして取得
data = json.loads(text) #テキストをJSONに変換

array = [] #ランキングを格納する配列を生成
array.clear()

for i in range(0, 300): #ランキング（タイトル名のみ）を配列に格納する
  title = data["data"]['workList'][i]['workInfo']['workTitle']
  array.append(title)
```
for文を使うことで、ランキングを上から順に配列へ格納できます。
ちなみに、dアニメストアのランキングサイトでは200位までしか確認できませんが、このようにプログラミングで取得すると300位まで確認できます。

さて、次に取得した配列をスプレッドシートに出力します。

#Spreadsheet APIを使う
[Spreadsheet API](https://developers.google.com/sheets/api/quickstart/python)を使えば、スプレッドシートをプログラミングで操作できます。

ここでは、基本的な操作方法は扱いません。[公式サイト](https://developers.google.com/sheets/api/quickstart/python)を見たり、検索エンジンで調べていくと色々情報が出てきますので試してみてください。ちなみに、私のコードもこのサンプルコードを基に書いています。
**APIを使うときは、credentials.jsonを同じディレクトリに置くことを忘れないように注意してください。**

#ランキング情報をSpreadsheetに書き込む
```Python:danime.py

from __future__ import print_function
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
import json, requests 

#ランキングを取得する
url = requests.get("https://anime.dmkt-sp.jp/animestore/rest/WS000103?rankingType=01")
text = url.text #ランキングのデータをテキストとして取得
data = json.loads(text) #テキストをJSONに変換

array = [] #ランキングを格納する配列を生成

for i in range(0, 300): #ランキング（タイトル名のみ）を配列に格納する
  title = data["data"]['workList'][i]['workInfo']['workTitle']
  array.append(title)

#スプレッドシートにデータを移す

# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']

# The ID and range of a sample spreadsheet.
SPREADSHEET_ID = 'スプレッドシートID'
RANGE_NAME = 'Sheet1!B3' #出力するセル

creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.json'):
    creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
    if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
    else:
        flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
        creds = flow.run_local_server(port=0)
# Save the credentials for the next run
    with open('token.json', 'w') as token:
        token.write(creds.to_json())

service = build('sheets', 'v4', credentials=creds)

#スプレッドシートに書き込む情報・形式
values = [array]
body = {
    'values': values,
    'majorDimension' : 'COLUMNS', #縦方向に出力する
    }

#スプレッドシートに書き込む
result = service.spreadsheets().values().update(
    spreadsheetId=SPREADSHEET_ID,
    range=RANGE_NAME,
    valueInputOption = 'USER_ENTERED',
     body=body).execute()

```
このコードを実行できると、以下のようになります。
![図３.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726763/262c8571-3fa4-73ba-f123-7e938f2c5344.jpeg)
集計日は取得日の前日です。自分でTODAY関数を使って実装できます。
これでスプレッドシートにランキングを出力できました。
次に、これらスクレイピング一連の動作を定期的に実行させます。

#定期的にスクレイピングを実行する
以下のような流れを想定します。

1. Heroku Scheduler でpythonファイルを毎日12:30に実行する。（dアニメストアのランキングは毎日12:00に更新されるため。）
1. 13:00～14:00の間に、スプレッドシートに出力した情報を別シートにコピーして保存する。

まずは一つ目。
###1. Heroku Scheduler でpythonファイルを毎日12:30に実行する
申し訳ないですが、基本的なHeroku Schedulerの使い方はご自身で調べてください。
``python danime.py(自分のファイル名)``というコマンドをHeroku Schedulerを使って定期的に実行させるだけです。
注意点としては、**Herokuでpythonを実行するときにはrequirements.txtというファイル必要です。同じディレクトリに配置してください。**

###2. スプレッドシートに出力した情報を別シートにコピーして保存する
GASを使って、出力した情報を別シートに保存します。

```javascript:myFunction.js
function myFunction() {
  var ash = SpreadsheetApp.getActiveSpreadsheet()
  var todayRank = ash.getSheetByName("Sheet1") //当日のランキングを出力するシート
  var thisSheet = ash.getSheetByName("Sheet2") //ランキングを毎日集積するシート
  var copyValue = todayRank.getRange('B1:B305').getValues(); //当日のランキングをコピーする

  var lastColumn = thisSheet.getLastColumn(); //ランキングを集積したシートの最後の列
  var addColumn = thisSheet.getLastColumn()+1; //当日のランキングを追加する列。ランキング最後の列の次の列  
  thisSheet.getRange(1, addColumn, copyValue.length, copyValue[0].length).setValues(copyValue);  //当日のランキングを所定の列にペーストする
}
```
このスクリプトをトリガーを使って毎日指定時刻に実行させます。

##実行結果
うまくいけば、以下のようなスプレッドシートができます。
![図6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1726763/e50d439b-e2dd-4676-f57b-ca282ad8c379.jpeg)

##これから
せっかくランキングを取得できたので、ランキングのグラフを作りたいですね。
ただ、ランキングのグラフをGASで作るのは難しいので、これから方法を考えたいですね。
分かり次第更新していきます。
((というか、JSONを取得するだけならGASのUrlFetchApp.fetchで取れたのでは？？もしかしてHeroku Schedulerも要らなかった？？
...と思いGASでも書いてみましたが、うまくいきませんでした。なぜでしょう...?))

###→→ GASだけで実行できるようになりました！！
新しい記事は[こちら](https://qiita.com/Kaz_prog/items/e9aac1fb8deb2b08a8b2)から。