統計学、機械学習などを使って身近な世界を分析したりするブログです

機械学習を使って東京23区のお買い得賃貸物件を探してみた 〜スクレイピング編〜

こんにちは、Shoです。

今年の6月にミシガン大学ロスを卒業し、晴れてMBAホルダーとなりました。12月までは大学に残って機械学習の研究をしているのですが、いよいよ帰国の時が近づいてまいりました。

来年の頭から東京に戻るので、どのへんに住もうかなぁと思案しておるところです。

しかし住居選びというのは考えなければいけない要因が多くて大変ですね。なるべくお買い得な物件を選びたいところですが、どの区がいいのか、広さはどのくらいの部屋にしようか、2LDKと3Kだとどっちがいいの?とか、これは人間の頭で考える案件ではありませんね。コンピューターができることは全部自動化してしまいたい。

ということで、やってみました。

機械学習を使って東京23区のお買い得賃貸物件を探してみた

物件情報サイトは色々ありますが、今回はSuumoさんを選択。著作権に関しては、利用規約に以下のように書いてあります。

「ユーザーは、本サイトを通じて提供されるすべてのコンテンツについて、当社の事前の承諾なく著作権法で定めるユーザー個人の私的利用の範囲を超える使用をしてはならないものとします。」

うん、これは私的利用だから大丈夫だろう。

まずは東京都23区の賃貸物件を全てスクレイピングしてデータフレームの形にまとめます。とりあえず足立区から始めましょう。

#必要なライブラリをインポート
from bs4 import BeautifulSoup
import requests
import pandas as pd
from pandas import Series, DataFrame
import time

#URL(東京都足立区の賃貸住宅情報 検索結果の1ページ目)
url = 'http://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'

#データ取得
result = requests.get(url)
c = result.content

#HTMLを元に、オブジェクトを作る
soup = BeautifulSoup(c)

#物件リストの部分を切り出し
summary = soup.find("div",{'id':'js-bukkenList'})

これで、足立区で検索した1ページ目の、物件情報が格納されている部分を切り出すことができました。1ページに30件表示した状態で、足立区の場合は225ページ(10月8日時点)ありますが、これは刻一刻と変わるので、このページ数も自動で拾ってくるようにしなければいけませんね。

#ページ数を取得
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>')
pages_split0 = pages_split[0]
pages_split1 = pages_split0[-3:]
pages_split2 = pages_split1.replace('>','')
pages_split3 = int(pages_split2)

はい、入りました。もっと良さげな方法もありそうですが、とりあえずこれで動くので進みましょう。次は、取得したページ数を使って、URLのリストを作ります。

#URLを入れるリスト
urls = []

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

#2ページ目から最後のページまでを格納
for i in range(pages_split3-1):
    pg = str(i+2)
    url_page = url + '&pn=' + pg
    urls.append(url_page)

1ページ目と2ページ目以降で分けているのは、少しURLの構造が違うからです。1ページ目のURL語尾が「~&srch_navi=1」に対して、2ページ目、225ページ目はそれぞれ「~&srch_navi=1&pn=2」、「~&srch_navi=1&pn=225」となっているので要注意です。

今回は、以下の情報をそれぞれの物件について取得するので、リストを用意しておきます。

name = [] #マンション名
address = [] #住所
locations0 = [] #立地1つ目(最寄駅/徒歩~分)
locations1 = [] #立地2つ目(最寄駅/徒歩~分)
locations2 = [] #立地3つ目(最寄駅/徒歩~分)
age = [] #築年数
height = [] #建物高さ
floor = [] #階
rent = [] #賃料
admin = [] #管理費
others = [] #敷/礼/保証/敷引,償却
floor_plan = [] #間取り
area = [] #専有面積

さて、いよいよクローリングに入ります。各ページですることは同じなので、あらかじめURLを格納しているリストの中でループを回し、上で用意したリストにどんどん放り込んで行きましょう。

#各ページで以下の動作をループ
for url in urls:
    #物件リストを切り出し
    result = requests.get(url)
    c = result.content
    soup = BeautifulSoup(c)
    summary = soup.find("div",{'id':'js-bukkenList'})
    
    #マンション名、住所、立地(最寄駅/徒歩~分)、築年数、建物高さが入っているcassetteitemを全て抜き出し
    cassetteitems = summary.find_all("div",{'class':'cassetteitem'})

    #各cassetteitemsに対し、以下の動作をループ
    for i in range(len(cassetteitems)):
        #各建物から売りに出ている部屋数を取得
        tbodies = cassetteitems[i].find_all('tbody')
        
        #マンション名取得
        subtitle = cassetteitems[i].find_all("div",{
            'class':'cassetteitem_content-title'})
        subtitle = str(subtitle)
        subtitle_rep = subtitle.replace(
            '[<div class="cassetteitem_content-title">', '')
        subtitle_rep2 = subtitle_rep.replace(
            '</div>]', '')

        #住所取得
        subaddress = cassetteitems[i].find_all("li",{
            'class':'cassetteitem_detail-col1'})
        subaddress = str(subaddress)
        subaddress_rep = subaddress.replace(
            '[<li class="cassetteitem_detail-col1">', '')
        subaddress_rep2 = subaddress_rep.replace(
            '</li>]', '')
        
        #部屋数だけ、マンション名と住所を繰り返しリストに格納(部屋情報と数を合致させるため)
        for y in range(len(tbodies)):
            name.append(subtitle_rep2)
            address.append(subaddress_rep2)

        #立地を取得
        sublocations = cassetteitems[i].find_all("li",{
            'class':'cassetteitem_detail-col2'})
        
        #立地は、1つ目から3つ目までを取得(4つ目以降は無視)
        for x in sublocations:
            cols = x.find_all('div')
            for i in range(len(cols)):
                text = cols[i].find(text=True)
                for y in range(len(tbodies)):
                    if i == 0:
                        locations0.append(text)
                    elif i == 1:
                        locations1.append(text)
                    elif i == 2:
                        locations2.append(text)
                        
        #築年数と建物高さを取得
        tbodies = cassetteitems[i].find_all('tbody')
        col3 = cassetteitems[i].find_all("li",{
            'class':'cassetteitem_detail-col3'})
        for x in col3:
            cols = x.find_all('div')
            for i in range(len(cols)):
                text = cols[i].find(text=True)
                for y in range(len(tbodies)):
                    if i == 0:
                        age.append(text)
                    else:
                        height.append(text)

    #階、賃料、管理費、敷/礼/保証/敷引,償却、間取り、専有面積が入っているtableを全て抜き出し
    tables = summary.find_all('table')

    #各建物(table)に対して、売りに出ている部屋(row)を取得
    rows = []
    for i in range(len(tables)):
        rows.append(tables[i].find_all('tr'))

    #各部屋に対して、tableに入っているtext情報を取得し、dataリストに格納
    data = []
    for row in rows:
        for tr in row:
            cols = tr.find_all('td')
            for td in cols:
                text = td.find(text=True)
                data.append(text)

    #dataリストから、階、賃料、管理費、敷/礼/保証/敷引,償却、間取り、専有面積を順番に取り出す
    index = 0
    for item in data:
        if '階' in item:
            floor.append(data[index])
            rent.append(data[index+1])
            admin.append(data[index+2])
            others.append(data[index+3])
            floor_plan.append(data[index+4])
            area.append(data[index+5])
        index +=1
    
    #プログラムを10秒間停止する(スクレイピングマナー)
    time.sleep(10)

少し冗長な感じもしますが、これで、必要な情報を各リストに格納することができました。建物一つにつき、売りに出ている部屋が複数件ある場合があるので、数がズレないように調整しています。

さて、あとはこれをくっつけてデータフレームにするだけです。

#各リストをシリーズ化
name = Series(name)
address = Series(address)
locations0 = Series(locations0)
locations1 = Series(locations1)
locations2 = Series(locations2)
age = Series(age)
height = Series(height)
floor = Series(floor)
rent = Series(rent)
admin = Series(admin)
others = Series(others)
floor_plan = Series(floor_plan)
area = Series(area)

#各シリーズをデータフレーム化
suumo_df = pd.concat([name, address, locations0, locations1, locations2, age, height, floor, rent, admin, others, floor_plan, area], axis=1)

#カラム名
suumo_df.columns=['マンション名','住所','立地1','立地2','立地3','築年数','建物高さ','階','賃料','管理費', '敷/礼/保証/敷引,償却','間取り','専有面積']

#csvファイルとして保存
suumo_df.to_csv('suumo_adachi.csv', sep = '\t',encoding='utf-16')

完成!!

今回は足立区のみでしたが、最初のURLを入れ替えれば他の区にも使えます。

次のブログでは、このデータを分析しやすいように前処理していこうと思います。

www.analyze-world.com