summaryrefslogtreecommitdiff
path: root/app/scrapers/nine-spices.ts
diff options
context:
space:
mode:
Diffstat (limited to 'app/scrapers/nine-spices.ts')
-rw-r--r--app/scrapers/nine-spices.ts94
1 files changed, 94 insertions, 0 deletions
diff --git a/app/scrapers/nine-spices.ts b/app/scrapers/nine-spices.ts
new file mode 100644
index 0000000..f4afa3d
--- /dev/null
+++ b/app/scrapers/nine-spices.ts
@@ -0,0 +1,94 @@
+/**
+ * Nine Spices (新宿) — https://9spices.rinky.info/schedule/
+ *
+ * WordPress ベースの独自テーマ。構造:
+ * <div class="event-cont-par YYYY-MM-DD">
+ * <h3 class="event-title sch"><a href="...">タイトル</a></h3>
+ * <div class="event-leftcol" itemprop="startDate" content="YYYY-MM-DDThh:mm">
+ * <div class="sch-actlist"><span class="actlist-name">アーティスト</span></div>
+ * <div class="sch-time"><div><span>OPEN</span><span>hh:mm</span></div><div><span>START</span>...</div></div>
+ * <div class="sch-price"><div><span>ADV</span><span>¥XXX</span></div></div>
+ */
+import * as cheerio from "cheerio";
+import type { Scraper, VenueMeta } from "./base";
+import type { EventInput } from "~/lib/db.server";
+
+export const venue: VenueMeta = {
+ id: "nine-spices",
+ name: "Nine Spices",
+ url: "https://9spices.rinky.info",
+ area: "新宿",
+};
+
+export const scraper: Scraper = {
+ venue,
+ async scrape(): Promise<EventInput[]> {
+ const res = await fetch("https://9spices.rinky.info/schedule/");
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+ const $ = cheerio.load(await res.text());
+ const events: EventInput[] = [];
+
+ $("div[class*='event-cont-par']").each((_, el) => {
+ const $el = $(el);
+
+ // class="event-cont-par 2026-05-01" → extract date
+ const classAttr = $el.attr("class") ?? "";
+ const dateMatch = classAttr.match(/(\d{4}-\d{2}-\d{2})/);
+ if (!dateMatch) return;
+ const date = dateMatch[1];
+
+ const $titleLink = $el.find("h3.event-title a").first();
+ const title = $titleLink.text().trim();
+ if (!title) return;
+
+ const sourceUrl = $titleLink.attr("href") ?? null;
+
+ const artist = $el.find("span.actlist-name")
+ .map((_, s) => $(s).text().trim())
+ .get()
+ .join("、") || null;
+
+ // <div class="sch-time"><div><span>OPEN</span><span>18:30</span></div>...
+ let openTime: string | null = null;
+ let startTime: string | null = null;
+ $el.find("div.sch-time div").each((_, row) => {
+ const spans = $(row).find("span");
+ const label = spans.eq(0).text().trim().toUpperCase();
+ const value = spans.eq(1).text().trim();
+ if (label === "OPEN") openTime = value || null;
+ if (label === "START") startTime = value || null;
+ });
+
+ // <div class="sch-price"><div><span>ADV</span><span>¥2,500</span></div>...
+ const priceParts: string[] = [];
+ $el.find("div.sch-price div").each((_, row) => {
+ const spans = $(row).find("span");
+ const label = spans.eq(0).text().trim();
+ const value = spans.eq(1).text().trim();
+ if (label && value) priceParts.push(`${label} ${value}`);
+ });
+ const price = priceParts.length ? priceParts.join(" / ") : null;
+
+ const imageUrl = $el.find("img.wp-post-image").first().attr("src") ?? null;
+
+ const ticketUrl =
+ $el.find("a[href*='livepocket'], a[href*='eplus'], a[href*='pia'], a[href*='tiget'], a[href*='ticket']")
+ .first().attr("href") ?? null;
+
+ events.push({
+ venue_id: venue.id,
+ title,
+ artist,
+ date,
+ open_time: openTime,
+ start_time: startTime,
+ price,
+ ticket_url: ticketUrl,
+ image_url: imageUrl,
+ source_url: sourceUrl,
+ });
+ });
+
+ return events;
+ },
+};