End-to-end FastMoss ads trend ranking scraper + analyzer. Uses kimi-webbridge to drive the user's logged-in browser, extracts rows into CSVs, and ships an aggregation script + Markdown report template. Scope: 3 table-based trend rankings in the Ad Engine section of fastmoss.com. The 3 card-based ad search pages (E-commerce Ads / Seeding Ads / Advertiser Insights) are out of scope — they're filter-search interfaces, not rankings. For products, creators, shops, videos, or live streams, use the corresponding sister skill.
Prerequisites
-
Kimi WebBridge daemon healthy:
~/.kimi-webbridge/bin/kimi-webbridge status # expect: {"running": true, "extension_connected": true}If not healthy: invoke
Skill(kimi-webbridge)and followreferences/operations.md. -
User is logged in to fastmoss.com in their browser (this skill reuses their session — no auth).
-
Read
references/environment.mdbefore writing new shell orevaluateJS.
Ads ranking URLs
| Ranking key | Chinese | URL | Schema |
|---|---|---|---|
tag |
Tag Insights | https://www.fastmoss.com/zh/creativecenter/insightTag |
table |
keyword |
Keyword Trends | https://www.fastmoss.com/zh/creativecenter/keyword-trends |
table |
category |
Trending Category Trends | https://www.fastmoss.com/zh/creativecenter/product-category-trends |
table |
| (not scripted) | E-commerce Ads | https://www.fastmoss.com/zh/creativecenter/search |
cards |
| (not scripted) | Seeding Ads | https://www.fastmoss.com/zh/creativecenter/seed |
cards |
| (not scripted) | Advertiser Insights | https://www.fastmoss.com/zh/creativecenter/advertiser |
cards |
Use /zh/ (Chinese) locale. Site is a NUXT/Vue SPA — always wait for hydration (--nav-sleep 6+).
Per-ranking column schemas
| Ranking | Header columns |
|---|---|
tag |
Tag, Preview, Impressions, Number of Related Videos |
keyword |
Rank, Keyword, Trend, Popularity Index, Impressions, Likes, Shares, Comments |
category |
Rank, Trending Category, Trend, Popularity Index, Impressions, Likes, Shares, Comments |
The Tag/Keyword/Trending Category columns are the primary entity. The scraper pre-parses into entity_name and keeps the raw cell under its original header.
Quick start
# Step 1: Scrape one ranking (Top N pages)
python <path-to-skill>/scripts/ads_scraper.py \
--ranking tag --pages 5 --out <out-dir>/tags.csv
python <path-to-skill>/scripts/ads_scraper.py \
--ranking keyword --pages 5 --out <out-dir>/keywords.csv
python <path-to-skill>/scripts/ads_scraper.py \
--ranking category --pages 3 --out <out-dir>/categories.csv
# Step 2: Multi-country comparison on one ranking
python <path-to-skill>/scripts/ads_filtered.py \
--ranking keyword --country 美国,印度尼西亚,泰国,马来西亚 \
--pages 3 \
--out <out-dir>/keywords_by_country.csv
# Step 3: Aggregate the per-ranking CSVs into a Markdown report
python <path-to-skill>/scripts/analyze.py \
--tag <out-dir>/tags.csv \
--keyword <out-dir>/keywords.csv \
--category <out-dir>/categories.csv \
--out-md <report>.md
<path-to-skill> is wherever this skill is installed (typically .claude/skills/fastmoss-ads inside the project, or ~/.claude/skills/fastmoss-ads for a user-level install). Run any script with --help for the full arg list.
Scripts
| Script | Purpose |
|---|---|
scripts/ads_scraper.py |
Top-N paginating scraper, any of the 3 trend rankings (schema auto-detected from <thead>) |
scripts/ads_filtered.py |
Multi-country filter + paginate |
scripts/analyze.py |
Aggregate CSVs → crosstabs (stdout) or full Markdown report (template-driven) |
References (read on demand)
| File | When to read |
|---|---|
references/environment.md |
Before writing any new shell or evaluate JS — Windows/bash/jq/heredoc rules (mirrors fastmoss-products' version) |
references/analysis_recipe.md |
The Markdown template analyze.py fills. Read to understand the report structure or to extend it |
Filter label vocabularies
Countries (16 markets)
--country <中文,comma-separated>: 全部, 美国, 印度尼西亚, 英国, 越南, 泰国, 马来西亚, 菲律宾, 西班牙, 墨西哥, 德国, 法国, 意大利, 巴西, 日本, 新加坡.
Same vocabulary as fastmoss-products / fastmoss-creators.
Critical gotchas (each cost real time on first run)
Full details in references/environment.md. Summary:
- @e refs from
snapshotgo stale after every navigation →"No node with given id found". Bundled scripts click by text viaevaluate, never @e refs. - Bash heredoc + JS regex =
"Invalid regular expression: missing /"— heredoc eats a backslash layer. Use.split('\n')over.replace(/\n+/g, ...). <thead>headers may report empty viainnerTexton first render. The bundledEXTRACT_JSreadsinnerText || textContentand falls back to thetitleattribute.- SPA hydration lag — pages may need slightly longer waits than product pages (6s default). Too fast → 0 rows.
- Schema differs between rankings —
ads_scraper.pyreads<thead>dynamically. Don't assume a fixed schema in downstream code; iterate overdict.keys(). jqmay not be installed — decode screenshots manually withpython -c+base64.- Python defaults to a locale-specific encoding on Windows — always
open(..., encoding='utf-8-sig')when reading the produced CSVs. - 3 of 6 ads pages have no
<table>— E-commerce Ads / Seeding Ads / Advertiser Insights are card-based filter UIs. Not covered by the bundled scraper.
CSV output schema
Base columns (always present):
page, ranking, rank, entity_name, country
Plus one column per <thead> header (Chinese, kept as-is). All CSVs are utf-8-sig (BOM) for Excel.
When using ads_filtered.py, one extra column is prepended:
filter_country
Session hygiene
- Default session
"fastmoss-ads"for all calls (isolated from sister skills' sessions). Override via--sessionif needed. - At task end:
curl -d '{"action":"close_session","args":{},"session":"fastmoss-ads"}' http://127.0.0.1:10086/command. - All bundled scripts use
newTab:Trueon first navigate so they work whether or not the session pre-exists.
Ads search pages (not scripted) — design note
The 3 unscripted pages (/search, /seed, /advertiser) are powerful filter-based ad listing interfaces. They expose 16-country, scenario (Hot Product Copy-Selling / Shop Ads / Creator Promotion / Commission-Based Selling), category, time-window, ad-language, CTA, landing-page, ROAS, and many other filters. Each result is a card, not a row. To scrape them:
- Apply filters by clicking the desired labels (text-match via evaluate).
- Wait for cards to load.
- Extract card data via DOM queries (
[class*=adItem]or similar).
Use references/environment.md rules when writing that JS. The bundled scripts in this skill do not cover this case.