diff options
Diffstat (limited to 'app/scrapers/nine-spices.ts')
| -rw-r--r-- | app/scrapers/nine-spices.ts | 94 |
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; + }, +}; |
