Boss Zhipin Scraper
eatmoreduck/boss-zhipin-scraper
Scrape BOSS直聘 (job listing site) via Chrome CDP. An agent skill from eatmoreduck/boss-zhipin-scraper.
Scrape video stats from the Douyin creator center (creator.douyin.com) using AppleScript to automate the user's logged-in Chrome.
$ npx skills add TradingAi666/TzFilm-Douyin-Tool --skill douyin-creator-scraping -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install TradingAi666/TzFilm-Douyin-Tool douyin-creator-scraping --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
Claude Code skills documentation · loads skills from .claude/skills/
Install the "douyin-creator-scraping" agent skill from https://github.com/TradingAi666/TzFilm-Douyin-Tool/tree/main into .claude/skills/douyin-creator-scraping/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "douyin-creator-scraping", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add TradingAi666/TzFilm-Douyin-Tool --skill douyin-creator-scraping -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install TradingAi666/TzFilm-Douyin-Tool douyin-creator-scraping --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "douyin-creator-scraping" agent skill from https://github.com/TradingAi666/TzFilm-Douyin-Tool/tree/main into .agents/skills/douyin-creator-scraping/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "douyin-creator-scraping", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add TradingAi666/TzFilm-Douyin-Tool --skill douyin-creator-scraping -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install TradingAi666/TzFilm-Douyin-Tool douyin-creator-scraping --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "douyin-creator-scraping" agent skill from https://github.com/TradingAi666/TzFilm-Douyin-Tool/tree/main into .cursor/skills/douyin-creator-scraping/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "douyin-creator-scraping", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add TradingAi666/TzFilm-Douyin-Tool --skill douyin-creator-scraping -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install TradingAi666/TzFilm-Douyin-Tool douyin-creator-scraping --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "douyin-creator-scraping" agent skill from https://github.com/TradingAi666/TzFilm-Douyin-Tool/tree/main into .gemini/skills/douyin-creator-scraping/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "douyin-creator-scraping", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install TradingAi666/TzFilm-Douyin-Tool douyin-creator-scrapingInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add TradingAi666/TzFilm-Douyin-Tool --skill douyin-creator-scraping -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "douyin-creator-scraping" agent skill from https://github.com/TradingAi666/TzFilm-Douyin-Tool/tree/main into .github/skills/douyin-creator-scraping/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "douyin-creator-scraping", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add TradingAi666/TzFilm-Douyin-Tool --skill douyin-creator-scraping -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install TradingAi666/TzFilm-Douyin-Tool douyin-creator-scraping --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "douyin-creator-scraping" agent skill from https://github.com/TradingAi666/TzFilm-Douyin-Tool/tree/main into .opencode/skills/douyin-creator-scraping/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "douyin-creator-scraping", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
douyin-creator-scrapingScrape video stats from the Douyin creator center (creator.douyin.com) using AppleScript to automate the user's logged-in Chrome.
Douyin Creator Scraping is an agent skill from TradingAi666/TzFilm-Douyin-Tool. Scrape video stats from the Douyin creator center (creator.douyin.com) using AppleScript to automate the user's logged-in Chrome. Covers the data-center/content page. Use when setting up periodic data collection, building a Douyin stats tracker, or automating export of video performance data.
Its SKILL.md is about 7.2k tokens, which your agent loads only when the skill is triggered. The skill folder holds 36 other files, including scripts (for example `README.md` and `auto-reply/package.json`).
It sits in Data & Analytics, covering Web scraping. It works with Douyin, macOS and Python. The repository describes itself as: 自动从抖音创作者中心导出视频数据,支持 macOS 后台定时运行. The licence is MIT.
8 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit a0c12c3. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships 1 file in scripts/ (JavaScript, from the files we listed), which the agent can run.
Shell commands in SKILL.md call:
osascriptpython3From the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
creator.douyin.comAlso links to:
github.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Douyin Creator Scraping loads about 7.2k tokens when it runs. Until then it costs about 79 tokens; SKILL.md has 2,631 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.
The full file from TradingAi666/TzFilm-Douyin-Tool at commit a0c12c3, republished under its MIT licence (© TradingAi666). 2,631 words, ~7,221 tokens.
.claude/skills/douyin-creator-scraping/SKILL.md (or your agent's skills folder). This skill also uses 33 other files; get the full folder from GitHub.This is the root cause of 99% of "download timeout" failures. launchd-spawned
Python processes cannot read ~/Downloads/, ~/Documents/, or ~/Desktop/ due to
macOS TCC (Transparency, Consent, and Control). glob.glob(), os.listdir(),
even osascript -e 'do shell script "ls/cp/cat ..."' — ALL silently return empty
or zero from launchd context.
The fix: redirect Chrome's download directory to /tmp/. /tmp has zero TCC
restrictions. Chrome writes there freely, Python reads there freely.
# One-time setup — persists across Chrome restarts:
defaults write com.google.Chrome DownloadDirectory -string "/tmp/douyin_dl"After this, download detection is trivially simple:
def fetch_newest_xlsx():
import glob
pattern = os.path.join('/tmp/douyin_dl', '作品列表*.xlsx')
files = glob.glob(pattern)
if not files:
return None, None
newest = max(files, key=os.path.getmtime)
return newest, os.path.basename(newest)Detection time: ~2 seconds. Works identically from terminal and launchd.
| Approach | Why |
|---|---|
glob.glob('~/Downloads/...') from launchd | TCC blocks — returns [] |
osascript -e 'do shell script "ls/cp/cat ..."' | Returns empty/0 from launchd |
osascript -e 'tell app "Finder" to duplicate...' | Finder times out (>30s) even for single files |
| mtime comparison | Can't detect if glob can't list files |
File snapshot on ~/Downloads/ | Snapshot always set() because glob returns empty |
| Adding python3 to Full Disk Access | macOS ignores FDA for system binaries |
Second most common failure after TCC. Douyin creator center sessions expire after a few days. There are TWO distinct failure modes depending on how expired the session is:
When the session expired recently (hours to ~1 day), clicking "导出数据" still triggers a download — but the server returns a JSON error instead of an xlsx file:
{"BaseResp":{"StatusCode":8,"StatusMessage":"用户未登录"}}openpyxl then fails with File is not a zip file. The PK header check catches this.
When the session has been expired for longer (~1+ days), the server returns an
error response that Chrome does NOT interpret as a file download. No file ever
appears in the download directory. The wait_for_download PK check never fires
because glob returns [] every iteration. The scraper logs only:
点击导出: clicked:3
等待下载... 10s
等待下载... 20s
...
❌ 等待下载超时Crucially, there are NO ⚠️ 非xlsx文件 or ❌ 登录态过期 log lines — the
file was never downloaded, so the PK validation code path is never reached.
After too many reload attempts with an expired session, the page degrades further. The Garfish micro-frontend stops initializing entirely:
document.body.innerText shows only sidebar navigation (~66 chars): "高清发布 首页 活动管理..."__garfishmockhtml__ attribute — the placeholder HTML, not the live appdocument.querySelectorAll('button') returns only 1 button ("高清发布")This is the same root cause as Mode B (expired session) but the symptoms are different — it looks like the page failed to load, not that the download failed.
| Symptom | Root cause |
|---|---|
Export clicked, download timeout, NO 非xlsx/登录态过期 log | Mode B: session deeply expired, no file downloaded |
Export clicked, download timeout, ⚠️ 非xlsx文件: {"BaseResp"... | Mode A: session recently expired, JSON error downloaded |
File is not a zip file in parse_xlsx | JSON error body passed PK check (unlikely but possible) |
作品列表.xlsx is only ~130 bytes | JSON response, not real Excel (which is 6KB+) |
not found, total buttons:1 + bodyLen ~66 | Degradation: session expired, Garfish app won't init |
| Error happens suddenly after days of success | Session cookie TTL expired → needs re-login |
Health check returns js_ok but scrape still fails | Health check does NOT validate login session — see note below |
The pre-flight health check (health_check()) validates AppleScript JS access
and tab existence — it does NOT validate the Douyin login session. A health
check returning js_ok means the AppleScript pipeline works, but the user may
still be logged out.
Recovery: User must re-login to creator.douyin.com in Chrome. No script changes needed.
After re-login, verify with a manual scrape run: python3 ~/.hermes/scripts/douyin_hourly.py
After login, Chrome may land on the creator home page (/creator-micro/home).
The Garfish data-center app never loads there.
Fix: force-navigate URL in reload_page() — never just reload t:
set URL of t to "https://creator.douyin.com/creator-micro/data-center/content"
delay 3
reload t| Symptom | Root cause |
|---|---|
tab not found / refresh btn not found | On /home not /data-center/content |
Before attempting any scrape, validate the AppleScript pipeline with a lightweight JS execution test. This catches 3 common silent failures before wasting time on a full page reload + Garfish wait cycle:
def health_check():
"""Returns (ok, reason). Runs in ~2 seconds if healthy."""
test_js = "/tmp/dy_health_check.js"
with open(test_js, "w") as f:
f.write("(function(){ return 'js_ok'; })()")
script = f'''
tell application "Google Chrome"
repeat with w in windows
repeat with t in tabs of w
if (URL of t) contains "creator.douyin.com" then
set js to read POSIX file "{test_js}"
return execute t javascript js
end if
end repeat
end repeat
return "no_tab"
end tell
'''
try:
result = subprocess.check_output(['osascript', '-e', script], timeout=15).decode()
except subprocess.TimeoutExpired:
return False, "AppleScript 执行超时"
except Exception as e:
if "Allow JavaScript" in str(e) or "JavaScript 的功能已关闭" in str(e):
return False, "🔧 AppleScript JS 已关闭 → Chrome 菜单勾上"
return False, f"异常: {str(e)[:100]}"
if result == "no_tab":
return False, "📄 未找到 creator.douyin.com 标签页"
if "js_ok" not in result:
return False, f"⚠️ JS 返回异常: {result[:100]}"
return True, "ok"Call this at the top of main(). If it fails, send the reason string directly to Telegram — the user gets an actionable error message instead of a generic timeout.
Why this matters: AppleScript JS silently resets (Chrome updates, re-logins, desktop switches). Without this check, the scrape silently fails after 30s of polling for buttons that never appear. With it, failure is detected in ~2s with a clear fix instruction.
When the health check returns "no_tab" but the user says Chrome is open, run this
diagnostic to find ALL douyin-related tabs across every window. Use execute_code
(not terminal) because the AppleScript & string concatenation operator is
misinterpreted as shell backgrounding by the terminal tool:
import subprocess
script = '''
tell application "Google Chrome"
set output to ""
repeat with w in windows
repeat with t in tabs of w
if URL of t contains "douyin" then
set output to output & (URL of t) & " | win_id:" & (id of w) & linefeed
end if
end repeat
end repeat
if output is "" then
return "NO_DOUYIN_TABS"
end if
return output
end tell
'''
result = subprocess.check_output(['osascript', '-e', script], timeout=10).decode().strip()
print(result)This reveals:
/content/manage, /data-center/content, /home, etc.)Common findings:
| Diagnostic result | Action |
|---|---|
NO_DOUYIN_TABS | User needs to open creator.douyin.com in Chrome |
Tab at /creator-micro/content/manage | Health check passes (URL contains creator.douyin.com) — reload_page() will navigate to /data-center/content |
Tab at /creator-micro/home | Health check passes — reload_page() will navigate to correct page |
Tab at www.douyin.com/user/self | Does NOT match — health check looks for creator.douyin.com specifically |
| Tab exists but on wrong Space | AppleScript may silently fail — move Chrome to current desktop |
creator.douyin.comdefaults write com.google.Chrome DownloadDirectory -string "/tmp/douyin_dl"execution error: JavaScript execution via AppleScript is turned off./tmp/douyin_dl/:
defaults write com.google.Chrome DownloadDirectory -string "/tmp/douyin_dl"execution error: "Google Chrome" has encountered an error: JavaScript execution via AppleScript is turned off. Fix:
re-check the menu item.The Douyin creator center uses Garfish (ByteDance micro-frontend framework):
#micro > #garfish_app_for_* (ID suffix changes on reload)document.querySelectorAll on document (not garfish container)document.body.innerText works after full page load + tab switchreload_page() MUST set the URL
to https://creator.douyin.com/creator-micro/data-center/content before reloading,
NOT just call reload t. Douyin's login redirect often lands on the home page
(/creator-micro/home), and reloading there keeps you on home — the Garfish
data-center app never loads. The AppleScript MUST:set URL of t to "https://creator.douyin.com/creator-micro/data-center/content"
delay 3
reload tactivate + set active tab index + set index of w to 1 are required.作品列表*.xlsx in /tmp/douyin_dl/ — poll with glob every 1s, validate PK header, timeout 40sThe script douyin_hourly.py was out of sync with this SKILL.md on two critical points:
reload_page() did reload t without setting URL → now sets URL to /data-center/contentwait_for_download() called move_latest_xlsx_via_applescript() looking in ~/Downloads → now uses direct glob on /tmp/douyin_dlBoth fixes are in the open-source repo. The SKILL.md was always correct — the code just wasn't following it.
Chrome blocks downloads triggered by JS click() on background tabs. Before any
interaction, the AppleScript MUST:
tell application "Google Chrome"
activate
set active tab index of w to tabIndex
set index of w to 1
end tellAll three (activate + active tab index + index of w to 1) are required.
Never use \uXXXX in JS via AppleScript — they fail silently. Use charCodeAt():
// 导出数据: 23548,20986,25968,25454
// 投稿列表: 25237,31295,21015,34920
// 刷新数据: 21047,26032,25968,25454
if(t.charCodeAt(0)===23548 && t.charCodeAt(1)===20986 ...) { ... }Always write JS to /tmp/*.js file, then read POSIX file in AppleScript.
作品名称 | 发布时间 | 体裁 | 审核状态 | 播放量 | 完播率 | 5s完播率 |
封面点击率 | 2s跳出率 | 平均播放时长 | 点赞量 | 分享量 | 评论量 | 收藏量 |
主页访问量 | 粉丝增量~99 rows per export. Percentages as decimals — multiply by 100.
CREATE TABLE video_stats (
timestamp DATETIME, title TEXT, publish_date TEXT,
plays INTEGER, avg_duration_sec INTEGER, ctr REAL,
finish_rate REAL,
likes INTEGER, comments INTEGER, shares INTEGER,
favorites INTEGER, danmaku INTEGER, status TEXT
)ctr = 5s完播率 (%), stored from xlsx column 「5s完播率」finish_rate = 完播率/播放占比 (%), stored from xlsx column 「完播率」 (2026-05-23 新增)avg_duration_sec = average watch duration in secondsstatus = 审核状态 («公开», «自见», «私密», «未通过», etc.) — added 2026-05-23. Must use ALTER TABLE ADD COLUMN + explicit column names in INSERT to avoid column ordering bugs (see Pitfall #7). Filter status NOT IN ('自见', '私密', '未通过', '审核中', '已删除') when syncing to Feishu — feishu_sync.py applies this filter in the SELECT query.publish_date has 3 formats mixed (short ISO / full ISO / Chinese). Always match on title only, never publish_date.
The scraper parsed «审核状态» from xlsx but didn't store it in the DB (line 348 reads it, line 388 didn't include it in INSERT). feishu_sync.py synced ALL 102 videos including 私密/自见 ones. Fix: ① ALTER TABLE video_stats ADD COLUMN status TEXT ② INSERT includes status with explicit column names ③ feishu_sync.py filters WHERE status NOT IN ('自见', '私密', '未通过', '审核中', '已删除'). Cleanup: after fix, manually delete stale Feishu records that match private video titles — sync is upsert-only, never deletes.
<!-- ~/Library/LaunchAgents/com.hermes.douyin-tracker.plist -->
<key>StartInterval</key><integer>3600</integer>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key><string>/Users/YOUR_USER</string>
<key>PATH</key><string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>Reload: launchctl unload/load ~/Library/LaunchAgents/...plist
Trigger: launchctl kickstart -k gui/$(id -u)/com.hermes.douyin-tracker
Douyin rolled out a new creator-center UI on 2026-05-22. The old
/data-center/content page (with "投稿列表" tab and "导出数据" button) was
replaced by /content/manage (with "全部作品/已发布/审核中/未通过" tabs and
no export button anywhere).
The fix: force-navigate to the OLD URL. The old Garfish micro-frontend at
/data-center/content is still served and still has the export button.
reload_page() MUST set the URL before reloading — never just reload t:
set URL of t to "https://creator.douyin.com/creator-micro/data-center/content"
delay 3
reload t| Symptom | Root cause |
|---|---|
tab not found (looking for 投稿列表) | On new /content/manage — tabs are 全部作品/已发布/审核中/未通过 |
not found, total buttons:1 (export) | New UI has NO export button, only "高清发布" |
| "投稿列表" charCode match returns nothing | The text "投稿列表" no longer exists in the new UI |
When Chrome's DownloadDirectory is set to /tmp/douyin_dl (as recommended in
Pitfall #1), the move_latest_xlsx_via_applescript() approach fails because
files are never in ~/Downloads. Poll /tmp/douyin_dl directly with glob
instead — it's faster (~2s vs 40s+ timeout) and more reliable:
def wait_for_download(before_ts, timeout=40):
import glob
for i in range(timeout):
time.sleep(1)
pattern = os.path.join(DOWNLOADS_DIR, '作品列表*.xlsx')
files = sorted(glob.glob(pattern), key=os.path.getmtime, reverse=True)
for f in files:
if os.path.getmtime(f) > before_ts:
with open(f, 'rb') as fh:
if fh.read(2) != b'PK': # validate real xlsx
... # login expiry check
continue
return f
return None| Symptom | Root cause |
|---|---|
等待下载超时 after export clicked | move_latest_xlsx_via_applescript looking in ~/Downloads but Chrome writes to /tmp/douyin_dl |
Files in /tmp/douyin_dl but script can't find them | Script never checks /tmp/douyin_dl directly |
AppleScript move from ~/Downloads returns empty | File was never there |
If the scraper's last data update was hours/days ago but launchctl list shows it
running (PID present, exit code 0), a zombie process may be stuck. The process
survived a Chrome/network hiccup but never exited.
Detection:
# Check for multiple instances:
ps aux | grep douyin_hourly
# If older PID still running alongside new ones, kill itFix: Kill the stale PID, then manually trigger a run or wait for next schedule:
kill <stale_pid>
launchctl kickstart -k gui/$(id -u)/com.hermes.douyin-trackerSymptoms:
douyin_hourly.log shows repeated "not found" for export button (10+ retries)douyin_hourly.py processes in ps auxWhen adding a new column (e.g. finish_rate) to an existing table via ALTER TABLE,
the column is added at the end of the table — regardless of where it appears in
CREATE TABLE IF NOT EXISTS. If the INSERT statement uses positional VALUES
without explicit column names, data goes into the wrong columns.
Example of the bug:
# Schema after ALTER TABLE: timestamp, title, ..., danmaku, finish_rate
# But CREATE TABLE IF NOT EXISTS has finish_rate before likes
# Since table already exists, CREATE TABLE is a no-op — schema unchanged
cursor.execute(
"INSERT INTO video_stats VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(now, title, pub_date, plays, duration, ctr5s, finish, # finish → position 6
likes, comments, shares, favorites, 0) # but position 6 is still 'likes'
)
# Result: finish_rate column gets 0 (the last placeholder),
# finish value goes to likes columnFix: Always use explicit column names in INSERT after schema migration:
cursor.execute(
"INSERT INTO video_stats (timestamp, title, publish_date, plays, "
"avg_duration_sec, ctr, finish_rate, likes, comments, shares, "
"favorites, danmaku) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(...)
)Detection: All rows show finish_rate = 0 or wrong values in adjacent columns.
The complete tool suite is now public at TradingAi666/TzFilmdouyintool
(formerly douyin-creator-scraping, renamed 2026-05-22):
douyin_hourly.py — hourly data scraping (AppleScript)douyin_new_video_tracker.py — video performance predictionprediction_query.py — query trending dataauto_reply.py — Python wrapper for comment auto-replyauto-reply/ — Node.js + Playwright browser automation for comment export/replyWhen adding a new column (e.g. finish_rate) to an existing table via ALTER TABLE,
the column is added at the end — regardless of CREATE TABLE IF NOT EXISTS ordering.
If INSERT uses positional VALUES without explicit column names, data goes into wrong columns.
Fix: Always use explicit column names after schema migration:
cursor.execute(
"INSERT INTO video_stats (timestamp, title, publish_date, plays, "
"avg_duration_sec, ctr, finish_rate, likes, comments, shares, "
"favorites, danmaku) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(...)
)Detection: finish_rate = 0 for all rows despite debug showing correct values.
Root cause A — flock() leaks through subprocess file descriptors:
douyin_hourly.py uses fcntl.flock() to prevent concurrent Chrome access.
After acquiring the lock, it calls subprocess.run(['python3', 'feishu_sync.py']).
Without close_fds=True, the child process inherits ALL open file descriptors,
including lock_fd. flock() locks are attached to the open file description
(kernel-level), not the file descriptor. Even when the parent's finally block
closes lock_fd, the child still holds a reference → lock never released until
child exits (up to 300s timeout).
Root cause B — stale lock from crash: If Python process crashes or is killed, 0-byte lock file persists on disk.
Symptoms:
⚠️ 已有抓取进程在运行,跳过本次 every tick, for hoursps aux | grep douyin returns nothing — NO process runningflock() returns EAGAINFix:
# In douyin_hourly.py AND douyin_new_video_tracker.py (3 locations total):
_sp.run(['python3', 'feishu_sync.py'],
timeout=300, capture_output=True, close_fds=True) # ← close_fds=TrueRecovery (stale lock):
rm -f ~/.hermes/.douyin_scrape.lock| Symptom | Root cause |
|---|---|
⚠️ 已有抓取进程在运行,跳过本次 × N | Lock leaked via subprocess FDs |
| Lock persists after parent process exits | Child process inherits open file description |
ps aux shows zero douyin processes | Parent exited, child still running feishu_sync |
scrape_total_followers — Garfish Isolation + AppleScript Fix (2026-05-24)The scrape_total_followers() function must navigate to /creator-micro/home to extract
fans because the Garfish micro-frontend at /data-center/content does NOT contain the
sidebar in document.body.innerText. The sidebar (with "关注 N 粉丝 N.N万") lives in
the parent frame outside Garfish.
Fix: AppleScript navigates to home page with proper exit repeat, then runs JS:
tell application "Google Chrome"
activate
repeat with w in windows
if found then exit repeat
repeat with t in tabs of w
if (URL of t) contains "creator.douyin.com" then
set URL of t to "https://creator.douyin.com/creator-micro/home"
delay 6
set js to read POSIX file "/tmp/dy_follower_js_v2.js"
return execute t javascript js
end if
end repeat
end repeat
end tellJS pattern: body.match(/\u5173\u6ce8\s*[\d,]+\s*\u7c89\u4e1d\s*([\d.]+)\s*\u4e07/) — matches "关注 N 粉丝 N.N万".
Timing: Fan extraction runs at step 10 (after data-center export + Telegram report),
when navigation to home won't break anything. close_fds=True on subprocess prevents lock leak.
today_new_fans Always 0 — Page Has No Daily New Fans Field (2026-05-24)The JS scraping searched document.body.innerText for "新增" (charCode 26032, 22686)
to find today's new follower count. The Douyin creator center home page does NOT
contain a single-day "今日新增粉丝" metric. The page only shows:
Symptom: account_stats.today_new_fans is always 0, even though total_fans
is increasing (48100→48900).
Fix: Compute today_new from DB deltas in save_account_stats():
def save_account_stats(data):
current_fans = data['fans']
today_start = datetime.now().strftime("%Y-%m-%d") + " 00:00:00"
# Baseline: first entry of today's total fans
first_today = cur.execute(
"SELECT total_fans FROM account_stats WHERE timestamp >= ? AND total_fans > 0 ORDER BY timestamp ASC LIMIT 1",
(today_start,)
).fetchone()
if first_today and first_today[0] > 0:
today_new = current_fans - first_today[0]
else:
# Fallback: difference from yesterday's last entry
yesterday_end = datetime.now().strftime("%Y-%m-%d") + " 00:00:00"
last_yesterday = cur.execute(
"SELECT total_fans FROM account_stats WHERE timestamp < ? AND total_fans > 0 ORDER BY timestamp DESC LIMIT 1",
(yesterday_end,)
).fetchone()
today_new = current_fans - (last_yesterday[0] if last_yesterday else current_fans)
cur.execute("INSERT INTO account_stats VALUES (?, ?, ?)",
(now, current_fans, today_new))Effect: At 12:07, log("💾 粉丝数据已入库: 48900 (+400)") — today_new correctly shows
400 new followers (48900 - 48500 = 400, where 48500 was the 06:06 first-of-day entry).
| Symptom | Root cause |
|---|---|
today_new_fans = 0 for all rows | Page has no "新增" text; JS search fails |
total_fans increases but today_new stays 0 | Two separate data sources: total from page works, daily doesn't exist |
| Feishu account chart shows flat "今日新增" line | All DB entries have today_new=0 |
The scrape_total_followers() function originally used AppleScript to navigate to
creator-micro/home, looping through ALL Chrome windows/tabs without exit repeat.
With many open tabs, the delay 5 per matching tab caused the 20s timeout.
Fix: Remove AppleScript navigation entirely. The creator center sidebar shows
"粉丝 N.N万" on every page — no need to navigate to home. Extract directly
from the current page's document.body.innerText.
Before (48 lines):
def scrape_total_followers():
# AppleScript: navigate to home (20s timeout!)
script = '''tell application "Google Chrome"
activate
repeat with w in windows # ← no exit repeat!
repeat with t in tabs of w # ← loops ALL tabs
if URL contains "creator.douyin.com" then
set URL to "creator-micro/home"
delay 5 # ← 5s delay × N tabs → timeout
end if
end repeat
end repeat
end tell'''
subprocess.check_output(['osascript', '-e', script], timeout=20)
time.sleep(3)
# ... then JS extractionAfter (~30 lines):
def scrape_total_followers():
"""从当前页面侧边栏提取粉丝总量(无需导航)"""
js = r"""(function(){
var result = {fans: 0};
var body = document.body.innerText.replace(/\n/g, ' ');
// Find 粉丝 by charCode (see Pitfall #7)
...
return JSON.stringify(result);
})()"""
# Write JS to file → AppleScript execute → no navigation needed
raw = run_js_file('/tmp/dy_follower_js.js', timeout=15)| Symptom | Root cause |
|---|---|
导航首页失败: timed out after 20 seconds | Nested loop × delay per tab × no exit |
| Fans data missing from account_stats | Function returns None on timeout |
粉丝: 48500 (今日+0) but total is 48900 | Stale data from old successful run |
The scraper chains a Feishu sync as step 10 after Telegram report — see douyin-automation/references/feishu-sync.md.
~/.hermes/scripts/douyin_hourly.py~/.hermes/logs/douyin_hourly.log~/.hermes/douyin_stats.db/tmp/douyin_dl/© TradingAi666, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 33 other files (scripts) in the repository root of TradingAi666/TzFilm-Douyin-Tool.
Open the folder on GitHubat commit a0c12c3
Douyin Creator Scraping next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Douyin Creator Scraping this skillTradingAi666/TzFilm-Douyin-Tool | 406 | — | ~7.2k | Automated safety check: Pass | MIT | |
| Boss Zhipin Scrapereatmoreduck/boss-zhipin-scraper | 1.5k | — | ~2.6k | Automated safety check: Pass | MIT | |
| Crawl4AI Web Scrapingsmallnest/goclaw | 599 | 1 repos | ~2.5k | Automated safety check: Pass | MIT | |
| Axyusukebe/ax | 719 | 1 repos | ~918 | Automated safety check: Pass | MIT | |
| Google Maps ScraperMahanaicoach/google-maps-scraper-kit | 1.3k | — | ~2.8k | Automated safety check: Pass | MIT | |
| Python Executorcortega26/chile-hub | 113 | 2 repos | ~1.5k | Automated safety check: Pass | MIT |
eatmoreduck/boss-zhipin-scraper
Scrape BOSS直聘 (job listing site) via Chrome CDP. An agent skill from eatmoreduck/boss-zhipin-scraper.
smallnest/goclaw
Scrapes sites, handles JavaScript-heavy pages and extracts structured data with Crawl4AI, through its crwl CLI or Python SDK, including schema-based extraction without an LLM.
yusukebe/ax
Use the ax CLI instead of curl + throwaway parsing scripts whenever you fetch a URL, explore an unknown web page, or extract structured data from HTML.
Mahanaicoach/google-maps-scraper-kit
Scrape Google Maps business listings (name, address, phone, website, rating, reviews, lat/lng, hours, emails) via the local gosom google-maps-scraper REST API.
cortega26/chile-hub
Execute Python code in a safe sandboxed environment via [inference.sh](https://inference.sh).
Cedriccmh/claude-code-skill-scrapling
使用 scrapling 进行网页抓取和数据提取。根据目标网站特征自动选择最佳 Fetcher, 生成并执行 Python 脚本完成任务。Use when: (1) 抓取/爬取网页内容或数据(scrape, crawl, fetch page, extract data) (2) 需要绕过 Cloudflare/WAF 等反爬保护 (3) 登录后抓取受保护页面 (4) 解析已有 HTML…
Categories
Scrape video stats from the Douyin creator center (creator.douyin.com) using AppleScript to automate the user's logged-in Chrome. Douyin Creator Scraping is an agent skill from TradingAi666/TzFilm-Douyin-Tool.com) using AppleScript to automate the user's logged-in Chrome.
Douyin Creator Scraping fits situations like: setting up periodic data collection; building a Douyin stats tracker; automating export of video performance data.
Run `npx skills add TradingAi666/TzFilm-Douyin-Tool --skill douyin-creator-scraping -a claude-code`. Or copy the skill folder (the TradingAi666/TzFilm-Douyin-Tool repository) into .claude/skills/douyin-creator-scraping in your project. Claude Code loads it when a task matches its description.
Run `npx skills add TradingAi666/TzFilm-Douyin-Tool --skill douyin-creator-scraping -a codex`. Or copy the skill folder (the TradingAi666/TzFilm-Douyin-Tool repository) into .agents/skills/douyin-creator-scraping in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add TradingAi666/TzFilm-Douyin-Tool --skill douyin-creator-scraping -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/douyin-creator-scraping, .gemini/skills/douyin-creator-scraping, .github/skills/douyin-creator-scraping and .opencode/skills/douyin-creator-scraping in your project.
Going by SKILL.md and its folder, Douyin Creator Scraping needs JavaScript for the scripts in its folder and the command-line tools its instructions call (osascript and python3). Our summary lists: Python 3; Node.js.
SKILL.md names 2 domains. In commands or code: creator.douyin.com; the agent is likely to contact it when it follows the instructions. As links in the text: github.com. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.
Douyin Creator Scraping is published under the MIT licence (from the LICENSE file in the skill folder). It allows redistribution, so the full SKILL.md is shown on this page.
About 7.2k tokens (SKILL.md is roughly 29k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Douyin Creator Scraping: Boss Zhipin Scraper (eatmoreduck/boss-zhipin-scraper, 1.5k stars), Crawl4AI Web Scraping (smallnest/goclaw, 599 stars), Ax (yusukebe/ax, 719 stars) and Google Maps Scraper (Mahanaicoach/google-maps-scraper-kit, 1.3k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
TradingAi666 (a GitHub user) maintains it in TradingAi666/TzFilm-Douyin-Tool, which has 406 GitHub stars. The repository was last updated on May 27, 2026.
Source: TradingAi666/TzFilm-Douyin-Tool on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.