akatak blog

プログラム初心者の50代ビジネスマンがセカンドキャリアを目指して働きながらPythonを中心に独学していましたが、昨年IT系企業に転職。新規事業開発の仕事をすることになりました。自らの覚え書きや成果物、感じたことなどを脈絡もなく書き連ねるブログです。

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

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

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

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

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

さて、まずは、東京23区の賃貸物件について、「データで見る世界」さんのブログを参考にして、Pythonを使って取得してみることにしましょう。「データで見る世界」さん同様に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を開いて、エラーがないことを確認しましょう。たまに、通信環境等によってか、エラーが発生することがありますが、少ない時は無視します。エラーが多い時だけ、時間を空けて再度トライしてみます。これでうまくいくことが多いので、エラーが発生した場合には、時間をおいてみましょう。

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