rsshub
Version:
Make RSS Great Again!
173 lines (172 loc) • 6.97 kB
JavaScript
import { t as rofetch } from "./ofetch-U0CdpE2g.mjs";
import { t as parseDate } from "./parse-date-3kcy2Eau.mjs";
import { t as timezone } from "./timezone-UiMFOuvL.mjs";
import { d as parseWard, f as parseYmd, h as tsuboUnit, i as parseArea, m as summarize, n as clean, r as normalizeFloor, s as parseJpy, u as parseWalkMin } from "./utils--J7EU6Hb.mjs";
import { load } from "cheerio";
//#region lib/routes/inshokuten/bukken.ts
const HOST = "https://www.inshokuten.com";
/** Region is the first path segment; the 首都圏 sub-areas are an extra `local-*` segment after `list/`. */
const AREAS = [
{
slug: "kanto",
label: "首都圏",
path: "kanto/bukkens/list"
},
{
slug: "23ward",
label: "東京23区",
path: "kanto/bukkens/list/local-23ward"
},
{
slug: "23ward_out",
label: "東京都下",
path: "kanto/bukkens/list/local-23ward_out"
},
{
slug: "yokohama_kawasaki",
label: "神奈川",
path: "kanto/bukkens/list/local-yokohama_kawasaki"
},
{
slug: "chiba",
label: "千葉",
path: "kanto/bukkens/list/local-chiba"
},
{
slug: "saitama",
label: "埼玉",
path: "kanto/bukkens/list/local-saitama"
},
{
slug: "kansai",
label: "関西",
path: "kansai/bukkens/list"
},
{
slug: "tokai",
label: "東海",
path: "tokai/bukkens/list"
},
{
slug: "kyushu",
label: "九州",
path: "kyushu/bukkens/list"
}
];
/**
* List page cards (`a.bukkenItem`, 20 per page, server-rendered):
* title .bukkenItem__title, id input.js-bukkenKeyId, rows table.bukkenItem__detailTable th → td:
* 賃料/坪単価 '60万円/28,749円', 階数/面積 '地上2階/20.87坪(69.0m2)', 最寄り駅 '東京メトロ南北線 六本木一丁目 徒歩 3分',
* 所在地 '港区六本木4', 前テナント/希望譲渡額 'コンカフェ/0万円' | '喫茶店/相談' | '飲食店/',
* 現況 .bukkenItem__inukiIcon--inuki | --skeleton, 出店可能業態 .bukkenItem__openableBusinessTypeCategory,
* 登録日 '登録日:2026-09-13', tags .bukkenItem__newIcon / .bukkenItem__restaurantNotOpenableIcon
* 保証金 / 礼金 are members-only on the detail page, so no detail page is fetched.
*/
const parseList = (html) => {
const $ = load(html);
return $("a.bukkenItem").toArray().map((el) => {
const $el = $(el);
const href = $el.attr("href");
const title = clean($el.find(".bukkenItem__title").text());
const id = $el.find("input.js-bukkenKeyId").attr("value") ?? href?.match(/\/bukkens\/(\d+)/)?.[1];
if (!href || !title || !id) return null;
const cell = (label) => clean($el.find(`th:contains("${label}")`).first().next("td").text());
const raw = {
rent: cell("賃料/坪単価"),
floor_area: cell("階数/面積"),
station: cell("最寄り駅"),
address: cell("所在地"),
transfer: cell("希望譲渡額"),
business_types: clean($el.find(".bukkenItem__openableBusinessTypeCategory").toArray().map((t) => clean($(t).text())).filter(Boolean).join(", ")),
listed_at: clean($el.find("div:contains(\"登録日:\")").last().text())?.replace(/^登録日[::]\s*/, "") ?? null,
comment: clean($el.find(".bukkenItem__shortComment").text())
};
const [rentText, tsuboUnitText] = (raw.rent ?? "").split("/", 2);
const [floorText, areaText] = (raw.floor_area ?? "").split("/", 2);
const [prevBusiness, fixturesText] = (raw.transfer ?? "").split("/", 2);
const stationParts = raw.station?.split(" ") ?? [];
const walkIndex = stationParts.findIndex((p) => p.startsWith("徒歩"));
const notOpenable = $el.find(".bukkenItem__restaurantNotOpenableIcon").length > 0;
const tags = [$el.find(".bukkenItem__newIcon").length > 0 ? "NEW" : null, notOpenable ? "飲食店不可" : null].filter((t) => t !== null);
const rentJpy = parseJpy(clean(rentText));
const { tsubo, area_m2 } = parseArea(clean(areaText));
const extra = {
source: "inshokuten",
listing_id: id,
rent_jpy: rentJpy,
tsubo,
area_m2,
tsubo_unit_jpy: parseJpy(clean(tsuboUnitText)) ?? tsuboUnit(rentJpy, tsubo),
floor: normalizeFloor(clean(floorText)?.replaceAll("地下", "B").replaceAll("地上", "") ?? null),
station: walkIndex > 1 ? stationParts.slice(1, walkIndex).join(" ") : null,
line: walkIndex > 1 ? stationParts[0] : null,
walk_min: parseWalkMin(raw.station),
deposit_months: null,
deposit_jpy: null,
key_money_months: null,
fixtures_transfer_jpy: parseJpy(clean(fixturesText)),
condition: $el.find(".bukkenItem__inukiIcon--inuki").length > 0 ? "inuki" : $el.find(".bukkenItem__inukiIcon--skeleton").length > 0 ? "skeleton" : null,
prev_business: clean(prevBusiness),
heavy_food_ok: notOpenable ? false : raw.business_types === null ? null : raw.business_types.includes("重飲食"),
business_limit: notOpenable ? "飲食店不可" : raw.business_types,
listed_at: parseYmd(raw.listed_at),
ward: parseWard(raw.address),
address_hint: raw.address,
tags,
raw
};
const link = new URL(href, HOST).href;
const image = $el.find("img.bukkenItem__picture").attr("src");
return {
title,
link,
guid: link,
pubDate: extra.listed_at === null ? void 0 : timezone(parseDate(extra.listed_at, "YYYY-MM-DD"), 9),
description: [raw.comment, summarize(extra)].filter(Boolean).join(" / "),
image: image && !image.includes("no_image") ? image : void 0,
_extra: extra
};
}).filter((item) => item !== null);
};
const handler = async (ctx) => {
const slug = ctx.req.param("area") ?? "kanto";
const area = AREAS.find((a) => a.slug === slug);
if (!area) throw new Error(`Unknown area "${slug}", expected one of ${AREAS.map((a) => a.slug).join(", ")}`);
const listUrl = `${HOST}/bukken/${area.path}/?mode=latest`;
return {
title: `飲食店.COM 新着物件 (${area.label})`,
link: listUrl,
language: "ja",
item: parseList(await rofetch(listUrl))
};
};
const route = {
path: "/bukken/:area?",
name: "新着物件",
url: "www.inshokuten.com",
maintainers: ["pseudoyu"],
handler,
example: "/inshokuten/bukken/23ward",
parameters: { area: {
description: "Region or 首都圏 sub-area",
default: "kanto",
options: AREAS.map((a) => ({
value: a.slug,
label: a.label
}))
} },
description: `New restaurant-property listings on 飲食店.COM sorted by 登録日 (first page, 20 listings). Each item's \`_extra\` carries the structured listing fields (賃料,坪,坪単価,階,最寄駅,造作譲渡料,現況,前業態,出店可能業態,登録日,…); unknown values are \`null\`. 保証金 and 礼金 are members-only on the site and therefore always \`null\`.`,
categories: ["other"],
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportRadar: true
},
radar: [{
source: ["www.inshokuten.com/bukken/:region/bukkens/list", "www.inshokuten.com/bukken/:region/bukkens/list/local-:area"],
target: (params) => `/bukken/${params.area ?? params.region}`
}]
};
//#endregion
export { handler, route };