/** * Pitbar 西荻窪 — http://freecalend.com/open/mem25771_date{YYYYMM} * * スケジュールは Ameblo (https://ameblo.jp/pitbar/) 経由で * freecalend.com に掲載されているが、自動リクエストをブロックしている。 * * 代替案: * - User-Agent を設定したヘッドレスブラウザで freecalend を取得 * - 公式 Instagram / X (@pitbar_nishiogi) の投稿を取得 * - 手動でイベントを登録する管理画面を用意する * * 月ごとの URL パターン: http://freecalend.com/open/mem25771_date{YYYYMM} */ import type { Scraper, VenueMeta } from "./base"; import type { EventInput } from "~/lib/db.server"; export const venue: VenueMeta = { id: "pitbar-nishiogikubo", name: "Pitbar 西荻窪", url: "https://ameblo.jp/pitbar", area: "西荻窪", }; const FREECALEND_MEMBER = "25771"; export const scraper: Scraper = { venue, async scrape(): Promise { const months = upcomingMonths(2); const events: EventInput[] = []; for (const ym of months) { const url = `http://freecalend.com/open/mem${FREECALEND_MEMBER}_date${ym}`; const res = await fetch(url, { headers: { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/124 Safari/537.36", Referer: "https://ameblo.jp/pitbar/", }, redirect: "follow", }); if (!res.ok) continue; const html = await res.text(); if (!html.trim()) continue; // freecalend は HTML テーブルカレンダー形式 // 内にイベント名と時刻が入る const { load } = await import("cheerio"); const $ = load(html); $("td[class*='day_']").each((_, el) => { const $el = $(el); const text = $el.text().trim(); if (!text || /^\d+$/.test(text)) return; // 日付のみのセルはスキップ const dayMatch = $el.attr("class")?.match(/day_(\d+)/); if (!dayMatch) return; const day = dayMatch[1].padStart(2, "0"); const date = `${ym.slice(0, 4)}-${ym.slice(4)}-${day}`; const lines = text.split(/[\n\r]+/).map((l) => l.trim()).filter(Boolean); const title = lines[0] ?? text.slice(0, 100); const timeMatch = text.match(/(\d{1,2}:\d{2})/g); const openTime = timeMatch?.[0] ?? null; const startTime = timeMatch?.[1] ?? null; events.push({ venue_id: venue.id, title, date, open_time: openTime, start_time: startTime, source_url: url, }); }); } if (events.length === 0) { throw new Error( "Pitbar freecalend からデータを取得できませんでした。" + "freecalend.com が自動リクエストをブロックしている可能性があります。" ); } return events; }, }; function upcomingMonths(count: number): string[] { const months: string[] = []; const now = new Date(); for (let i = 0; i < count; i++) { const d = new Date(now.getFullYear(), now.getMonth() + i, 1); const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); months.push(`${y}${m}`); } return months; }