UNPKG

rsshub

Version:
173 lines (172 loc) 6.97 kB
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 };