【スクレイピング】 他社の商品データを収集

![974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3132303839382f30313366663136332d333730642d373335382d376363612d3839346637393636613138302e706e675のコピー35436.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120898/9814b979-709f-5714-35bf-267fc04c33e5.png)

#概要

Pythonでのスクレイピングにより、競合など**他社サイトに掲載されている商品データを収集**し、

マーケティング・**競合分析への活用**のため、CSVファイルにまとめ、出力します

#対象ページ

スクレイピング対象は、以下のようなページです

- インテリアのECサイト
- チェア：カテゴリの商品一覧ページ

![demostore2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120898/b2ab2dc0-92e2-7632-0660-36ec8146dd2f.png)

##対象データ

今回は、先述のページから

- 商品名
- 価格
- レビュー｜評価値
- レビュー｜投稿数

以上 4つの情報を、チェア：カテゴリの全商品分、取得します

#注意

- 記事内の「Webサイト／URL／データ」は全てダミーです
- 記事内のコードで、実際にスクレイピングする事は可能ですが、実在のサイトに対するスクレイピングは、サーバー負担への配慮、著作権／利用規約の十分な確認等が必要となります

#対象分析

対象から利用制限が指定されていないか、対象ページの構造はどうなっているか、等を分析／確認していきます

##利用制限

人様のサイトからデータを取得するにあたり、まずはそのサイトの「著作権／利用規約ページ」をしっかり確認した上で、以下のコードでアクセスに対する制限を確認

. . . . .

クローリングへの指示書である「robots.txt」解析のためのモジュール`urllib.robotparser`をインポート

```py
import urllib.robotparser
rp = urllib.robotparser.RobotFileParser()
```

robots.txtの読み込み

```py
rp.set_url("https://demo-store.com/robots.txt")
rp.read()
```

`request_rate`で、クローラーに対し遅延時間が指定されていないかの確認

```py
req_rate = rp.request_rate("*")
```

```py
print(req_rate)
```

```ruby:output
None
```

遅延時間の指定は無し。とはいえ、相手サーバー負荷への配慮として、ロード遅延処理は必須

. . . . .

`can_fetch`でURLの取得が許可されているかを確認

```py
rp.can_fetch("*", "https://demo-store.com/")
```

```ruby:output
True
```

`True`が返され、URL取得の許可が確認できました

##ページ構造

Chrome｜デベロッパーツールを用い、対象ページの構造を確認

![arg8g7rbd6r87.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120898/418d0137-3140-2d9e-ce74-0ab636c9c527.png)


![ges8b9fg7ndf687.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120898/37d29b28-6fef-00e0-0a63-0dbe3679e484.png)



1商品ごとに`pd_box`というクラス名のパーツで囲われ、
その中に今回取得したい4つの商品情報が入っており、それぞれ以下のようにクラス名が付けられています

情報|クラス名|
:--|:--
商品名|pd_name
価格|pd_price
レビュー：投稿数|rev_count
レビュー：評価値|rev_rate

##URLの規則性

対象ページのURLは以下（※ ダミーURLです）
`https://demo-store.com/living/chair/?page=1`

また、この「チェア：カテゴリ｜商品一覧」は、全10ページで

![3efg57rs687bd6n9ht8d7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120898/07c716b3-eb85-bdee-1b7a-a3fc9129b29b.png)

10ページ目のURLは`https://demo-store.com/living/chair/?page=10`となっており、
`?page=XX`の箇所が、現在のページ数を表しているという事が分かりました

以上を踏まえ、スクレイピング実行のコードを考えていきます

#データ取得

実際にブラウザを起動・サイトを描画しながらスクレイピングする手法もありますが、今回はヘッダレスブラウザを用い、画面描写無しでサッサとスクレイピングしていきます

. . . . .

Selenium｜Chromeブラウザをインポート

```py
from selenium.webdriver import Chrome, ChromeOptions
from selenium.webdriver.common.keys import Keys
```

`--headless`でオプション：ヘッドレスモードを有効化

```py
options = ChromeOptions()
options.add_argument('--headless')
```

ChromeのWebDriverオブジェクトを作成

```py
browser = Chrome(options=options)
```

後に商品情報を格納していく、空のリストを作成

```py
names = []
prices = []
rev_rates = []
rev_counts = []
```

今回の対象は「商品一覧：10ページ分」つまり

`https://demo-store.com/living/chair/?page=1`から
`https://demo-store.com/living/chair/?page=10`までが対象です

よって`?page={}`として、ページ数箇所を変数とし、`format(i)`で変数箇所に`i`を埋め込みます

`i`には`for`文で1から10までの数字を順に当てはめていきます

```py
for i in range(1,11):
    url = 'https://demo-store.com/living/chair/?page={}'.format(i)
```

ややこしいですが`range(1,11)`で、1から10までの数字が入ることになります

. . . . .


相手サーバーへの負荷配慮として`implicitly_wait`でロードの際に3秒の間隔が空くように設定

```py
    browser.implicitly_wait(3)
```

「`?page={}`箇所に連番が入ったURL」を取得

```py
    browser.get(url)
```

一覧ページでは、商品ごとに`pd_box`で囲われていました
その中に、それぞれの商品名（`pd_name`）や価格（`pd_price`）が含まれている

![ges8b9fg7ndf687.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120898/37d29b28-6fef-00e0-0a63-0dbe3679e484.png)


よって`pd_box`の中身を探し終えたら、次の`pd_box`の中身を探し始める、という内容の`for`文とします

`find_elements_by_class_name`で指定したクラス名の要素を取得し、リストとして`pd_boxes`に格納

```py
    pd_boxes = browser.find_elements_by_class_name('pd_box')
```

`pd_box`1つあたりに対し、「pd_name／pd_price／rev_rate／rev_count」を探し、取得し、それぞれリストとして格納していく。完了したら次の`pd_box`に対し同様の処理を行っていく

```py
    for pd_box in pd_boxes:

        name = pd_box.find_element_by_class_name('pd_name').text
        names.append(name)

        price = pd_box.find_element_by_class_name('pd_price').text
        price = price.replace("¥","").replace(",","")
        prices.append(price)

        rev_rate = pd_box.find_element_by_class_name('rev_rate').text
        rev_rates.append(rev_rate)

        rev_count = pd_box.find_element_by_class_name('rev_count').text
        rev_count = rev_count.replace("(","").replace("件)","")
        rev_counts.append(rev_count)
```

名前や価格を取得しつつ、「¥」や「件」などの不要文字列は`replace`により削除しています

. . . . .

取得したデータを確認

```py
names
```

```ruby:output
['Ultra Chair','Super Chair','Hyper Chair'...]
```

```py
prices
```

```ruby:output
['10000','20000','30000'...]
```

```py
rev_rates
```

```ruby:output
['5','5','5'...]
```

```py
rev_counts
```

```ruby:output
['26','36','46'...]
```

ちゃんとデータ取得できています

#CSV出力

取得したデータは現時点では個々のリストになっているので、データフレームとしてまとめます

```py
import pandas as pd
df = pd.DataFrame()
```

リスト毎に列を作成

```py
df['name'] = names
df['price'] = prices
df['rev_rate'] = rev_rates
df['rev_count'] = rev_counts
```

内容確認のため、データフレームの上から5行目までを表示

```py
df.head()
```

|| name | price | rev_rate | rev_count |
|:--|:--|:--|--:|--:|--:|
1| Ultra Chair | 10000 | 5 | 26 |
2| Super Chair | 20000 | 5 | 36 |
3| Hyper Chair | 30000 | 5 | 46 |
4| Miracle Chair | 40000 | 5 | 56 |
5| Passion Chair | 50000 | 5 | 66 |

問題無くデータフレームになっているので、CSVファイルに変換します

```py
df.to_csv('living_chair.csv', index=False)
```

![gre68vf7sf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120898/095db23a-7f4c-00ea-937c-59983d2f077c.png)



CSVファイルが出力されました

#総括

**他社サイトの商品情報**をスクレイピングで収集し、CSVファイルにまとめました

- 各商品のレビュー投稿数を、販売数とある程度の相関があると仮定し、販売数予測の目安とする
- 価格帯ごとのレビュー評価（満足度）を分析する

など、**競合分析において活用のできる、データファイルが取得できた**かと考えます