akatak’s blog

プログラム初心者の50代ビジネスマンがセカンドキャリアを目指して働きながらPythonを中心に独学しています。自らの覚え書きや感じたことなどを脈絡もなく書き連ねるブログです。

不動産情報のデータ取得&分析【賃貸物件/Webスクレイピング編】

暑い日が続きますね。皆さんはいかがお過ごしでしょうか。

我が家では、不動産の賃貸で住み替えか?購入か?を検討しなくてはいけない局面が生じたので、何かデータを取得して分析できないかな、とネットサーフィンをしていたところ、発見!

【データで見る世界】機械学習を使って東京23区のお買い得賃貸物件を探してみた 〜スクレイピング編〜

ありがとうございます。このような先人の方がいらっしゃると、真似をしつつ、何とかやってみようという気がしてきます。

さて、まずは、東京23区の賃貸物件について、「データで見る世界」さんのブログを参考にして取得してみることにしましょう。「データで見る世界」さん同様にSUUMOさんからデータを取得していくにあたり、まずはSUUMO規約を確認しましょう。

SUUMO(スーモ)規約

私的利用を超えて使用してはいけないことは書かれていますが、それ以外に、スクレイピングに関する制限はなさそうですね。どうやらスクレイピングをしても大丈夫そうです。

不動産情報を取得する準備

まず、いつものようにrequestsモジュールとBeautifulSoupモジュールをimportしましょう。それ以外にもtimeモジュールとpandasモジュールもimportしておきます。

import requests
from bs4 import BeautifulSoup
import time
import pandas as pd

SUUMOさんのホームページから「関東」「賃貸物件」と選択していくと、「関東の賃貸住宅[賃貸マンション・アパート情報探し」のページとなり、「沿線・エリアから探す」よう選択が求められますので、「東京都」「エリア」を選びます。そうすると、「東京都ー市区郡を選択」するページが表示されます。ここで東京23区を全て選択してもいいかと思いますが、全部で20万件程度のデータになるかと思います。時間もかかるし、実行中にエラーとなる可能性も高くなりますので、区毎にデータを取得していきます。

最初は、足立区を選択してみましょう。足立区の賃貸物件検索ページのアドレスをコピペし、urlに格納します。

# SUUMO 賃貸 東京都足立区 検索
url = 'https://suumo.jp/jj/chintai/ichiran/FR301FC001/?ar=030&bs=040&ta=13&sc=13121&cb=0.0&ct=9999999&et=9999999&cn=9999999&mb=0&mt=9999999&shkr1=03&shkr2=03&shkr3=03&shkr4=03&fw2=&srch_navi=1'

いつものようにurlの情報を取得して、htmlで構文解析を行い、結果をsoupオブジェクトに格納します。そして、そのsoupオブジェクトを通じて、基本的にデータを取得していくことになります。

result = requests.get(url)
c = result.content
soup = BeautifulSoup(c, "html.parser")

実際にホームページをご覧になると分かると思いますが、物件データは数十ページに跨がっています。また、データ数によって日々、ページ数は変わってきますので、まずはページ数を取得して、その後1ページずつデータしていきます。

ページ数・urlの取得

# ページ数を取得
body = soup.find("body")
pages = body.find_all("div", {'class':'pagination pagination_set-nav'})
pages_text = str(pages)
pages_split = pages_text.split('</a></li>\n</ol>')
num_pages = int(pages_split[0].split('>')[-1])

「データで見る世界」さんとは、自分自身で理解しやすいように、スクリプトは変更しています。 実際にデータを取得していく際には、かなり変更している部分がありますので、参考にしていただければと思います。

さて、次に、urlsリストに1ページずつ、urlを加えていきます。ここはそのまま使わさせていただいています。

# 全てのページのURLを作成
# urlを入れる箱(リスト)を設定
urls = []

# 1ページ目を格納
urls.append(url)

# 2ページ目以降を格納
for i in range(num_pages-1):
    pg = str(i+2)
    url_page = url + '&pn=' + pg
    urls.append(url_page)

スクレイピングによるデータ取得の準備

まずは、全データを格納するリストdataとエラーが出たときにその内容を格納するリストerrorsを用意します。賃貸物件の件別データ(1つの物件でも複数の部屋の物件が出ている場合は、その件別も含む)を1つ1つ取り込みます。その場合の件別データそのものもリスト形式なので、dataはリストのリストになります。

data = []
errors = []

さて、スクレイピングの本体部分に入っていきます。urlsに格納したurlを一つずつ取り出して、1ページ毎にスクレイピングしていきます。

for url in urls:

スクレイピングの最中にエラーが出ても、スクリプトが動き続けるように、try〜exceptを入れます。ついでにerrorsにエラーの内容、エラーが出たときのurl、何番目のデータかを保存しておきます。

    try:
    
    # ここにスクリプトのエンジン部分を書きます。
    
    except Exception as e:
        errors.append([e, url, len(data)])
        pass    

いよいよ、スクレイピングのエンジン部分に入っていきましょう。url毎にsoupオブジェクトを作り、soupオブジェクトから各種情報を取り出すようにします。

        result = requests.get(url)
        c = result.content
        soup = BeautifulSoup(c, "html.parser")

Google Chromeの検証機能を使って探していくと、ページの中で件別データ全体を選択するのはdivタグのうち、id='js-bukkenList'のものになります。findメソッドの引数でこれを指定して、summaryに情報を格納します。更に、summaryに格納された情報の中で、各件別データを選択するのは、divタグのうち、class='cassetteitem'ですので、find_allメソッドの引数でこれを指定して、件別データをcassetteitemsに格納します。

        summary = soup.find("div",{'id':'js-bukkenList'})
        cassetteitems = summary.find_all("div",{'class':'cassetteitem'})

スクレイピングのエンジン部分

その後は、cassetteitemsから件別データ(cassetteitem)毎に情報を取り出していきます。この段階でもエラー処理を行えるようにしております。

        for cas in cassetteitems:
            try:
              # 情報取得用の箱を準備します。
                new = ''  # 新着
                subtitle = '' # 物件名
                location = '' # 住所
                station_list = []  # 最寄駅(リスト)
                yrs = ''           # 築年数
                heights = ''      # (建物の)階数
                floor = ''         # (物件のある)階数
                rent = ''      # 家賃
                admin = ''         # 管理費
                others = ''     # その他(敷金/礼金等)
                floor_plan = ''   # 間取り
                area = 0       # 面積

                # 物件名
                subtitle = cas.find("div",{"class":"cassetteitem_content-title"}).string

                # 住所
                location = cas.find("li",{"class":"cassetteitem_detail-col1"}).string

                # 最寄駅
                sta = cas.find("li", {"class":"cassetteitem_detail-col2"})
                stas = sta.find_all("div", {"class":"cassetteitem_detail-text"})
                for s in stas:
                    station_list.append(s.string)

                # 築年数、階数
                col3 = cas.find("li",{"class":"cassetteitem_detail-col3"})
                yrs = col3.find_all('div')[0].string
                heights = col3.find_all('div')[1].string

                tbodies = cas.find_all('tbody')

                for tbody in tbodies:
                    cols = tbody.find_all('td')
                    for i, col in enumerate(cols):
                        if i == 0:
                            try:
                                new = col.span.string.strip('\r\t\n')
                            except:
                                new = ''
                        if i == 2:
                            floor = col.string
                        if i == 3:
                            rent = col.string
                        if i == 4:
                            admin = col.string
                        if i == 5:
                            others = col.string
                        if i == 6:
                            floor_plan = col.string
                        if i == 7:
                            area = float(col.contents[0].split('m')[0])
                    data.append([new, subtitle, location, station_list[0], station_list[1], station_list[2], yrs, heights, floor,
                                 rent, admin, others, floor_plan, area])
            except Exception as e:
                errors.append([e, url,len(data)])
                pass

        time.sleep(1) # スクレイピングする際の礼儀として、1秒待ちましょう

これで全ての物件データが取得でき、dataとerrorsに格納されました。

pandasを使ってデータをcsvファイルとして保存

さて、これらのデータをpandasのメソッドを利用してDataFrameオブジェクトに変換、その後、csvファイルとして保存します。

# data listを DataFrameに変換
df = pd.DataFrame(data, columns=['新着','物件名','住所','立地1','立地2','立地3','築年数','階数','物件階','家賃','管理費','敷金礼金','間取り','面積'])

# csvファイルとして保存
df.to_csv('data/suumo_adachi.csv', sep = ',',encoding='utf-8')

# ついでに errors fileも保存
df_errors = pd.DataFrame(errors)
df_errors.to_csv('data/errors_adachi.csv', sep = ',', encoding='utf-8')

以上でデータが保存できました。念のため、errorsを開いて、エラーがないことを確認しましょう。たまに、通信環境等によってか、エラーが発生することがありますが、少ない時は無視します。エラーが多い時だけ、時間を空けて再度トライしてみます。これでうまくいくことが多いので、エラーが発生した場合には、時間をおいてみましょう。

次回以降、取得したデータの加工をしていきます。