楽天商品検索APIを使って商品情報の収集・データ加工を行う[Python]

## はじめに
[前回](https://qiita.com/Massasquash/items/ed565b8ba4fd51dca54f)は「楽天商品検索API」を使って30件（1ページ分）のデータを抽出し、csvファイルに保存するまでを行いました。

今回は分析して意思決定に繋げるためにより実用的なデータを揃える目的で、まず前回のスクリプトを発展させて取得するデータ件数を増やしたのちに、集計や分析の前段階として、商品データを商品重量ごと（5kg, 10kg...）に分類できるようなデータ加工まで行います。


## 方針
### （１）商品情報を取得する
- 検索キーワード「メークイン」商品情報を300件（30件×10ページ）取得する（NGワードも指定する）
- 必要な商品情報のみを入れたdict型を作成する
- PandasのDataFrameに格納し、列を整理する

前回と流れは同じですが、今回は取得件数とNGワードをここで指定しています。
これらはデータを集める目的や、必要な情報量と中身に応じて調整します。

NGワードは僕の場合は、自分の想定と合わず商品価格に影響してきそうなワードを指定しました。ここでは「有機栽培/オーガニック/減農薬/農薬不使用」「セット/詰め合わせ」「ふるさと納税」を除きます。
取得件数を300件としたのは特に深い意味はありませんが、今回のキーワード・NGワードで商品検索をかけたところ320件程度のヒット数だったため、キリの良い数字を選びました。

### （２）データ加工の手順
- 収集したデータから必要なデータのみを抽出する
- 「商品名」から重さ（5kg, 10kg...）を抽出し、新たな「入数」列を作る

本来は野菜の品種ごとにまで分けた方が正確な金額が出せるのですが、今回はそこまではしないことにしました。あまり細分化しすぎるとデータ数が減ってしまうためです。

## （1）商品情報を取得するスクリプト
### 1.必要なライブラリのインポートと入力パラメータの準備
ここは前回とほぼ変わりありませんが、APIでリクエストを送る入力パラメータの`page`と`NGKeyword`、そして`postageFlag`の部分のみ変えています。

楽天商品検索APIの１回のリクエストで取得できるデータの上限は30件（入力パラメータの`hits`の値）となっています。`page`にページ数`2`を指定してやれば、31件目以降のデータが取得できます。
この`page`の値を`for`文で回すことを想定しています。

また、入力パラメータの`postageFlag`は商品価格に送料を含むかどうかを指定しています（1で送料含むor送料無料）

```python
import requests
import numpy as np
import pandas as pd

REQUEST_URL = "https://app.rakuten.co.jp/services/api/IchibaItem/Search/20170706"
APP_ID="<楽天APIのIDを入れる>"

# 入力パラメータ
serch_keyword = 'メークイン'
ng_keyword = 'ふるさと納税 有機 オーガニック 減農薬 農薬不使用 セット 詰め合わせ'
page = 1
serch_params={
    "format" : "json",
    "keyword" : serch_keyword,
    "NGKeyword":ng_keyword,
    "applicationId" : [APP_ID],
    "availability" : 0,
    "hits" : 30,
    "page" : page,
    "sort" : "standard",
    "postageFlag" : 1
}
```

### 2.必要な商品情報のみを入れたdict型を作成する
`for`文を使って複数にまたがるページの商品情報を取得していきます。

商品情報から必要な項目のみ抜き出して`tmp_item`という名前のdictに格納し、それを`item_list`という名前のリストに格納しているのは、前回と同様の流れになります。

```python
# 商品情報をリストで取得
item_list = [] # 30件ずつ取得した辞書型の商品情報tmp_itemが10ページ分入る
max_page = 10
for page in range(1, max_page+1):
    serch_params['page'] = page
    # APIにリクエストを送り、結果として商品データresultを得る
    response = requests.get(REQUEST_URL, serch_params)
    result = response.json()

    # resultから必要な情報を抜き出したdictを作る
    item_key = ['itemName', 'itemPrice', 'itemCaption', 'shopName', 'shopUrl', 'itemUrl']

    for i in range(0, len(result['Items'])):
        tmp_item = {}
        item = result['Items'][i]['Item']
        for key, value in item.items():
            if key in item_key:
                tmp_item[key] = value
        item_list.append(tmp_item.copy())
```

ここで、仮に商品件数が10ページ分に満たないようなキーワードを入れた場合でも、特にエラーは出ずに取得することができました。ページ数は実際の商品数よりも大きく設定しても良さそうです（APIのリファレンスによると`page`の上限は100）。
ですがその分処理に時間がかかり、自分の環境だと稀にエラーが出てしまう現象が起きたので、必要最小限に留めた方が良いのかなと思います。

### 3.DataFrameを作成する
これも前回と同様に、先ほどの商品情報のdictが格納されたlistからPandasのDataFrameを作成します。
少し修正して、インデックスを0からではなく1から順番に振ってくれるようにしました。

```python
# データフレーム作成
df = pd.DataFrame(item_list)
df = df.reindex(columns=['itemName', 'itemPrice', 'itemCaption', 'itemUrl', 'shopName', 'shopUrl'])
df.columns = ['商品名', '商品価格', '商品説明文', '商品URL', '店舗名', '店舗URL']
df.index = df.index + 1 # インデックスを1からに振り直す
```

`df.count()` で取得件数、`df.head()`で先頭５件のデータを確認。
想定どおりの300件のデータが入っていれば大丈夫そうです。

### 4.csv出力
毎回このスクリプトを走らせてデータを取得するのも面倒なので、使いたい時に使えるようにcsv出力しておきます。

```python
df.to_csv('20200914_rakuten_mayqueen.csv')
```


## (2)データ加工・集計のスクリプト
### 1.csv読み込み
データの件数を増やすことができたので、次はそのデータを加工して分析に適した形にしてみます。
このまま進めても良いですが、いったん先ほどのcsvフィルを読み込んでみてから進めます。

```python
df = pd.read_csv('20200914_rakuten_mayqueen.csv')
```
### 2.必要なデータのみを抽出する
このDataFrameを加工していくのですが、まずはざっとデータの中身をspread sheetで眺めてみると、今回の価格調査の目的には当てはまらない、野菜以外の余計なデータが混じっていました。
今回必要なのは重量のデータなので「商品名に重量が入っている商品データのみを残す」という方針でやってみます。

Pandasで、指定した文字列を含む（部分一致）行を抽出してbool値で返す`str.contains()`を使います。

```python
# 商品名に「kg」が入っている商品データのみ残す
kg_flg = df['商品名'].str.contains('kg')
df = df[kg_flg]
```

`kg_flg`はbool値のSeriesとなっていて、「kg」が含まれている行にはTrue,そうでない行にはFalseが入ります。Trueになっている行が必要な残したいデータです。
これを用いて`df[kg_flg]`とすることで、Trueの行だけデータの入ったDataFrameを抽出することができます。

`df.count()`で件数を確認すると、116件とだいぶ減ってしまいました。
よりデータ数を確保するなら、この辺りはもうちょっと検証する必要がありそうです。

### 3.商品名から重量を抽出して新たな列を作る
これで商品名にkgが含まれている行のみが残りましたが、さらにこの重量を別の列に切り出したいです。
商品名には「数字 + kg」という形で重量が入っているはずなので、これの数値を取り出して「入数」という列を新しく作ります。

ここでは正規表現を使ってみます（詳細な説明は省きますが、`([0-9]+)kg`とすることで「数字 + kg」を表現できます）
この正規表現をPandasの`str.extract()`の引数に指定します。このメソッドは引数に正規表現を指定すると、それに最初にマッチした文字列を抽出して新たな列を作る、というものです。まさに今回やりたいことにぴったりな便利メソッドです。

```python
# 商品名から重量を別カラムに切り出す
df['入数'] = df['商品名'].str.extract('([0-9]+)kg')
df =df.reindex(columns=['商品名', '入数', '商品価格', '商品説明文', '商品URL', '店舗名', '店舗URL'])

df.to_csv('20200914_rakuten_mayqueen_2.csv')
```

見やすいように列の入れ替えも行い、最後にcsv出力をしておきます。
こんな感じでDataFrameを作ることができました。

![image](https://crieit.now.sh/upload_images/3a9a287d043e84762270b380660926425f609fb1250c2.png)

画像では、この後の分析のために入数を数値型に変換したのちに、商品価格を入数で割返したkg単価も付け加えてみました。ですが結構バラバラの数値が出ているので、ここは次回もうちょっと掘り下げてみます。

## おわりに
[次回](https://qiita.com/Massasquash/items/ef0e6f581e113ff4ba5d)、データを集計して商品価格の平均値などの統計量を算出してみたり、可視化や線形近似を行ってみてkg単価を算出してみたりと、意思決定に繋がりそうな簡単な分析を行ってみようと思います。