ワンクリックで
peatix
Platform rules, field mappings, date extraction, and troubleshooting for the Peatix scraper
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Platform rules, field mappings, date extraction, and troubleshooting for the Peatix scraper
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Planning principles, model selection, and scope rules for the Architect agent
Implementation rules for database migrations, Python scrapers, and Next.js web for the Engineer agent
BaseScraper contract, field rules, and Peatix-specific conventions for the Scraper Expert agent
Scraper test execution rules, output validation criteria, and report format for the Tester agent
BaseScraper contract, field rules, and Peatix-specific conventions for the Scraper Expert agent
Platform rules and field mappings for the 誠品生活日本橋 (eslite spectrum Nihonbashi) scraper
| name | peatix |
| description | Platform rules, field mappings, date extraction, and troubleshooting for the Peatix scraper |
| applyTo | scraper/sources/peatix.py |
| Field | Value |
|---|---|
| Search URL | https://peatix.com/search?q=<keyword>&l=JP-13 |
| Rendering | JS-heavy — requires Playwright (sync_playwright) |
| Auth required | No |
| Rate limit | 1.5 s sleep between detail page requests |
| Source name | peatix |
| Event Field | Peatix Source |
|---|---|
source_id | MD5 hash of event URL (first 16 chars) |
raw_title | Event page <title> or <h1> |
start_date | _extract_peatix_dates(page_text) result |
end_date | Same function; None if single-day |
business_hours | _extract_peatix_business_hours(page_text, date_text) — time range (e.g., "14:00 - 15:30 GMT+09:00") |
location_name | _extract_peatix_location_from_text(page_text) — venue name |
location_address | Same function — street-level address (ward-only addresses rejected) |
raw_description | Full body text with date prefix prepended |
_extract_peatix_dates(page_text) — regex on full body textMon, May 12, 2025 (English, typical)YYYY年MM月DD日 (Japanese fallback)Prepend 開催日時: YYYY年MM月DD日\n\n when start_date is known.
Function: _extract_peatix_business_hours(page_text: str, date_text: str | None = None) -> str | None
Supported Formats:
DATE AND TIME\n\n...\n\n1:00 PM - 2:00 PM GMT+09:00日時\n\nYYYY/M/D ...\n\n14:00 - 15:30 GMT+09:00時 間|14:00~15:30 or 時間:14:00-15:30Fallback: If no structured block found, extracts time range from date_text CSS selector result (if provided).
Returns: Time range string (e.g., "14:00 - 15:30 GMT+09:00") or None.
Primary selector output comes from .ticket-price or [class*='price']. Treat selector values that are only generic labels (料金, 参加費, チケット, etc.) as empty.
When selector output is empty or generic, fall back to body text only if the line starts with a known price label and contains a price/free signal:
参加費, 料金, 入場料, 入場, チケット, 費用無料, 無償, 円, ¥, ¥, JPYprice_amount to the integer valueprice_info text and leave price_amount = NoneNever extract an unlabeled amount from body copy. It may be an item value, gift value, or unrelated number.
seen_urls set — initialized once per scrape() call, shared across all keyword loopsBody text must contain at least one TAIWAN_KEYWORDS entry.
Never disable this gate. Non-Taiwan events will pass the Tokyo filter and pollute the DB.
| Rule | Implementation |
|---|---|
| Location | l=JP-13 (Tokyo prefecture) in search URL |
| Date cutoff | Skip events older than 90 days; keep events with no start_date (annotator will handle) |
| Search limit | 20 pages per keyword |
When Peatix changes its page structure and selectors break:
mcp_browser_snapshot to capture the rendered DOM — Peatix is JS-rendered so static HTML differs.DATE AND TIME heading, price section, venue block._extract_peatix_dates() and _safe_text() selectors in peatix.py.Function: _extract_peatix_location_from_text(page_text: str) -> tuple[str | None, str | None]
Returns: (location_name, location_address)
Peatix renders venues in a fixed 3-line block with double blank lines as structural separators:
LOCATION\n\n<venue>\n\n<address>\n\nJapan場所\n\n<venue>\n\n<address>\n\nJapanOnline event detection: If first line after LOCATION or 場所 matches online markers (Online event, オンライン, ライブ配信), returns ("オンライン", "オンライン").
Address validation: Ward-only addresses (e.g., 中央区) without street-level detail (丁目, 番地) are rejected → returns (venue_name, None). Full addresses with street-level detail are accepted.
# English block
loc_block_m = re.search(r'LOCATION\n\n(.{3,100})\n\n([^\n]{3,200})', page_text)
# Japanese block
jp_loc_block_m = re.search(r'場所\n\n(.{3,100})\n\n([^\n]{3,200})', page_text)
if not location_name:
location_name = (
_safe_text(page, ".venue-name")
or _safe_text(page, "[class*='venue']")
)
# Do NOT use ".location" — it matches unrelated elements on the page
if not location_address:
location_address = (
_safe_text(page, ".venue-address")
or _safe_text(page, "[class*='address']")
)
if not location_address:
addr_m = re.search(r'(?:〒\d{3}-\d{4}[^\n]*|東京都[^\s,,\n]{3,60})', page_text)
if addr_m:
location_address = addr_m.group(0).strip()
| Symptom | Cause | Fix |
|---|---|---|
location_name set to wrong venue (e.g. 赤坂) | .location CSS selector matching unrelated element | Remove .location selector; rely on structured block regex |
location_name/location_address = 台南/台北 etc. | Structured block regex failed (single-newline bug), address fell through to annotation | Use double-newline regex (LOCATION\n\n or 場所\n\n) |
Missing business_hours for Japanese events | Only English DATE AND TIME block was extracted | Verify _extract_peatix_business_hours() includes 日時 block + body label patterns |
price_info is only 料金 | Selector returned a label-only string | Treat generic price labels as empty and use high-confidence body price label fallback |
All events missing start_date | Peatix changed date element markup | Use Chrome MCP to find new selector; update _extract_peatix_dates() |
| Events from outside Tokyo | l=JP-13 ignored or URL changed | Verify search URL still includes l=JP-13 |
| Duplicate events | seen_urls not shared across keyword loops | Ensure seen_urls is initialized once in scrape(), not per-keyword |
| Playwright timeout | JS render slower than usual | Increase wait_for_load_state("networkidle") timeout |
| Non-Taiwan events passing gate | TAIWAN_KEYWORDS too broad | Review keyword list; never remove the gate entirely |
| Cookie wall stored as event body | Pre-2026-06-28 events scraped before cookie guard added | Existing events need direct-URL one-off rescue (see below) — upsert_events() will not auto-fix due to idempotent skip |
Timeline: OneTrust cookie-consent wall became visible to Playwright in mid-2026. Events scraped between ~2026-06-15 and 2026-06-28 may have raw_description starting with "About cookies on this site" and null business_hours/location_name/location_address/price_info.
Why main.py --rescrape-ids won't fix it: database.upsert_events() skips existing non-force Peatix events by default (if event_key in existing_keys and not force: continue). The cookie guard (_looks_like_cookie_banner()) added in commit a62c3903 (2026-06-28) only prevents new writes — it does not trigger re-scraping of existing dirty rows.
Correct repair path: Use a direct-URL one-off rescue script (e.g., scraper/_oneoff_rescue_peatix_cookies.py) that:
raw_description LIKE 'About cookies on this site%'PeatixScraper()._scrape_detail(page, source_url) directly for each URLraw_description + complete fields (location/time/price)upsert_events(..., force_keys={(source_name, source_id)}) to force-overwrite existing rowsNever rely on --rescrape-ids for cookie-wall repair — it will silently skip the row and report "0 events scraped" with no warning.