読者です 読者をやめる 読者になる 読者になる

エンジニアリングとお金の話

都内で働くエンジニアの日記です。投資と技術の話題がメインです。

【技術】エンジニアの家探しについて

最近結婚したのを機に引っ越ししようと部屋を探していた。

年明けぐらいから物件を見始めていたが、条件の良い物件はすぐ無くなってしまう。
3月中には引っ越ししたいと考えていた事もあり、だんだんと焦ってきた。

よい物件に住む為には誰よりも早く、また多くの情報を取得する必要がある。その為には武器が必要だと思い以下2つのPGを書てみた。

1つ目 不動産情報取得ツール

だいたいの不動産サイトは

①検索条件を入れる(住みたい場所)
②条件に該当する一覧を表示
③一覧のリンクより詳細情報画面へ遷移

と言う作りになっている。

ただ、自分の場合は住みたい場所はあまりこだわらず2LDK以上の物件の中で一番良い所に住みたいと思っていた。その為、既存のサイトの作りでは非常に比較がしづらい。

そこで、2LDKの条件に該当する物件のみをスクレイピングにて抽出し、スプレッドシートにて比較を行う事とした。なお、抽出元のサイトは不動産ジャパンという不動産総合情報サイトから行った。

プログラムは以下の通り。スクレイピングにはseleniumを使用

#-*- coding:utf-8 -*-
from selenium import webdriver
from BeautifulSoup import BeautifulSoup
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By
import re
import csv
import traceback
import time

url="http://www.fudousan.or.jp/system/?act=f&type=31&pref=13&stype=l"

driver=webdriver.PhantomJS()
driver.implicitly_wait(10)
driver.get(url)


#エリア検索 23区
for line in range(1,24):
    driver.find_element_by_xpath('//*[@id="131' + str(line).zfill(2) + '"]').click()

#物件種別
driver.find_element_by_xpath('//*[@id="tm"]').click()

#金額(下限)
driver.find_element_by_xpath('//*[@id="cont"]/form/table[2]/tbody/tr[3]/td/ul[1]/li[1]/select/option[20]').click()

#金額(上限 18以下)
driver.find_element_by_xpath('//*[@id="cont"]/form/table[2]/tbody/tr[3]/td/ul[1]/li[2]/select/option[34]').click()

#年数(15年未満)
driver.find_element_by_xpath('//*[@id="yc4"]').click() 

#最寄駅
driver.find_element_by_xpath('//*[@id="th3"]').click() 

#間取り
driver.find_element_by_xpath('//*[@id="md3"]').click()
driver.find_element_by_xpath('//*[@id="md4"]').click()

#部屋の位置(二階以上)
driver.find_element_by_xpath('//*[@id="ts"]').click()

#設備(モニター付きインタフォン)
#driver.find_element_by_xpath('//*[@id="mi"]').click()

#設備(オートロック)
#driver.find_element_by_xpath('//*[@id="al"]').click()

#サブミット
driver.find_element_by_xpath('//*[@id="cont"]/form/table[2]/tbody/tr[13]/td/p/a/img').click()

#ページ件数変更(100件)
driver.find_element_by_xpath('//*[@id="cont"]/table[3]/tbody/tr[2]/td/table/tbody/tr[2]/td[1]/p/select/option[3]').click()

#新着順に表示変更
driver.find_element_by_xpath('//*[@id="cont"]/table[3]/tbody/tr[2]/td/table/tbody/tr[3]/td/p/a[1]').click()

#ページ読み込み
bs=BeautifulSoup(driver.page_source)

#最終ページ数
maxPage=bs.findAll("a",attrs={"href":re.compile("javascript:page.*")})[-2].text

#不動産件数
bukkenCount=re.compile(u"(\d+)件中.*").search(bs.findAll("p",text=re.compile(u".*件中.*"))[1])

with open("./data.txt","w") as fp:

    header=["物件番号","情報登録日","次回更新予定日","物件名","物件URL","住所","最寄駅","家賃","管理費","部屋タイプ","広さ","間取り","築年数","階数","向き","設備","構造","現状","入居時期","契約期間","敷金/礼金","更新料","備考1","備考2","特記"]
    writer=csv.writer(fp,lineterminator='\n')
    writer.writerow(header)

    try:
        for page in range(1,int(maxPage)+2):
            #各ページのURLを取得
            for line in range(4,104):
                csvArray=[]
                driver.find_element_by_xpath('//*[@id="cont"]/table[' + str(line) + ']/tbody/tr[1]/th/h5/a').click()
                WebDriverWait(driver,10).until(expected_conditions.text_to_be_present_in_element((By.XPATH,'//*[@id="cont"]/h4'),u'物件詳細'))
                time.sleep(1)
                bs=BeautifulSoup(driver.page_source)
        
                #物件番号
                csvArray.append(bs.findAll("th",attrs={"class":"midashi"})[1].p.text.encode("utf-8").replace("物件番号:",""))

                #情報取得
                data=[x for x in bs.findAll("td")]

                #情報登録日
                csvArray.append(data[34].p.text.encode("utf-8"))
        
                #次回更新予定日
                csvArray.append(data[35].p.text.encode("utf-8"))
        
                #物件名(該当するデータが1件のみである為findにて取得)
                if bs.find("th",attrs={"class":"midashi_name"}) is not None:
                    csvArray.append(bs.find("th",attrs={"class":"midashi_name"}).p.text.encode("utf-8") if bs.find("th",attrs={"class":"midashi_name"}).p.text.encode("utf-8") != "" else u"不明".encode("utf-8"))
                else:
                    csvArray.append("不明")
        
                #物件URL
                url1=driver.current_url.encode("utf-8").split("&pref=13")[0]
                url2=driver.current_url.encode("utf-8").split("v=on&s=n")[1]
                csvArray.append(url1 + url2)
        
                #住所
                csvArray.append(data[1].p.text.encode("utf-8"))
        
                #最寄駅
                csvArray.append(data[2].p.text.encode("utf-8"))
        
                #家賃
                csvArray.append(data[3].p.text.encode("utf-8"))
        
                #管理費 
                csvArray.append(data[4].p.text.encode("utf-8"))

                #部屋タイプ
                csvArray.append(data[9].p.text.encode("utf-8"))
 
                 #広さ
                csvArray.append(data[11].p.text.encode("utf-8"))

                 #間取り
                csvArray.append(data[10].p.text.encode("utf-8"))
        
                #築年数
                csvArray.append(data[12].p.text.encode("utf-8"))

                 #階数
                csvArray.append(data[15].p.text.encode("utf-8"))
        
                #向き
                csvArray.append(data[16].p.text.encode("utf-8"))

                 #設備
                csvArray.append(data[19].p.text.encode("utf-8"))
       
                #構造
                csvArray.append(data[14].p.text.encode("utf-8"))
        
                #現状
                csvArray.append(data[21].p.text.encode("utf-8"))
        
                #入居時期
                csvArray.append(data[22].p.text.encode("utf-8"))
        
                #契約期間
                csvArray.append(data[23].p.text.encode("utf-8"))

                 #敷金/礼金
                csvArray.append(data[5].p.text.encode("utf-8"))
        
                #更新料
                csvArray.append(data[26].p.text.encode("utf-8"))
        
                #備考1
                csvArray.append(data[30].p.text.encode("utf-8"))
        
                #備考2
                csvArray.append(data[31].p.text.encode("utf-8"))
        
                #特記
                csvArray.append(data[32].p.text.encode("utf-8"))
        
                writer=csv.writer(fp,lineterminator='\n')
                writer.writerow(csvArray)
        
                driver.back()
                time.sleep(1)
            driver.find_element_by_xpath('//*[@id="cont"]/table[3]/tbody/tr[2]/td/table/tbody/tr[1]/td/p/a[' + str(page) + ']').click()
            time.sleep(1)
    except:
        print traceback.format_exc()
        pass

2つ目 不動産登録通知ツール

この季節の不動産はすごい勢いで入れ替わっており、新しい物件情報が雨後の竹の子のごとく現れては消えて行く感じとなっている。

その為、頻繁に不動産サイトを確認する必要があるが正直しんどい。そこで、不動産情報が新規追加された際は自分の携帯にメールが届くプログラムを作成した。なお、起動は1時間毎にcronにて実行する。

新規物件抽出

#-*- coding:utf-8 -*-
from selenium import webdriver
from BeautifulSoup import BeautifulSoup
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By
import re
import csv
import traceback
import time
import pymongo
import mails


url="http://www.fudousan.or.jp/system/?act=f&type=31&pref=13&stype=l"

driver=webdriver.PhantomJS()
driver.implicitly_wait(10)
driver.get(url)

for line in range(1,24):
    driver.find_element_by_xpath('//*[@id="131' + str(line).zfill(2) + '"]').click()

#物件種別
driver.find_element_by_xpath('//*[@id="tm"]').click()

#金額(下限)
driver.find_element_by_xpath('//*[@id="cont"]/form/table[2]/tbody/tr[3]/td/ul[1]/li[1]/select/option[20]').click()

#金額(上限 18以下)
driver.find_element_by_xpath('//*[@id="cont"]/form/table[2]/tbody/tr[3]/td/ul[1]/li[2]/select/option[34]').click()

#年数(15年未満)
driver.find_element_by_xpath('//*[@id="yc4"]').click()

#最寄駅
driver.find_element_by_xpath('//*[@id="th3"]').click()

#間取り
driver.find_element_by_xpath('//*[@id="md3"]').click()
driver.find_element_by_xpath('//*[@id="md4"]').click()

#部屋の位置(二階以上)
driver.find_element_by_xpath('//*[@id="ts"]').click()

#設備(モニター付きインタフォン)
#driver.find_element_by_xpath('//*[@id="mi"]').click()

#設備(オートロック)
driver.find_element_by_xpath('//*[@id="al"]').click()

#サブミット
driver.find_element_by_xpath('//*[@id="cont"]/form/table[2]/tbody/tr[13]/td/p/a/img').click()

#ページ件数変更(100件)
driver.find_element_by_xpath('//*[@id="cont"]/table[3]/tbody/tr[2]/td/table/tbody/tr[2]/td[1]/p/select/option[3]').click()

#新着順に表示変更
driver.find_element_by_xpath('//*[@id="cont"]/table[3]/tbody/tr[2]/td/table/tbody/tr[3]/td/p/a[1]').click()

#ページ読み込み
bs=BeautifulSoup(driver.page_source)

#最終ページ数
maxPage=bs.findAll("a",attrs={"href":re.compile("javascript:page.*")})[-2].text

#不動産件数
#bukkenCountNew=int(re.compile(u"(\d+)件中.*").search(bs.findAll("p",text=re.compile(u".*件中.*"))[1]).group(1))

#mongoDb
client=pymongo.MongoClient("localhost:27017")
db=client["fudosandb"]
collection=db["bukken"]

dataArray=[]
for page in range(1,int(maxPage)+1):
    #各ページのURLを取得
    for line in range(4,104):
        driver.find_element_by_xpath('//*[@id="cont"]/table[' + str(line) + ']/tbody/tr[1]/th/h5/a').click()
        WebDriverWait(driver,10).until(expected_conditions.text_to_be_present_in_element((By.XPATH,'//*[@id="cont"]/h4'),u'物件詳細'))
        time.sleep(1)
        bs=BeautifulSoup(driver.page_source)
    
        #物件番号
        #print bs.findAll("th",attrs={"class":"midashi"})[1].p.text.encode("utf-8")
        bukkenNo=bs.findAll("th",attrs={"class":"midashi"})[1].p.text.encode("utf-8").replace("物件番号:","")

        #物件番号がDBに存在しない場合は新規物件としてメール(物件名、物件URL)を通知
        if collection.count({"bukkenNo":bukkenNo}) == 0:

            bukkenName=""
            bukkenUrl=""

            #物件名(該当するデータが1件のみである為findにて取得)
            if bs.find("th",attrs={"class":"midashi_name"}) is not None:
                #print bs.find("th",attrs={"class":"midashi_name"}).p.text.encode("utf-8")
                bukkenName=bs.find("th",attrs={"class":"midashi_name"}).p.text.encode("utf-8") if bs.find("th",attrs={"class":"midashi_name"}).p.text.encode("utf-8") != "" else u"不明".encode("utf-8")
            else:
                bukkenName="不明"
            
            #物件URL
            #print driver.current_url
            url1=driver.current_url.encode("utf-8").split("&pref=13")[0]
            url2=driver.current_url.encode("utf-8").split("v=on&s=n")[1]
            bukkenUrl=url1 + url2
            dataArray.append((bukkenName,bukkenUrl))
            collection.insert({"bukkenNo":bukkenNo})

        driver.back()
        time.sleep(1)
    driver.find_element_by_xpath('//*[@id="cont"]/table[3]/tbody/tr[2]/td/table/tbody/tr[1]/td/p/a[' + str(page) + ']').click()

#メール送信
if len(dataArray) > 0:
    mailArray=[]
    for name,url in dataArray:
        mailArray.append(name + "\n" + url + "\n\n")
    mails.mailsend(mailArray)

メール送信

#-*- coding:utf-8 -*-

import smtplib
from email.MIMEText import MIMEText
from email.Header import Header
from email.Utils import formatdate

def mailsend(data):

    from_address = "sample@test.test"
    to_address   = ["送信先アドレス1","送信先アドレス2"]

    charset = "ISO-2022-JP"
    subject = u"新規物件がありました"
    text    = "".join(data).decode("utf-8","ignore")

    msg = MIMEText(text.encode(charset),"plain",charset)
    msg["Subject"] = Header(subject,charset)
    msg["From"]    = from_address
    msg["To"]      = ",".join(to_address)
    msg["Date"]    = formatdate(localtime=True)

    s = smtplib.SMTP('smtp.gmail.com', 587)
    s.ehlo()
    s.starttls()
    s.ehlo()
    s.login("ログインID","パスワード")
    s.sendmail(from_address, to_address, msg.as_string())
    s.close()

で、最終的にどうなったのか?

以前応募していた、UR賃貸より補欠当選だったヌーベル赤羽が繰り上げ当選しましたとの連絡が昨日あった。
意外な形で家探し終了である。

後1週間早く連絡があればプログラムを書く事もなかったんだけどな。。
まぁ家が決まって良かった。

広告を非表示にする