Note: see the link below for the English version of this article.

https://duongnt.com/urchintai-api

Nếu từng đi thuê nhà ở Nhật thì có lẽ các bạn đã nghe đến cái tên UR Chintai (UR賃貸住宅). Khi thuê nhà qua tổ chức này, người dùng được hưởng nhiều ưu đãi như không cần tiền lễ hay tiền bảo lãnh, còn tiền đặt cọc cũng thường được hoàn trả gần hết. Gần đây, tôi tình cờ biết được rằng UR Chintai cung cấp một API cho phép ta truy xuất thông tin một cách tùy ý. Tuy nhiên, không có nhiều thông tin về API này trên Internet. Nơi duy nhất có nhắc đến API này là ở trong một tài liệu trên trang chủ của UR Chintai. Vì thế, tôi thử tìm hiểu cách sử dụng API đó và đã thu được những kết quả đáng kể.

Hôm nay, chúng ta sẽ dùng API để kiểm tra xem một tòa nhà hoặc một phòng trong hệ thống UR Chintai có còn trống hay không. Sau đó ta sẽ dùng thử urchintai-client, đây là một package tôi viết bằng Python để giúp việc truy xuất dữ liệu từ API trở nên dễ dàng hơn. Các bạn có thể xem mã nguồn của urchintai-client tại đây.

https://github.com/duongntbk/urchintai-client

Trong bài, ta sẽ dùng hai URL dưới đây làm ví dụ. Chúng được chọn một cách ngẫu nhiên và không có gì đặc biệt so với tất cả các tòa nhà hay phòng khác. Tùy vào thời điểm bạn đọc bài này, tòa nhà hay phòng này có thể còn trống hoặc đã có người thuê, nhưng điều đó không ảnh hưởng nhiều tới nội dung bài.

https://www.ur-net.go.jp/chintai/kanto/kanagawa/40_2600.html # <- Đây là URL của một tòa nhà trong hệ thống URL Chintai
https://www.ur-net.go.jp/chintai/kanto/kanagawa/40_2600_room.html?JKSS=000000410 # <- Đây là URL của một phòng trong hệ thống UR Chintai

Lấy dữ liệu về một tòa nhà

Tìm hiểu code

Đoạn code lấy thông tin về một tòa nhà nằm ở link này. Nếu các bạn mở nó bằng Chrome thì font tiếng Nhật sẽ không hiển thị đúng. Vì thế ta nên dùng Firefox và chỉnh encoding của trang thành Unicode cho dễ đọc.

Vì ta muốn biết một tòa nhà có phòng trống hay không nên ta sẽ xem đoạn code thực hiện tải danh sách phòng trống. Đoạn đó bắt đầu từ dòng 589.

return ur.api.ajax('bukken/detail/detail_bukken_room/', ajaxData)
    .done(function (data, status, jqXHR) {
        // nhiều code
    }).fail(function() {
        // nhiều code
    });

Ta có thể đoán rằng cần gửi request tới endpoint bukken/detail/detail_bukken_room/. Sau đó ta xem hàm ur.api.ajax để tìm root của API. Hàm đó được định nghĩa ở đây. Từ file này ta có thể thấy root của API là https://chintai.sumai.ur-net.go.jp/chintai/api/. Do ur.api.ajax được gọi với giá trị HTTP method bỏ trống nên hàm POST sẽ được sử dụng. Tiếp theo, ta cần xem giá trị của ajaxData để biết endpoint này cần những dữ liệu đầu vào nào.

var ajaxData = ur.api.bukken_detail.setPostData();

Hàm setPostData được định nghĩa ở dòng 1124. Ta có thể thấy rằng ajaxData chứa rất nhiều dữ liệu, nhưng thực ra chỉ có sáu trường sau là bắt buộc: shisya, danchi, shikibetu, orderByField, orderBySort and pageIndex (ai tinh mắt sẽ thấy giá trị pageIndex được gán thừa một lần). Ta có thể gán giá trị 0 cho orderByField, orderBySort and pageIndex khi tìm phòng trống, vì thế chỉ còn ba trường mà ta cần quan tâm.

Đoạn code gán giá trị cho ba trường đó như sau.

ajaxData.push({name: 'shisya', value: ur.api.bukken_detail.param.shisya});
ajaxData.push({name: 'danchi', value: ur.api.bukken_detail.param.danchi});
ajaxData.push({ name: 'shikibetu', value: ur.api.bukken_detail.param.shikibetu });

Các giá trị của ur.api.bukken_detail.param.XXX thì lại được gán trong hàm ur.api.bukken_detail.initSearch, ở dòng 260 ~ 264.

ur.api.bukken_detail.initSearch = function (shisya, danchi, shikibetu, life_designhouse, new_bukken_room) {
    //...
    // 支社
    ur.api.bukken_detail.param.shisya = shisya;
    // 団地
    ur.api.bukken_detail.param.danchi = danchi;
    // 識別
    ur.api.bukken_detail.param.shikibetu = shikibetu;
    //...
}

Hàm ur.api.bukken_detail.initSearch không được gọi trong file JavaScript. Nhưng nếu ta dùng curl hay các tool tương tự để load trang về mà không bật JavaScript thì ta sẽ thấy đoạn code sau.

<script type="text/JavaScript">
    $(function () {
        ur.cookie.setupBookmarkBukken();
        ur.cookie.saveHistoryBukken('40_2600');
        ur.api.bukken_detail.initSearch('40', '260', '0');
        ur.api.bukken_bukken.initSearch('40', '260', '0');
        $("a.td_underline_before:empty").parents(".cassettes_contact_box").remove();
        $("span.item_tel_number:empty").parents("span.button_tel_purple").remove();
    });
</script>

Nhớ rằng URL ví dụ mà ta đang dùng là https://www.ur-net.go.jp/chintai/kanto/kanagawa/40_2600.html, từ đó ta có thể hiểu được các con số 40, 260 and 0 ở đâu ra. Vậy ta có thể kết luận rằng URL của một tòa nhà có dạng https://www.ur-net.go.jp/chintai/<tên khu vực>/<tên tỉnh>/AA_BBBC.html.

  • AA: shisya
  • BBB: danchi
  • C: shikibetu

Gọi thử API

Áp dụng những điều đã biết ở trên, ta có thể gọi API để lấy danh sách phòng trống như sau.

Method: POST
Endpoint: https://chintai.sumai.ur-net.go.jp/chintai/api/bukken/detail/detail_bukken_room/
Form data: {
    'shisya': '40',
    'danchi': '260'
    'shikibetu': '0',
    'orderByField': '0',
    'orderBySort': '0',
    'pageIndex': '0',
}

Giá trị trả về vào thời điểm viết bài này là.

[
  {
    "pageIndex": "0",
    "rowMax": "5",
    "rowMaxSp": "3",
    "rowMaxNext": "10",
    "pageMax": "5",
    "allCount": "4",
    "block": "kanto",
    "tdfk": "kanagawa",
    "shisya": "40",
    "danchi": "260",
    "shikibetu": "0",
    "floorAll": "15階",
    "roomDetailLink": "/chintai/kanto/kanagawa/40_2600_room.html?JKSS=000000404",
    "roomDetailLinkSp": "/chintai/sp/kanto/kanagawa/40_2600_room.html?JKSS=000000404",
    "system": [
      {
        "制度_IMG": "btn_kinkyo.png",
        "制度名": "近居割",
        "制度HTML": "kinkyo"
      }
    ],
    "parking": null,
    "design": [
      {
        "デザイン_HTML": "renovation",
        "デザイン_IMG": "logo_renovation.png",
        "デザイン名": "リノベーション住宅"
      },
      {
        "デザイン_HTML": "premium",
        "デザイン_IMG": "logo_premium.png",
        "デザイン名": "プレミアムなお部屋"
      }
    ],
    "featureParam": [],
    "traffic": null,
    "place": null,
    "kanris": null,
    "kouzou": null,
    "soukosu": null,
    "id": "000000404",
    "year": null,
    "name": "404号室",
    "shikikin": "2か月",
    "requirement": "ナシ",
    "madori": "https://chintai.sumai.ur-net.go.jp/chintai/img_madori/40/40_260/40_260_0-00-0000_B5_RA_01_00011.gif",
    "rent": "101,800円",
    "rent_normal": "",
    "rent_normal_css": " dn",
    "commonfee": "4,100円",
    "commonfee_sp": null,
    "status": null,
    "type": "2DK",
    "floorspace": "45㎡",
    "floor": "4階",
    "urlDetail": null,
    "urlDetail_sp": null,
    "feature": null
  },
  {
    <OMITTED>
  },
  {
    <OMITTED>
  },
  {
    <OMITTED>
  }
]

Response từ API chứa nhiều thông tin về các phòng trống trong tòa nhà. Ta thấy có một trường gọi là allCount, trường này chứa tổng số phòng trống trong tòa nhà (vào thời điểm viết bài, trường này có giá trị là 4). Ngoài ra ta cũng có thể lấy được giá thuê phòng, số phòng, diện tích,… Tiếp theo ta sẽ thử gọi endpoint này với URL của một tòa nhà đã hết phòng.

https://www.ur-net.go.jp/chintai/kanto/kanagawa/40_3600.html

Kết quả như sau.

null

Có thể thấy rằng cách kiểm tra xem một tòa nhà còn phòng trống không là rất đơn giản. Ta chỉ cần gửi request tới bukken/detail/detail_bukken_room/ và xem giá trị trả về có là null hay không.

Lấy dữ liệu về một phòng

Tìm hiểu code

Đoạn code JavaScript để tải thông tin về từng căn phòng nằm ở đây. Đoạn ta cần quan tâm bắt đầu từ dòng 170.

ur.api.room_detail.getDataRoom = function () {
    var ajaxData = [
        {name: 'id', value: ur.api.room_detail.param.id},
        {name: 'shisya', value: ur.api.room_detail.param.shisya},
        {name: 'danchi', value: ur.api.room_detail.param.danchi},
        {name: 'shikibetu', value: ur.api.room_detail.param.shikibetu },
        {name: 'sp', value: (conf.isMobile()) ? '1' : '' }
    ];
    ur.api.ajax('bukken/detail/detail_room/', ajaxData)
        .done(function (data, status, jqXHR) {
            // nhiều code
        }).fail(function () {
            // nhiều code
        });
};

Ta có thể bỏ qua tham số sp vì tham số này chỉ để biết người dùng đang sử dụng thiết bị di động hay không. Dễ thấy rằng các giá trị ur.api.room_detail.param.XXX được khởi tạo ở dòng 144 ~ 159, bên trong hàm ur.api.room_detail.initSearch. Ta đã biết rằng shisya, danchishikibetu được lấy từ URL. Còn từ dòng 154, ta đoán được rằng id chính là tham số JKSS từ URL.

var id = ur.func.getParam('JKSS');

Gọi thử API

Vì URL của phòng ta dùng làm ví dụ là https://www.ur-net.go.jp/chintai/kanto/kanagawa/40_2600_room.html?JKSS=000000410 nên ta gọi API như sau.

Method: POST
Endpoint: https://chintai.sumai.ur-net.go.jp/chintai/api/bukken/detail/detail_room/
Form data: {
    'shisya': '40',
    'danchi': '260'
    'shikibetu': '0',
    'id': '000000410'
}

Giá trị trả về vào thời điểm viết bài này là.

[
  {
    "roomCatch1": "",
    "roomCatch2": "",
    "roomRead1": "",
    "roomRead2": "",
    "roomNm": "410号室",
    "rent_sp": "<span class=\"item_price\">135,600円</span><span class=\"item_commonfee\">(4,100円)</span>",
    "madoriYuka": "2LDK /63㎡",
    "madoriYuka_sp": "2LDK /63㎡",
    "floor_sp": "4階 /15階",
    "netLink": "https://sumai.ur-net.go.jp/chintai/s/otoiawase/juko.html?in=1040260000000410",
    "kariLink": "",
    "img": [
        <LINK TO ROOM's IMAGES>
    ],
    "movie": null,
    "otherRoom": [],
    "system": [
      {
        "制度_IMG": "btn_kinkyo.png",
        "制度名": "近居割",
        "制度HTML": "kinkyo"
      }
    ],
    "design": [
      {
        "デザイン_HTML": "renovation",
        "デザイン_IMG": "logo_renovation.png",
        "デザイン名": "リノベーション住宅"
      },
      {
        "デザイン_HTML": "premium",
        "デザイン_IMG": "logo_premium.png",
        "デザイン名": "プレミアムなお部屋"
      }
    ],
    "bookmark_key": "40_2600000000410",
    "parking": "<span>機械 空き台数:7台 料金:19,030円~21,120円<br></span><span class=\"item_text_parking-caution\">※ご契約時、駐車場敷金2か月が必要です。</span>",
    "facility": "エレベーター、エアコン、ドロップインコンロ、オートドアロック、防犯カメラ、プレイロット、BS、VDSL、アルコーブ、バス・トイレ別、洗面所独立、追い焚き、洗濯機置場(室内)、玄関収納、バルコニー",
    "biko": "<li>・2020年12月リノベーション済み(キッチン交換、洗面化粧台交換、床改修)</li>",
    "bikoUrLight": "",
    "featureParam": [],
    "mihoshu": "",
    "id": "000000410",
    "year": "32",
    "name": null,
    "shikikin": "2か月",
    "requirement": "ナシ",
    "madori": null,
    "rent": "<p class=\"price\">135,600円<span class=\"item_commonfee\">(4,100円)</span></p>",
    "rent_normal": null,
    "rent_normal_css": " dn",
    "commonfee": null,
    "commonfee_sp": null,
    "status": null,
    "type": null,
    "floorspace": null,
    "floor": "4階 /15階",
    "urlDetail": null,
    "urlDetail_sp": null,
    "feature": "最寄りの小中学校が徒歩10分以内、敷地内に公園あり、最寄りの公園が徒歩5分以内"
  }
]

Nếu ta gọi endpoint đó với thông tin của một phòng đã có người thuê thì sao?

https://www.ur-net.go.jp/chintai/kanto/kanagawa/40_2600_room.html?JKSS=000000411

Có lẽ các bạn cũng đã đoán được kết quả.

null

Cách xem một phòng còn trống hay không cũng thật đơn giản. Ta chỉ cần gửi request tới bukken/detail/detail_room/ và xem giá trị trả về có là null hay không.

Lưu ý về giá trị của room ID

Trong phần trước, ta đã biết rằng ID của phòng số 410 là 000000410. Vì phần lớn các tòa nhà trong hệ thống UR Chintai có nhiều hơn 9 tầng nhưng không cái nào có hơn 99 tầng nên ta có thể đoán được rằng 4 số cuối trong room ID là số phòng. Thế còn 6 số đầu thì sao?

Ta xét tòa nhà trong URL này. Nó có 3 tòa con đánh số lần lượt là 1, 2 and 3, và ID của phòng 204 trong tòa số 2 là 000020204. Có lẽ không tòa nhà nào trong hệ thống có tới hơn 99 tòa con, vì thế có thể đoán rằng room ID có dạng AAABBCCCC.

  • AAA: tôi chưa tìm ra giá trị của phần này, nhưng trong tất cả các phòng mà tôi đã thấy, giá trị này đều là 000.
  • BB: số của tòa con trong một tòa nhà lớn. Chú ý là nếu tòa nhà chỉ có 1 tòa thì giá trị này sẽ là 00 chứ không phải 01.
  • CCCC: số phòng.

Một số endpoint còn lại

Method: POST
Endpoint: https://chintai.sumai.ur-net.go.jp/chintai/api/bukken/detail/detail_bukken_bukken/
Form data: {
    'shisya': 'AA',
    'danchi': 'BBB'
    'shikibetu': 'C'
}

Endpoint này trả về thông tin tổng quan của tòa nhà, ví dụ như.

  • Số chỗ đậu xe và giá thuê hàng tháng.
  • Các cơ sở công cộng quanh đó như nhà trẻ, thư viện, công viên.
  • Trang thiết bị trong tòa nhà: thang máy, truyền hình cáp, khóa tự động,…
Method: POST
Endpoint: https://chintai.sumai.ur-net.go.jp/chintai/api/bukken/detail/detail_bukken/
Form data: {
    'shisya': 'AA',
    'danchi': 'BBB'
    'shikibetu': 'C'
}

Endpoint này trả về các đoạn video và ảnh chụp của tòa nhà cũng như khu vực xung quanh. Mỗi ảnh có 2 bản, một bản thường và một bản thumbnail.

Method: POST
Endpoint: https://chintai.sumai.ur-net.go.jp/chintai/api/bukken/detail/detail_bukken_design/
Form data: {
    'bukkenid': 'AA_BBB' # For our example, the bukkenid is 40_260. Don't know why this one is different from the rest
}

Endpoint này trả về các thông tin sau của tòa nhà.

  • Địa chỉ.
  • Tổng số phòng.
  • Ga tàu gần nhất.
  • Năm UR Chintai bắt đầu quản lý tòa nhà này.
  • Chất liệu xây dựng (bê tông cốt thép, gỗ,…)
Method: POST
Endpoint: https://chintai.sumai.ur-net.go.jp/chintai/api/bukken/detail/detail_bukken_tenpo/
Form data: {
    'shisya': 'AA',
    'danchi': 'BBB'
    'shikibetu': 'C'
}

Endpoint này trả về thông tin về văn phòng quản lý tòa nhà. Nó có các thông tin như số điện thoại, địa chỉ văn phòng, giờ mở cửa, ngày nghỉ,… Những thông tin này sẽ có ích khi ta muốn liên hệ để đăng ký thuê phòng.

Method: POST
Endpoint: https://chintai.sumai.ur-net.go.jp/chintai/api/bukken/detail/bukken_main/
Form data: {
    'shisya': 'AA',
    'danchi': 'BBB'
    'shikibetu': 'C'
}

Endpoint này là bản đơn giản của detail_bukken_bukken. Cả 2 endpoint đều trả về thông tin giống nhau, nhưng bukken/search/bukken_main chỉ trả thông tin về các phòng còn trống.

Giới thiệu urchintai-client

Thật tốn công nếu mỗi lần gọi API của UR Chintai ta lại phải đi tra xem endpoint cần gọi là gì và phải gọi với tham số nào. Để việc gọi API trở nên đơn giản hơn, tôi đã viết một package với tên gọi urchintai-client bằng Python. Các bạn có thể tham khảo cách sử dụng chi tiết của package này trên GitHub.

https://github.com/duongntbk/urchintai-client

Trong bài này ta sẽ dùng thử urchintai-client để kiểm tra xem trong tòa nhà ta quan tâm còn phòng nào trống không. Sau khi cài urchintai-client, ta chạy đoạn code sau đây bằng Python.

import asyncio

from urchintai_client.session_manager import SessionManager
from urchintai_client.request_sender import RequestSender
from urchintai_client.ur_client import UrClient

sender = RequestSender(SessionManager.GetSession())
client = UrClient(sender)

url = 'https://www.ur-net.go.jp/chintai/kanto/kanagawa/40_2600.html'
loop = asyncio.get_event_loop()
is_vacant = loop.run_until_complete(client.is_property_vacant(url))
print(f'Property has vacant room(s): {is_vacant}')

loop.run_until_complete(SessionManager.CloseSession()) # Don't forgot to close the session

Tại thời điểm viết bài này, đoạn code trên trả về kết quả như sau.

Property has vacant room(s): True

Kết thúc

Sau khi đã tìm hiểu về API của UR Chintai, ta có thể dễ dàng bổ sung thêm chức năng cho urchintai-client. Bằng cách kết hợp nhiều request tới API, ta có thể lấy về hầu như bất kỳ thông tin nào mình muốn từ hệ thống của UR Chintai. Và việc viết một con bot để tự động quét và gửi thông báo khi phòng mà ta quan tâm trở thành trống cũng không phải là khó khăn.

A software developer from Vietnam and is currently living in Japan.

One Thought on “Dùng API để truy xuất dữ liệu của UR Chintai”

Leave a Reply