スクレイピングで物件探し！その２

# はじめに

[前回の記事](https://qiita.com/virusyun/items/bf784998d806a2c4634d)の続きです。

前回の記事で一応物件探しと、移動距離を出すことはできたのですが、物件情報を1行ずつ書き出すように書いていたので、実行時間が長いという問題点がありました。
そこで、サイト内の情報を丸ごと取ってくることで以前より高速にしました。
また、前回は次のページへ移動する機能がついてなかったので追加しました。

※Google Maps Platform を使用するように変更しました。Googleは外部からのスクレイピングは利用規約で禁止しています。私のように迷惑をかけないようお願いいたします。[利用規約](https://cloud.google.com/maps-platform/terms?_ga=2.219471129.103809388.1611700969-418788476.1611700969&_gac=1.53442266.1611701016.Cj0KCQiAmL-ABhDFARIsAKywVafwa6UWzmjEUfkB8rEj-)
また、サーバーに負荷をかける行為のため、利用規約で禁止していないサイトでも節度を持ってやるよう注意しましょう。
本記事では、1分の間隔を取ることとします。


# 前提

## 環境

Windows 10 バージョン 20H2
Python 3.7.4
jupyter notebook

## 必要なライブラリ

```python
pip install beautifulsoup4
pip install selenium
pip install openpyxl
pip install xlwt
```

## Chrome driver のダウンロード

chromeでスクレイピングするために必要になります。
「Chrome driver ダウンロード」で検索すると出てきます。
自身のchrome のバージョンと合っている driver をダウンロードする必要があります。

## Google Maps Platform

Google Maps Platform のDirections API を用いることで移動時間を計算します。
APIキー取得の方法はこちらの記事に詳しく書いてあります。
https://qiita.com/kngsym2018/items/15f19a88ea37c1cd3646

1か月に40000回までの呼び出しが無料なので、今回の目的は十分果たせそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993875/7ba4a41a-6422-e72f-f4db-99c68ca7f912.png)

## 利用するサイト

今回は賃貸サイトはSUUMOを利用します。

## 今回の探した物件の想定

- 東京スカイツリーから徒歩15分圏内
- 1K
- 65000円以下
- バストイレ別

の物件を探すという想定で、コードを書いていきます。

# コード

## モジュールのimportと目的設定

まず、必要になるモジュールを import します。

```python
import time
import re
import pandas as pd
import urllib.request, json
import urllib.parse

from bs4 import BeautifulSoup
from selenium import webdriver
```

次に、目的地と、徒歩何分まで許容するかを定義します。

```python
# 目的地 東京スカイツリー
DESTINATION = '東京都墨田区押上１丁目１−２'
# 徒歩何分まで許容するか
DURATION = 15

global df
df = pd.DataFrame()
```

Directions API の設定
api_key にはご自身で取得したキーを入力してください

```python
endpoint = 'https://maps.googleapis.com/maps/api/directions/json?'
api_key = 'hogehoge' # 取得したキーを入れる
google_map_url = 'https://www.google.co.jp/maps/dir/'
# Google maps api のwalking, driving, bicycling, transit, の中から移動手段を選択できます。
mode = 'walking'
```

## SUUMOサイトのスクレイピング

続いてSUUMOのサイトにアクセスします。
変数 url_suumoには気に入った条件で絞り込みをした後のURLを入力して下さい。

```python
### SUUMO スクレイピング

# インストールした chromedrive までのパスを設定
suumo_br = webdriver.Chrome('C:\\Users\\hogehoge\\chromedriver')
# MAC の場合
# suumo_br = webdriver.Chrome()
suumo_br.implicitly_wait(5)
url_suumo = "https://suumo.jp/jj/chintai/ichiran/FR301FC001/?ar=030&bs=040&pc=30&smk=&po1=25&po2=99&shkr1=03&shkr2=03&shkr3=03&shkr4=03&sc=13107&ta=13&cb=0.0&ct=6.5&co=1&md=02&et=9999999&mb=0&mt=9999999&cn=9999999&tc=0400301&fw2="
suumo_br.get(url_suumo)
time.sleep(5)
print('SUUMO にアクセスしました')
```

必要な関数を定義します。

```python
# 時間を分に変換する関数
def time_conversion(travel_time):
    minute = re.search('\d*分', travel_time)
    hour = re.search('\d*時間', travel_time)
    day = re.search('\d*日', travel_time)
    if minute:
        minute = int(re.search('\d*', minute.group()).group())
    else: minute = 0
    if hour:
        hour = int(re.search('\d*', hour.group()).group()) * 60
    else: hour = 0
    if day:
        day = int(re.search('\d*', day.group()).group()) * 60 * 24
    else: day = 0

    convert_travel_time = minute + hour + day
    return convert_travel_time

# 物件情報を取得する関数
def get_property(soup, building_list, map_url_list, min_travel_list):
    temp_df = pd.DataFrame()
    temp_df["建物名"] = building_list # 建物名
    temp_df["通勤時間"] = min_travel_list # 通勤時間
    temp_df.info
    temp_df.head()
    class_dict = {
        "家賃": "cassetteitem_other-emphasis", # 家賃
        "管理費": "cassetteitem_price cassetteitem_price--administration", # 管理費
        "間取り": "cassetteitem_madori", # 間取り
        "専有面積": "cassetteitem_menseki", # 面積
    }
    
    for item, value in class_dict.items():
        temp = [c.get_text() for c in soup.find_all(class_=value)]
        temp_df[item] = temp
            
    # suumo url の取得
    temp_df["map"] = map_url_list # Google Map

    # 修正前
    # url_list = [c.get('href') for c in soup.find_all(class_="js-cassette_link_href")]
    # 修正後
    url_list = ['https://suumo.jp' + c.get('href') for c in soup.find_all(class_="js-cassette_link_href")]
    temp_df["物件URL"] = url_list # 物件のURL
    df = pd.concat([df, temp_df], axis=0)
```

メイン関数

```python
def main():
    soup = BeautifulSoup(suumo_br.page_source, 'html.parser')

    # 物件の住所のリスト
    addresses = [c.get_text() for c in soup.find_all(class_='cassetteitem_detail-col1')]

    # 目的地からの距離を計算
    map_url = []
    travel_times = []
    
    # Directions API を用いて徒歩移動時間を取得
    for i, address in enumerate(addresses):
        nav_request = 'language=ja&origin={}&destination={}&mode={}&key={}'.format(address,DESTINATION, mode, api_key)
        nav_request = urllib.parse.quote_plus(nav_request, safe='=&')
        request = endpoint + nav_request
        response = urllib.request.urlopen(request).read()
        directions = json.loads(response)
        travel_time = directions['routes'][0]['legs'][0]['duration']['text']
        convert_travel_time = time_conversion(travel_time)
        travel_times.append(convert_travel_time)
        
        # google map の url を保存
        map_url.append(google_map_url + address + '/' + DESTINATION)
        print('徒歩時間:', travel_time)

    # 物件数の確認
    properties = soup.find_all('table', class_='cassetteitem_other')

    # サイト内の物件の数をカウントする
    properties_num_list = []
    for prop in properties:

        prop = str(prop)
        properties_num_list.append(prop.count('<tbody>'))

    # 建物名を取得
    buildings = [c.get_text() for c in soup.find_all('div', class_='cassetteitem_content-title')]
    
    building_list= []
    map_url_list = []
    min_travel_list = []

    for i, prop in enumerate(zip(buildings, map_url, min_travel_times)):
        for _ in range(properties_num_list[i]):
            building_list.append(prop[0])
            map_url_list.append(prop[1])
            min_travel_list.append(prop[2])

    get_property(soup, building_list, map_url_list, min_travel_list)
    
    # 次のページがあるか確認、あれば再実行
    next_path_list = [
        '//*[@id="js-leftColumnForm"]/div[11]/div[2]/p[1]/a',
        '//*[@id="js-leftColumnForm"]/div[11]/div[2]/p[2]/a'
    ]

    for next_path in next_path_list:
        try :
            text = suumo_br.find_element_by_xpath(next_path).text
            if text == '次へ':
                print('次のページへ')
                suumo_br.find_element_by_xpath(next_path).click()
                time.sleep(60)
                main()

        except:
            print('最終ページです')
main()
```

実行結果

```result
徒歩時間: 7 分
徒歩時間: 7 分
徒歩時間: 9 分
徒歩時間: 6 分
#...
最終ページです
```

家賃と管理費を数値化して加算

```python
df['家賃'] = df['家賃'].str.replace('万円', '')
df['管理費'] = df['管理費'].str.replace('円', '').replace('-', 0)
df['家賃'] = df['家賃'].astype('float')
df['家賃'] = df['家賃'] * 10000
df['管理費'] = df['管理費'].astype('float')
df['家賃'] = df['家賃'] + df['管理費']
df = df.drop('管理費', axis=1)
```

```python
df.head()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/993875/4d6a4544-0b2e-9e18-2295-9a0147fd5bce.png)



徒歩何分圏内まで保存するか設定します。

```python
df_15 = df[df['通勤時間'] < DURATION]
```

最後にエクセルファイルにします。

```python
df_15.to_excel('sample.xlsx', encoding='utf_8_sig', index=False)
```