第３回 【Python】まだ見ぬアクティビティーを求めてアソビュー ！口コミ スクレイピング

# 1. 概要
この記事は第２回の続きです。

https://qiita.com/Tokoroteen/items/7f1460ffe0ea6ca959a4

第１回および第２回で、アクティビティー予約サイトである<a href="https://asoview.com/" target="_blank" rel="noopener noreferrer">アソビュー!</a>から掲載されている施設の基本情報を取得しました。

そこで、本来の目的である口コミ情報を学習し、オススメのアクティビティーを提示するモデルを構築するために口コミ集めの作業に入っていきます！

完成したアプリはこちら↓

https://lifac.herokuapp.com/

例えば「空を飛びたい」とき（検索後に海要素を足して、山要素を引くこともできます！！）

![スクリーンショット 2022-11-03 13.50.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/9b146bef-8190-1813-93c9-ab67e5ad1086.png)

例えば「動物に癒されたい」とき

![スクリーンショット 2022-11-03 13.50.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/4ce7ede4-a952-443b-59ba-807ceec0f2f2.png)

第１回：<a href="https://qiita.com/Tokoroteen/items/2bc88767a01124816a1a" target="_blank" rel="noopener noreferrer">【Python】まだ見ぬアクティビティーを求めてアソビュー ！スクレイピング</a>
第２回：<a href="https://qiita.com/Tokoroteen/items/7f1460ffe0ea6ca959a4" target="_blank" rel="noopener noreferrer">第２回 【Python】まだ見ぬアクティビティーを求めてアソビュー ！分析</a>
第３回：本記事
第４回：<a href="https://qiita.com/Tokoroteen/items/064c60bdea219909c85c" target="_blank" rel="noopener noreferrer"> 第４回 【Python】まだ見ぬアクティビティーを求めてアソビュー ！機械学習(Doc2Vec) </a>
第５回：<a href="https://qiita.com/Tokoroteen/items/1da9434e90ff407e6422" target="_blank" rel="noopener noreferrer">第５回 【Python】まだ見ぬアクティビティーを求めてアソビュー ！Doc2Vecモデルチューニング </a>


# 2. 口コミページの作りを確認
まずは口コミがどのように掲載されているかについて、以前からずっと気になっているインドアスカイダイビング施設<a href="https://www.asoview.com/base/151512/" target="_blank" rel="noopener noreferrer">FlyStation Japan</a>を例に確認してみます。

![flystationjapan.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/b623bbdc-37cd-9a0c-0312-744cfad55f33.jpeg)

口コミは<a target="_blank" rel="noopener noreferrer">https://www.asoview.com/base/151512/asorepo/list/</a>に掲載されており、`https://www.asoview.com/base/<<アクティビティーNo.>>/asorepo/list/`とすることで他の施設の口コミも見られそうです。

1ページに20件口コミが掲載されており、FlyStation Japanの場合は2022/09/30時点で240件の口コミが付いているので口コミページが12ページ存在します。

![asorepo_page.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/3a2da361-dec7-ba55-d94f-ebe1bbd7f780.jpeg)

試しに3ページ目に飛んでみると、<a target="_blank" rel="noopener noreferrer">https://www.asoview.com/base/151512/asorepo/list/?page=2</a>となり、口コミのトップページ(`https://www.asoview.com/base/151512/asorepo/list/`)に`?page=<<数字>>`を付け加えることでページを移動できます。

# 3. 口コミを取得してみる
先ほどまでの内容を踏まえて、FlyStation Japanの口コミを取得してみます。


![asorepo.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/a82d1817-1531-efbe-69d1-8afca29ea5b6.jpeg)

それぞれの口コミはclass名が"asorepo-article__asorepo-articles"のulタグの中に入っており、`<li class="asorepo-article__article">`が口コミ１つ分です。

そこで、
```python
import requests
from bs4 import BeautifulSoup
import time
from datetime import datetime
import pandas as pd
import re
import math
```
必要なライブラリをインストールしたら、
```python
url = 'https://www.asoview.com/base/151512/asorepo/list/'
res = requests.get(url)
soup = BeautifulSoup(res.text, "html.parser")

#口コミのブロック
review_block = soup.find("ul", class_="asorepo-article__asorepo-articles")
#各口コミのリスト
review_block_list = review_block.find_all("li", class_="asorepo-article__article")
```
として`<li class="asorepo-article__article">`全てをリストとして取得します。

口コミの
投稿日は`<time class="asorepo-article__post-date">`
評価は`<em class="asorepo-article__review-total-score-value">`
コメントは`<i class="asorepo-article__user-comment">`
に入っているので`review_block_list`から１つずつ取り出していきます。

```python
for r in review_block_list:
    #日付を取得
    r_date = r.find("time", class_="asorepo-article__post-date")
    r_date = re.sub(' \D','', r_date.text)
    r_date = datetime.strptime(r_date, '%Y/%m/%d')
    print(r_date)

    #評価を取得
    r_star = r.find("em", class_="asorepo-article__review-total-score-value")
    if r_star.text != "未設定": #評価があるとき
        r_star = int(r_star.text)
    else: #評価がないとき
        r_star = None
    print(r_star)

    #コメントを取得
    r_comment = r.find("i", class_="asorepo-article__user-comment")
    r_comment = r_comment.text
    print(r_comment)
    print('---------')
```
```
2022-09-26 00:00:00
5
ものすごく楽しかった
---------
2022-09-25 00:00:00
4
風に乗る感じです。
---------
2022-09-23 00:00:00
5
(以下略)
```
とこんな感じで口コミを取得できました！
ここで１つ問題が起こりました。この手法では次のように長いコメントの全文を取得できません。
![asorepo_long.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/36058886-e05e-d78e-f453-e907e93c44e6.jpeg)
```
未知の体験でとても良かったです。
１分だけ？短いな、と思いましたが、全身の筋肉を使うので逆に１分以上は無理だと思います。
ふわふわ浮くようなイメージをしていたのですが、強風に負けないよ...
```
となってしまいます。そこで長いコメントに対してはそのコメントの詳細ページまで行って取得するようにします。

```python
#コメントが長い場合は詳細ページまで確認しにいく
if "..." in r_comment:
    r_url = r.find("a", class_="asorepo-article__article-detail")
    r_url = r_url.get('href')
    r_url = "https://www.asoview.com" + r_url
    r_res = requests.get(r_url)
    r_soup = BeautifulSoup(r_res.text, "html.parser")
    r_comment = r_soup.find("pre", class_="asorepo-detail__text").text
```

長いコメントには末尾に"..."が付くので、それを頼りにコメントが省略されているかどうかを判別しました。
こうすることで口コミの日時と評価、およびコメントを取得できるようになりました！！

# 4. 口コミが掲載されているページのURL
上で確認した通り、1ページに20件の口コミが掲載されているので、それ以上を取得する場合は新たに訪問するべきページのリンクを知っておく必要があります。

![nubmer_of_asorepo.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2704675/02a9ef44-0764-1932-054f-45dd794bb8b6.jpeg)

口コミの総数はここに記述されているので、`口コミ総数/20`(小数点以下切り上げ)でページ数を取得し、全口コミの全リンクを取得します。

```python
#baseとなるURLとそのレスポンス
url_base = "https://www.asoview.com/base/151512/asorepo/list/"
res_base = requests.get(url_base)
soup_base = BeautifulSoup(res_base.text, "html.parser")

#URLの数
#口コミが存在するページ数を取得する
num_review = soup_base.find("b", class_="page-title").text
num_review = re.sub("(\D)*","", num_review)
num_review = int(num_review)
page_num = math.ceil(num_review/20)

#口コミのある全URLをリストに格納する
url_list = []
url_list.append(url_base)
for i in range(1, page_num):
    url = url_base + "?page=" + str(i)
    url_list.append(url)

print(url_list)
```
```
['https://www.asoview.com/base/151512/asorepo/list/', 'https://www.asoview.com/base/151512/asorepo/list/?page=1', 'https://www.asoview.com/base/151512/asorepo/list/?page=2', 'https://www.asoview.com/base/151512/asorepo/list/?page=3', 'https://www.asoview.com/base/151512/asorepo/list/?page=4', 'https://www.asoview.com/base/151512/asorepo/list/?page=5', 'https://www.asoview.com/base/151512/asorepo/list/?page=6', 'https://www.asoview.com/base/151512/asorepo/list/?page=7', 'https://www.asoview.com/base/151512/asorepo/list/?page=8', 'https://www.asoview.com/base/151512/asorepo/list/?page=9', 'https://www.asoview.com/base/151512/asorepo/list/?page=10', 'https://www.asoview.com/base/151512/asorepo/list/?page=11']
```

# 5. 口コミをアクティビティー毎に取得していく
ここまできたらあとは<a href="https://qiita.com/Tokoroteen/items/2bc88767a01124816a1a" target="_blank" rel="noopener noreferrer">第１回</a>に取得した基本情報のうち、アクティビティーNo.を用いて口コミを取得していきます！

まずは取得したいアクティビティーNo.のリスト
```python
#第１回の取得ファイル
df_base_data = pd.read_csv('base_data.csv')

#アクティビティーNo.のリスト
#10件以上レビューがあるものを使う
n_list = n_list = df_base_data[df_base_data['rating_asorepo']>=10]['No.']
print(n_list)
```
```
6          263
7          264
8          281
12         337
13         340
         ...  
8591    158104
8601    158220
8606    158341
8625    158646
8630    158757
Name: No., Length: 2865, dtype: int64
```
取得したデータを入れていく空のDataFrame
```python
#dfをつくる
#アクティビティーNo.／日付／評価／コメント
df_review_all = pd.DataFrame(index=[], columns=["No.", "date", "star", "comment"])
```

各アクティビティーの口コミを100件まで取得する関数

```python
#各アクティビティーの全口コミを取得する関数
def get_reviews(n_list, df_review_all): #引数はアクティビティーNo.のリストとDataFrame
    start=time.time()
    count=0 #取得したアクティビティ数を数える

    for n in n_list: #各アクティビティー毎
        r_count = 0 #口コミ数を数える
        print(f"No.{n}の口コミを集める")
        #そのアクティビティーの口コミをまとめるdf
        df_review = pd.DataFrame(index=[], columns=["No.", "date", "star", "comment"])

        #baseとなるURLとそのレスポンス
        url_base = f"https://www.asoview.com/base/{n}/asorepo/list/"
        res_base = requests.get(url_base)
        soup_base = BeautifulSoup(res_base.text, "html.parser")
        time.sleep(1)
        
        #URLの数
        #口コミが存在するページ数を取得する
        num_review = soup_base.find("b", class_="page-title").text
        num_review = re.sub("(\D)*","", num_review)
        num_review = int(num_review)
        page_num = math.ceil(num_review/20)

        #口コミのある全URLをリストに格納する
        url_list = []
        url_list.append(url_base)
        for i in range(1, page_num):
            url = url_base + "?page=" + str(i)
            url_list.append(url)

        try:
            #URLの各リスト毎に口コミを取得する
            for url in url_list:
                res = requests.get(url)
                soup = BeautifulSoup(res.text, "html.parser")
                time.sleep(1)

                #口コミのブロック
                review_block = soup.find("ul", class_="asorepo-article__asorepo-articles")
                #各口コミのリスト
                review_block_list = review_block.find_all("li", class_="asorepo-article__article")

                for r in review_block_list:
                    #日付を取得
                    r_date = r.find("time", class_="asorepo-article__post-date")
                    r_date = re.sub(' \D','', r_date.text)
                    r_date = datetime.strptime(r_date, '%Y/%m/%d')
                    

                    #評価を取得
                    r_star = r.find("em", class_="asorepo-article__review-total-score-value")
                    if r_star.text != "未設定":
                        r_star = int(r_star.text)
                    else:
                        r_star = None

                    #コメントを取得
                    r_comment = r.find("i", class_="asorepo-article__user-comment")
                    r_comment = r_comment.text
                    # print('コメント', r_comment)
                    
                    #空欄でないレビュー数
                    if r_comment != '':
                        r_count += 1
                        # print('レビュー数', str(r_count))

                    #コメントが長い場合は詳細ページまで確認しにいく
                    if "..." in r_comment:
                        r_url = r.find("a", class_="asorepo-article__article-detail")
                        r_url = r_url.get('href')
                        r_url = "https://www.asoview.com" + r_url
                        r_res = requests.get(r_url)
                        r_soup = BeautifulSoup(r_res.text, "html.parser")
                        time.sleep(1)
                        r_comment = r_soup.find("pre", class_="asorepo-detail__text").text
                    
                    #1行ずつdf_reviewに追加する
                    df_new = pd.DataFrame([[n, r_date, r_star, r_comment]], columns=["No.", "date", "star", "comment"])
                    #df_reviewと結合
                    df_review = pd.concat([df_review, df_new], axis=0)

                #アクティビティー毎のレビュー数の取得上限は100とする
                if r_count >= 100:
                    print('レビューが100件に達しました')
                    break
                    
        except requests.exceptions.HTTPError as e: #例外処理
            print("HTTPError:", e)
            continue

        except requests.exceptions.ConnectionError as e:
            print("ConnectionError:", e)
            continue
        
        except:
            print("エラー")
            continue

        df_review_all = pd.concat([df_review_all, df_review], axis=0)
        count+=1
        print(f"進捗率：{(count/len(n_list))*100}％")
        print("経過時間：", time.time()-start, "秒")
        print("=============================================")

        #1時間経ったら止める
        if time.time()-start > 3600:
            print('1時間に達しました')
            break

    return df_review_all
```

これで口コミを取得できます！！
```python
df_review_all = get_reviews(n_list, df_review_all)
#csvとして保存
df_review_all.to_csv('df_review_all.csv')
df_review_all.head()
```

```
	No.	date	star	comment
0	263	2022-09-19	5	最高のカヌー体験でした！ありがとうございました！
0	263	2022-09-16	5	ガイドの方が親切。\nツアー内容も充実していて、素敵な思い出になりました。
0	263	2022-08-28	5	
0	263	2022-08-18	5	
...
```

# 6. まとめ
今回は各アクティビティー毎の口コミを取得することができました。次回はこの口コミを学習して、オススメのアクティビティーをレコメンドしてくれるモデルを構築していきます💪

次回

https://qiita.com/Tokoroteen/items/064c60bdea219909c85c

第１回：<a href="https://qiita.com/Tokoroteen/items/2bc88767a01124816a1a" target="_blank" rel="noopener noreferrer">【Python】まだ見ぬアクティビティーを求めてアソビュー ！スクレイピング</a>
第２回：<a href="https://qiita.com/Tokoroteen/items/7f1460ffe0ea6ca959a4" target="_blank" rel="noopener noreferrer">第２回 【Python】まだ見ぬアクティビティーを求めてアソビュー ！分析</a>
第３回：本記事
第４回：<a href="https://qiita.com/Tokoroteen/items/064c60bdea219909c85c" target="_blank" rel="noopener noreferrer"> 第４回 【Python】まだ見ぬアクティビティーを求めてアソビュー ！機械学習(Doc2Vec) </a>
第５回：<a href="https://qiita.com/Tokoroteen/items/1da9434e90ff407e6422" target="_blank" rel="noopener noreferrer">第５回 【Python】まだ見ぬアクティビティーを求めてアソビュー ！Doc2Vecモデルチューニング </a>