Install
openclaw skills install @nanookai/ptt-bbsScrape PTT web BBS (ptt.cc) without login to fetch boards, categories, articles, comments, push/boo counts, scores, search results, and essence area data.
openclaw skills install @nanookai/ptt-bbsScrape the PTT web interface (https://www.ptt.cc) following its natural
navigation flow: board index (article list) → single article. The primary
deliverable is push statistics: 推 (push/upvote), 噓 (boo/downvote),
→ (neutral comment), and the score 推 − 噓.
| Feature | URL pattern | Script command |
|---|---|---|
| Hot board list (熱門看板) | /bbs/index.html | boards |
| Board category tree (分類看板) | /cls/<id> (root = 1) | cls [id] |
| Board article list + pagination | /bbs/<Board>/index.html, older: index<N>.html | list <board> --pages N |
| Board search | /bbs/<Board>/search?q=...&page=N | search <board> <query> |
| Single article + push comments | /bbs/<Board>/M.<ts>.A.<id>.html | article <url> |
| Per-article + board push totals | (list, then fetch each article) | stats <board> |
| Essence area (精華區) | /man/<Board>/index.html | man <board> [path] |
Everything else on ptt.cc (posting, pushing, mail) requires a real BBS login over telnet/ssh — not available on the web interface, out of scope.
scripts/ptt_scraper.py implements all of the above and prints JSON
(UTF-8, ensure_ascii=False). Prefer running it over writing new code.
It needs requests and beautifulsoup4:
pip install requests beautifulsoup4
# or without installing: uv run --with requests --with beautifulsoup4 python scripts/ptt_scraper.py ...
# Discover boards
python scripts/ptt_scraper.py boards # hot boards with user counts
python scripts/ptt_scraper.py cls # category root; follow ids deeper
# 1. Article list of a board (newest first; --pages walks older index pages)
python scripts/ptt_scraper.py list Gossiping --pages 2
# Search: keywords, author:<userid>, thread:<title>, recommend:<n> (score >= n)
python scripts/ptt_scraper.py search Gossiping "recommend:50 颱風"
python scripts/ptt_scraper.py search NBA "author:someuser" --pages 2
# 2. One article: exact push/boo/neutral counts + every comment
python scripts/ptt_scraper.py article https://www.ptt.cc/bbs/Gossiping/M.1234567890.A.ABC.html
# 3. Board stats: fetch each listed article, report per-article counts
# and board-wide totals (push_count, boo_count, neutral_count, score)
python scripts/ptt_scraper.py stats NBA --pages 1 --limit 10
# Essence area (精華區): directories end in index.html, crawl deeper via path
python scripts/ptt_scraper.py man Gossiping
stats is the command that answers "give me the 推/噓/總和 for board X":
its totals object sums counts across the fetched articles, and articles
carries the per-article breakdown.
Unless the user asks otherwise:
over18=1 (domain .ptt.cc) or boards like
Gossiping redirect to a confirmation page. Always set it; it is harmless on
unrestricted boards.https://www.ptt.cc/bbs/<Board>/index.html is the newest
page. Older pages are index<N>.html with N decreasing; follow the
‹ 上頁 link in div.btn-group-paging rather than guessing numbers.div.r-ent with children div.nrec
(score bucket), div.title > a (title + href), div.meta div.author,
div.date. A div.title without an <a> is a deleted article — skip it.
Rows after div.r-list-sep are pinned posts (置底), usually skip those too.nrec is a bucket, not exact counts: 爆 = score ≥ 100, digits = exact
score 1–99, X1…X9 ≈ −10…−90, XX ≤ −100, empty = 0. For exact 推/噓
numbers you must fetch the article page.GET /bbs/<Board>/search?q=<query>&page=<n> (page starts at 1).
Query operators combine with spaces: plain keywords (title match),
author:<userid>, thread:<title> (same reply thread),
recommend:<n> (score bucket ≥ n, −100..100). Results use the same
r-ent markup as index pages. An empty result page means past the end.div#main-content. Header fields:
div.article-metaline holds 作者/標題/時間, div.article-metaline-right
holds 看板 — match class^="article-metaline" to get both. Comments are
div.push, each with span.push-tag (推 / 噓 / →),
span.push-userid, span.push-content, span.push-ipdatetime.push_count = tags equal to 推, boo_count = 噓,
everything else (the → arrow) is neutral; score = push − boo./bbs/index.html, /cls/<id>): entries are
a.board with div.board-name, div.board-nuser (online users),
div.board-class, div.board-title. On /cls/ pages an href starting
with /cls/ is a subcategory, /bbs/ is a board./man/<Board>/index.html): rows are div.m-ent
(not r-ent), each div.title > a. Links ending in index.html are
subdirectories; others are archived articles.ensure_ascii=False
so Chinese titles stay readable.boards or cls to find it.list (or search with the user's filter) to show what's on the
board: title, author, date, score hint from nrec.search with recommend:<n> is the
cheap way to get high-score articles).article on the selected URLs (or stats for a whole page of them)
to get exact 推/噓/→ counts and the score.stats.If the user needs something the script doesn't cover (saving to CSV, crawling
many pages, custom aggregation), import its functions (make_session,
list_board, search_board, get_article, parse_article, hot_boards,
man_listing) instead of re-implementing the parsing from scratch.