Pythonでじゃらんの口コミをスクレイピングする

# はじめに
本記事では、Pythonを使ったスクレイピングの例として、[じゃらんの箱根湯本温泉][jalan]の口コミをスクレイピングし、csvで出力します。

[jalan]:https://www.jalan.net/yad385995/kuchikomi/4.HTML?contHideFlg=1&rootCd=7701&roomCrack=000000&maxPrice=999999&screenId=UWW3701&idx=60&smlCd=141602&dateUndecided=1&minPrice=0&yadNo=385995&callbackHistFlg=1&distCd=01

# 環境
python 3.10.4
Windows 11 Home
ChromeDriver 114.0.5735.110

# 必要なライブラリのインストール
seleniumとpandasを使用します。
seleniumはブラウザの操作を自動化するライブラリですが、スクレイピングも行うことができます。今回は「次へ」ボタンの押下も自動化し、全ページの口コミ取得を目的とするため、こちらを使用します。
pandasはデータ解析に使うライブラリです。今回はスクレイピングして取得したデータをcsvで出力する目的で使用します。
```python
pip install selenium
pip install pandas
```
# 実装
作成したコードは以下になります。

```python
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException
import pandas as pd
import time

# ブラウザ起動
chromedriver = 'chromedriver.exe'
service = Service(executable_path=chromedriver)
driver = webdriver.Chrome(service=service)
url = "https://www.jalan.net/yad385995/kuchikomi/?contHideFlg=1&maxPrice=999999&rootCd=7701" \
      "&roomCrack=000000&screenId=UWW3701&idx=0&smlCd=141602&dateUndecided=1&minPrice=0" \
      "&yadNo=385995&callbackHistFlg=1&distCd=01"
driver.get(url)

# データを取得し、2次元配列に格納する
table = []
isContinue = True
while(isContinue):
  parentElems = driver.find_elements(By.CLASS_NAME, "jlnpc-kuchikomiCassette__rightArea")
  for parent_i in parentElems:
    # 評価値
    room = parent_i.find_elements(By.TAG_NAME,"dd")[0].text
    bath = parent_i.find_elements(By.TAG_NAME,"dd")[1].text
    breakfast = parent_i.find_elements(By.TAG_NAME,"dd")[2].text
    dinner = parent_i.find_elements(By.TAG_NAME,"dd")[3].text
    customerService = parent_i.find_elements(By.TAG_NAME,"dd")[4].text
    clean = parent_i.find_elements(By.TAG_NAME,"dd")[5].text
    # 表題
    head = parent_i.find_element(By.CLASS_NAME, 'jlnpc-kuchikomiCassette__lead').text
    # 改行を削除した口コミ本文
    body = parent_i.find_element(By.CLASS_NAME, 'jlnpc-kuchikomiCassette__postBody').text.replace("\n","")

    # 配列にデータを格納
    rowData = []
    rowData.append(room)
    rowData.append(bath)
    rowData.append(breakfast)
    rowData.append(dinner)
    rowData.append(customerService)
    rowData.append(clean)
    rowData.append(head)
    rowData.append(body)
    table.append(rowData)

  #　次へボタンがあったらボタンを押す、無ければフラグを変更してループを終了する
  try:
    driver.find_element(By.CSS_SELECTOR, "a.next")
    nextButton = driver.find_element(By.CSS_SELECTOR, "a.next")
    nextButton.click()
    time.sleep(2)
  except NoSuchElementException:
    isContinue = False

#　配列からDataFrameを作成し、csvで出力する
df = pd.DataFrame(table, columns=['部屋','風呂','料理（朝食）','料理（夕食）',
                                   '接客・サービス','清潔感','見出し','本文'])
df.index.name = 'index'
df.to_csv("ホテル南風荘_口コミデータ.csv", encoding="utf-8")

#ブラウザの終了
driver.quit()
```

# 解説

### HTMLの構造
じゃらんの口コミページのHTMLは次のように構成されています。（2023年6月現在）
![名称未設定ファイル.drawio (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3292716/d9e35790-d615-ed89-8600-edfa117b3c5b.png)

クラス「jlnpc-kuchikomiCassette__rightArea」、「next」をループの材料にして全データを取得していきます。

### 親要素の取得

```python
  parentElems = driver.find_elements(By.CLASS_NAME, "jlnpc-kuchikomiCassette__rightArea")
  for parent_i in parentElems:
```
- `driver.find_elements(By.CLASS_NAME, "jlnpc-kuchikomiCassette__rightArea")`
クラスが「jlnpc-kuchikomiCassette__rightArea」である全要素を取得

`parentElems`をループに組み込み、子要素を取得する処理を共通化します。

### データの取得
```python
    # 評価値
    room = parent_i.find_elements(By.TAG_NAME,"dd")[0].text
    bath = parent_i.find_elements(By.TAG_NAME,"dd")[1].text
    breakfast = parent_i.find_elements(By.TAG_NAME,"dd")[2].text
    dinner = parent_i.find_elements(By.TAG_NAME,"dd")[3].text
    customerService = parent_i.find_elements(By.TAG_NAME,"dd")[4].text
    clean = parent_i.find_elements(By.TAG_NAME,"dd")[5].text
    # 表題
    head = parent_i.find_element(By.CLASS_NAME, 'jlnpc-kuchikomiCassette__lead').text
    # 改行を削除した口コミ本文
    body = parent_i.find_element(By.CLASS_NAME, 'jlnpc-kuchikomiCassette__postBody').text.replace("\n","")
```

- `parent_i.find_elements(By.TAG_NAME,"dd")[x].text`
`parent_i`内のタグ「dd」である要素を全て取得し、x番目の文字を取得
- `parent_i.find_element(By.CLASS_NAME, 'jlnpc-kuchikomiCassette__lead').text`
`parent_i`内のクラス名「jlnpc-kuchikomiCassette__lead」である最初の要素を取得
- `replace("\n","")`
文字列に含まれる改行を削除

親要素から必要なデータを取得していきます。
`parent_i.find_elements(By.TAG_NAME,"dd")[x].text`はxに0から5を指定し、以下のデータを取得します。
[0].text：部屋の点数
[1].text：朝食の点数
[2].text：接客の点数
[3].text：風呂の点数
[4].text：夕食の点数
[5].text：清潔感の点数

また`parent_i`内にクラス「jlnpc-kuchikomiCassette__lead」は1つしか存在しないため、確実に表題のデータを取得できます。

### 「次へ」ボタンの押下

```python
isContinue = True
while(isContinue):

############################
######上で解説した処理#######
############################

  try:
    driver.find_element(By.CSS_SELECTOR, "a.next")
    nextButton = driver.find_element(By.CSS_SELECTOR, "a.next")
    nextButton.click()
    time.sleep(2)
  except NoSuchElementException:
    isContinue = False
```

- `isContinue`
ループを続けるか判定する変数
- `driver.find_element(By.CSS_SELECTOR, "a.next")`
タグ「a」でクラス「next」を持つ最初の要素（次へボタン）を取得
- `time.sleep(2)`
2秒間待機
- `NoSuchElementException`
driver.find_elementで検索した要素が見つからない時に発生するエラー

次へボタンがあれば押下しデータをさらに取得、なければデータ取得処理を終了します。
2秒間待機を挟むのは、ページが読み込まれる前に要素の検索が実行され、エラーとなるのを防ぐためです。

### csv出力

```python
df = pd.DataFrame(table, columns=['部屋','風呂','料理（朝食）','料理（夕食）',
                                   '接客・サービス','清潔感','見出し','本文'])
df.index.name = 'index'
df.to_csv("ホテル南風荘_口コミデータ.csv", encoding="utf-8")
```
- `pd.DataFrame`
    - columns
    列名の指定 
- `df.index.name`
インデックス列の名称指定
- `df.to_csv`
DataFrameをcsvで出力

取得したデータをDataFrameに変換後、csvとして出力します。

# 終わりに
今回はじゃらんの口コミページを例にスクレイピングを行いました。
全データを取得する際はHTMLの構造が繰り返しになっている部分を見つけることが重要だと感じました。
今後は取得した口コミデータを使って分析を行う予定です。