1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Googleスプレッドシート][GAS]引越し候補物件をスマートに整理しよう Sprint1

Last updated at Posted at 2024-07-09

はじめに

続編:

注意書き:

※ 本ページ記載内容には、Suumoに対するスクレイピング処理が含まれます。商用利用は禁止されておりますので、個人利用の範囲で参考にしていただければと思います。

背景

  • 最近引越し予定で家探しをすることになったものの、お気に入り物件をざっと眺められる場所が欲しい
  • スプレッドシートにまとめていきたい
  • 物件探しはSuumoでやっていく予定

課題

課題①: 必要な情報すべてを手で入力すると、めんどくさい

  • URLだけ貼っていても、いちいち覗きにいかなければいけないため、だるい
  • かといって、URLに加えて、家賃・築年数・面積... などなどをSuumoから手で転記するのは、もっとだるい

課題②: 通勤時間は、毎回Google Mapで検索しないと不明であり、めんどくさい

  • 「通勤にどれくらい移動時間がかかるか?」は、家探しで結構重要なパラメータ
  • しかし、物件情報として取得できるのは以下であり、家⇔オフィスの合計通勤時間は物件情報から取得不可能
    • 物件から最寄り駅までの移動時間 (←しかも、ガバガバで信用ならない!!!)
    • 最寄り駅からオフィス最寄り駅までの電車移動時間
  • 重要な情報であるにもかかわらず、毎回Google Mapで検索するのは、めちゃくちゃだるい

目的

Suumo URLを入力すると、物件を比較するために必要な家賃・通勤時間などの情報を、自動で取得・計算するスプレッドシートを作ろう!


進め方

Quick and Dirty!

ちゃんと考えるのはだるいので、とっとと動くものを作る。課題に対しては適宜アップデートをかけてつぶしていく。

実装

実装のイメージ

  1. 入力する値はSuumoのURLのみ
    • Suumoの機能のお気に入り登録をしたSuumo URLをいれる前提
  2. Suumo URLを使って、以下の値をスクレイピングする
    • 物件名
    • 家賃 (管理費・共益費込)
    • 築年数
    • 最寄り駅と最寄り駅からのアクセス
    • 住所
  3. ②で取得した住所とオフィス住所を利用し、Google Maps APIで通勤時間を計算する

使い方イメージ

A1にSuumoのURLが入っているとしたら、以下をスプレッドシートのセルに書けば、家賃が取得できる、といったイメージ

=getRent(A1)

通勤時間に関しては、以下の計算式で計算するイメージ

=getCommuteTime(<物件住所>, <オフィス住所>)

※後ほど、この実装はまずかったことが判明。改善すべきポイントになります。

実装

スプレッドシート > 拡張機能 > App Script でエディタを開き、以下を実装。

コード.gs
function getCommuteTime(src, dest) {
 const target_date = '2024/YY/MM HH:mm:ss' // 想定する通勤日時で到着したい時刻を入力
 const datetime = new Date(target_date); 
 const arrival = new Date(datetime.getTime());

 let finder = Maps.newDirectionFinder()
 .setOrigin(src)
 .setDestination(dest)
 .setLanguage("ja")
 .setArrive(arrival)
 .setMode(Maps.DirectionFinder.Mode.TRANSIT); // 公共交通機関

 let route = finder.getDirections().routes[0];
 let value = route.legs[0].duration.value/60; //minutes

 return value;
}

function checkError(return_value){
  let exceptional_strings = '<'
  if (return_value.includes(exceptional_strings)){
    return_value = 'WARNING: 掲載終了の可能性、URLを参照してください。'
  }
  return return_value
}

function getSuumoInfo(url, start, end) {
 let response = UrlFetchApp.fetch(url);
 let content = response.getContentText("utf-8");
 let info = Parser.data(content).from(start).to(end).iterate();
 
 return info
}

function getName(url) {
 const start = '<h1 class="section_h1-header-title">';
 const end = '</h1>';

 let info = getSuumoInfo(url, start, end);
 let name_tmp = info.pop().trim();

 if (name_tmp.includes('-')){
  name = name_tmp.slice(0, name_tmp.indexOf("-"))
 }else{
  name = name_tmp
 }
 
 name = checkError(name)
 return name
}

function getAge(url) {
 const start = '<div class="property_data-body">';
 const end = '</div>';

 let info = getSuumoInfo(url, start, end);
 let age = info[8].trim();
 
 return age
}

function getArea(url) {
 const start = '<div class="property_data-body">';
 const end = '</div>';

 let info = getSuumoInfo(url, start, end);
 let area_tmp = info[5].trim();
 let area = Number(area_tmp.slice(0, area_tmp.indexOf("m")));

 return area
}


function getRent(url) {
 const start_rent = '<div class="property_view_main-emphasis">';
 const start_management_fee = '<div class="property_data-body">';
 const end = '</div>';

 let info_rent = getSuumoInfo(url, start_rent, end);
 let rent_str = info_rent[0].trim().replace('万円', '');
 let rent = Number(rent_str) * 10000;

 let info_management_fee = getSuumoInfo(url, start_management_fee, end);
 let management_fee_str = info_management_fee[0].trim().replace('', '');
 if (management_fee_str=="-"){ // 管理費・共益費が0の場合
  management_fee_str="0"
 };
 let management_fee = Number(management_fee_str);

 let total_fee = (rent + management_fee)/10000;

 return total_fee
}

function getAddress(url) {
 const start = '<div class="property_view_detail-text">';
 const end = '</div>';

 let info = getSuumoInfo(url, start, end);
 let address = info.pop().trim();

 address = checkError(address)
 return address
}

function getAccessInfo(url) {
 const start = '<div class="property_view_detail-text">';
 const end = '</div>';

 let info = getSuumoInfo(url, start, end);
 let access = info.shift().trim();

 access = checkError(access)
 return access
}

結果

よかったポイント

  • 「これが欲しかったんや!」という良いPoCになった
  • URLを貼るだけでズバズバ情報が入力されるのは、やっぱり気持ちいい!快適!!!
  • 通勤時間は結構重要な情報だった、物件一覧と通勤時間を横並びに見れるのは大変便利。

改善すべきポイント

  1. セル上で実施する計算式として実装しているため、スプレッドシートのリロードや、行の挿入・削除等で毎回計算が走ってしまう (優先度: Urgent)
  2. Suumoでお気に入りしていない状態のURLを入力すると、エラーになる (優先度: Medium)

課題については、しっかり記載していきます。

①毎回計算が走る問題 (Urgent)

セルに直書きしている実装となっているため、以下の状況で毎回計算が走ります。

  • シートを開く、リロードする場合
  • 行の挿入や削除を行った場合

計算が走ることで、以下の問題が発生します。結構クリティカル。

  • Google Maps APIの「短時間にAPI叩きすぎじゃボケ」エラー
  • 毎回計算が走るので、処理が遅い

-> 以下によって解決可能そう

  • スプレッドシート上でトリガーを設定し、トリガーによって計算を実行する
  • URL入力断面の情報をスプレッドシート上の別シートに保持・蓄積し、比較するシートは参照のみを行う

次の記事で対応予定

②お気に入りじゃないURL、エラーになる問題 (Medium)

Suumoの物件ページは、以下のようにお気に入り系のページと非お気に入りのページでURLが異なるそうです。

  • お気に入り: https://suumo.jp/chintai/bc_xxxxxxxxx/
  • 非お気に入り: https://suumo.jp/chintai/jnc_xxxxxxxxxx/

※細かく言えば他にもあるけど、上記2つで大別できる、程度の認識

URLが異なる上、ページ構造も完全に一致するわけではないので、単一のスクレイピングロジックだとエラーになってしまう、という状態。

想定している通常の使い方だと問題ないのですが、「お気に入り登録しないと、物件URLを入力できない」はダサいので、そのうち対応しようかなと。

終わりに

  • JavaScript系の言語触るの初めてなので、手こずる部分もあったけど、ドキュメントとか見れば別に普通に書けた。うれしい。
  • 動くものを作って、「すげえw」って一緒に喜べる体験自体がとても楽しかった。
  • せっかくなので、もう少し便利な仕様にアップデートしたい。

参考情報

通勤時間計算周り

スクレイピング周り

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?