UNPKG

rsshub

Version:
148 lines (146 loc) 5.91 kB
import { t as rofetch } from "./ofetch-U0CdpE2g.mjs"; import { t as cache_default } from "./cache-C1Y-9qDV.mjs"; import { a as ridershipItem, i as parsePct, n as parseCount, r as parseFiscalYear, t as cellText } from "./utils-BkC3p9Kh.mjs"; import { load } from "cheerio"; import pMap from "p-map"; //#region lib/routes/jreast/ridership.ts const HOST = "https://www.jreast.co.jp"; const LATEST = `${HOST}/company/data/passenger/`; const OPERATOR = "JR東日本"; const MAX_SUBPAGES = 9; /** The site's WAF rejects requests that do not look like a browser navigation. */ const BROWSER_HEADERS = { accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "accept-language": "ja,en;q=0.8", "sec-fetch-dest": "document", "sec-fetch-mode": "navigate", "sec-fetch-site": "none", "sec-fetch-user": "?1" }; const fetchPage = (url) => rofetch(url, { headers: BROWSER_HEADERS, responseType: "text" }); /** null on 404 (the 101位以下 sequence and older-year URLs end that way). */ const fetchOptional = async (url) => { try { return await fetchPage(url); } catch { return null; } }; /** * `table.passengerTable` rows. ベスト100 page: 順位 | 駅名 | 定期外 | 定期 | 合計 | 前年度比; 101位以下 pages * (`{year}_01.html` …): 駅名 | 定期外 | 定期 | 合計 with no rank, so the rank is computed from the position. * 前年度比 is a ratio (102.5) from FY2023 on and a % change (△ 38.5) before that. */ const parseStations = (html, fiscalYear, firstRank) => { const $ = load(html); return $("table.passengerTable tr").toArray().map((tr) => ({ cells: $(tr).find("td").toArray().map((td) => cellText($(td).text())), link: $(tr).find("td.stationName a").attr("href") })).filter((r) => r.cells.length >= 4).map((r, index) => { const ranked = r.cells.length >= 6; const [rankText, station, nonCommuter, commuter, total, yoyText] = ranked ? r.cells : [ "", ...r.cells, "" ]; const yoy = parsePct(yoyText); const raw = { rank: rankText, station, non_commuter: nonCommuter, commuter, total, yoy: yoyText }; const extra = { source: "jreast", operator: OPERATOR, station, line: null, fiscal_year: fiscalYear, daily_average: parseCount(total), unit: "人/日", measure: "boarding", rank: ranked ? parseCount(rankText) : firstRank + index, yoy_pct: yoy === null ? null : yoy > 50 ? Math.round((yoy - 100) * 10) / 10 : yoy, raw }; return ridershipItem(extra, r.link ?? `${HOST}/company/data/passenger/`); }); }; const fetchYear = async (indexHtml, indexUrl) => { const heading = load(indexHtml)("h1").first().text(); const fiscalYear = parseFiscalYear(heading); if (fiscalYear === null) throw new Error("jreast: fiscal year not found in the page heading"); const year = fiscalYear; const items = parseStations(indexHtml, fiscalYear, 1); const dir = indexUrl.slice(0, indexUrl.lastIndexOf("/") + 1); const subpages = await pMap(Array.from({ length: MAX_SUBPAGES }, (_, i) => `${dir}${year}_${String(i + 1).padStart(2, "0")}.html`), (url) => fetchOptional(url), { concurrency: 2 }); for (const html of subpages) { const page = html === null ? [] : parseStations(html, fiscalYear, items.length + 1); if (page.length === 0) break; items.push(...page); } return { fiscalYear, items }; }; /** Latest year lives at `/company/data/passenger/`; earlier years at `{year}.html` there (FY2023) or under `/passenger/` (FY2022 and before). */ const resolveYearPage = async (year) => { const candidates = year === void 0 ? [LATEST] : [`${LATEST}${year}.html`, `${HOST}/passenger/${year}.html`]; for (const link of candidates) { const html = await fetchOptional(link); if (html !== null) return { link, html }; } throw new Error(`jreast: FY${year} is not published`); }; const handler = async (ctx) => { const yearParam = ctx.req.param("year"); const page = await cache_default.tryGet(`jreast/ridership:${yearParam ?? "latest"}`, async () => { const { link, html } = await resolveYearPage(yearParam); return { link, ...await fetchYear(html, link) }; }); const link = page.link; return { title: `JR東日本 各駅の乗車人員(${page.fiscalYear}年度一日平均)`, link, language: "ja", item: page.items }; }; const route = { path: "/ridership/:year?", name: "各駅の乗車人員", url: "www.jreast.co.jp", maintainers: ["pseudoyu"], handler, example: "/jreast/ridership", parameters: { year: { description: "Fiscal year (`2019` … latest); omit for the latest year" } }, description: `Annual 各駅の乗車人員 (one-day average of boarding passengers) for every JR東日本 station, from [各駅の乗車人員](${HOST}/company/data/passenger/) (ベスト100 plus the 101位以下 pages). One item per station and fiscal year; \`_extra\` follows the shared ridership shape with \`measure: 'boarding'\` — JR東日本 counts 乗車 only, so the figures are not comparable with the 乗降 figures of other operators. \`yoy_pct\` is normalised to a % change (the site prints a ratio such as 102.5 from FY2023 on). The operator does not publish a release date, so items have no \`pubDate\`. ::: warning The site's [ご利用にあたって](${HOST}/site/rules.html) states: 「当サイト上に掲載されている全ての写真、社名ロゴ、画像、文章等のデータ等の利用については、複製・転用・転載・電磁的加工・送信・頒布・二次的使用・その他これらに類する全ての行為も含め、一切お断りいたします。」 Use the figures accordingly. :::`, categories: ["other"], features: { requireConfig: false, requirePuppeteer: false, antiCrawler: true, supportRadar: true }, radar: [{ source: ["www.jreast.co.jp/company/data/passenger/", "www.jreast.co.jp/passenger/"], target: "/ridership" }] }; //#endregion export { handler, route };