summaryrefslogtreecommitdiff
path: root/app/scrapers/nine-spices.ts
diff options
context:
space:
mode:
authoryyamashita <yyamashita@mosquit.one>2026-05-07 19:27:50 +0900
committeryyamashita <yyamashita@mosquit.one>2026-05-07 19:27:50 +0900
commitd5e975b601e70adf901c8e1eb7e61f0388941195 (patch)
treef1778ff15b6540b44c354cb76c44aac795448c4a /app/scrapers/nine-spices.ts
parentbffc2c74408ff7163cea0c0392dfc4b15c620a5f (diff)
Add 5 new venue scrapers; extract artist info for WARP, shibuya-o, MOON STEP, mod
New scrapers: Fever 下北沢, Nine Spices 下北沢, 西荻窪 JAM, mod 柴崎, 中野 MOON STEP Artist extraction added/fixed: - warp-kichijoji: parse div.w-flyer (clone + remove nested notes-wrapper) - shibuya-o: rewrite to scrape each sub-venue; artist from li.p-scheduled-card__artist-item - moon-step-nakano: parse 出演 section from WordPress API description HTML - mod-shibasaki: fetch individual event pages in parallel; handle live:/出演:/・ bullet formats Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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;
+ },
+};