clip family'sOfficial Account batch member: unlike soia-pkm-clip-wechat-article(single article, archive by pasting a URL), this skill is aimed at the batch scenario of "pulling my own official account's historical articles into the vault all at once". Only for archivingofficial accounts you manage yourself—not a general-purpose WeChat Official Account scraper.
Client-readable description
What this skill can do
Batch-archive articles already published on the WeChat Official Account that the user manages themselves into an Obsidian vault. Supports three routes: the official API, the WeChat Official Account backend interface, and logged-in session cookies, with deduplication by URL
| The client wants | The skill will do | The client can see |
|---|---|---|
| Complete the work covered by this skill | Read the user request, necessary context, and this skill's body workflow, and execute the minimal reliable steps | The client will see Obsidian/vault file changes, terminal logs, generated artifact paths, and a final receipt. |
| Missing dependencies, permissions, configuration, or keys | Stop actions that require external state, and clearly state what is missing | Install commands, application addresses, configuration paths, or questions that require client confirmation |
| Execution complete | Summarize successes, skips, failures, file changes, and verification results | A completion receipt that can be copied into a ticket/log |
How the client uses it
- State the goal in natural language and provide the necessary inputs: files, URLs, repo, workspace, proposal, vault, or platform account status.
- The Agent first determines whether this skill applies, then checks dependencies, configuration, permissions, and risky actions.
- For actions that can be dry-run or previewed, provide a preview first; when deletion, overwriting, sending, publishing, or writing remote state is involved, ask for client confirmation first.
- After execution, verify the actual output; do not substitute "looks successful" for evidence.
- The final reply must give the client a visible summary: what was done, log summary, file changes, issues, and next steps.
Dependencies and installation
Install this skill (single skill):
npx skills add soia-team/soia-open-pkm-clip-skills -g -a '*' -s soia-pkm-clip-wechat-account -y
Configuration conventions:
~/.config/soia-skills/soia-pkm-clip-wechat-account/config.yml
SOIA_PKM_CLIP_WECHAT_ACCOUNT_CONFIG_FILE=<custom-config-path>
# 兼容别名:$SOIA_PKM_CLIP_GZH_CONFIG_FILE(以及原有 $SOIA_PKM_CLIP_GZH_ENV_FILE)
- If this skill does not need private configuration, you may skip creating
config.yml。 - If an API key, cookie, session, provider home, or local path is needed, it may only go into private
config.yml, the process environment, or the provider's own login state; it must not be written into the repository, vault body, or logs. - Strong dependencies, optional dependencies, and third-party skill relationships must be based on the "dependencies / prerequisites / resources / boundaries" description later in this
SKILL.md; if it is not clearly stated, add the description first or ask the client—do not guess. - Third-party skills may only declare dependencies and installation methods; do not directly modify third-party skill files.
Logs and completion receipt
Every execution must let the client see the process and results. Minimum receipt format:
完成:<一句话说明本次完成了什么>。
日志摘要:
- started: <检查到的输入/配置/依赖,不打印秘密值>
- processed: <数量或范围>
- created/updated: <数量或路径>
- skipped/failed: <数量和原因>
文件变化:
- <绝对路径或“未改动文件”>
验证:
- <运行过的检查、命令或人工核对点>
问题与下一步:
- <缺 key / 缺依赖 / 需要客户确认 / 建议下一条命令;没有则写“无”>
Delivery order: batch archiving must first write all articles to disk, and only then output the above receipt uniformly; the reverse is not allowed;author rely on --account-name fallback,content_complete: false and other uncertain fields must be explicitly marked "unverified" in the receipt; do not fabricate.
How to choose among the three routes
| Route A · Official API | Route C · Official Account backend interface | Route B · Logged-in Cookie | |
|---|---|---|---|
| Coverage | Only articles sent via the **Drafts box "Publish"** | All history, including old articles sent manually via mass send | All history, including old articles sent manually via mass send |
| Account requirements | Requires a verified Service Account/Subscription Account | Any Official Account, as long as you can log in to the backend | Any Official Account, as long as you can log in to the backend |
| Credentials | AppID/AppSecret, valid long-term + IP whitelist | token/Cookie, expires in a few hours | key/pass_ticket/appmsg_token, expires in a few hours to a few days |
| Difficulty of obtaining credentials | One-time configuration (generated in the backend) | Capture 1 pair of values (token+Cookie) | Capture 3 tickets + Cookie, easy to miss one or mismatch the pair |
| Degree of officialness | Documented at developers.weixin.qq.com | Unofficial, community reverse engineering (source-level verification) | Unofficial, community reverse engineering, not found in official documentation |
| Script | scripts/fetch_api.py |
scripts/fetch_mp.py |
scripts/fetch_cookie.py |
Recommendation: Run Route A first to scout (setting up the credentials once is low-cost and can be reused long-term for incremental sync; most personal unverified accounts will directly hit the permission threshold, see below). To read all history (including old articles sent manually via mass send),prefer Route C—the credentials only require capturing one pair of token/Cookie, which is less error-prone than Route B's need to pair three tickets, and is the recommended route in this skill for reading your own account's historical articles. Route B is kept as a backup for when Route C fails (for example, if both interfaces are revised someday). All three routes only run on accounts you yourself can log in to/manage; accounts that have been run will, on landing, be url automatically deduplicated (articles already archived are skipped mutually).
Route C is further divided into two sub-paths; the script automatically chooses based on whether you pass --name/--fakeid :If not passed (default), it uses appmsgpublish, reading the account you are currently logged in to; if passed, --name/--fakeid only then does it use searchbiz+appmsg, reading the specified/someone else's account—see the Route C subsection below for the reason.
Pre-execution confirmation and authorization
Confirmation strategy
Before formally scraping the body text/writing files, first use --dry-run --limit 3 or a similar small sample to scout, and report to the client the Official Accounts, order of magnitude of article count, and time range expected to be processed this time; only after the client confirms the scope is correct should full scraping be released. Explicitly invoking this skill and passing --name/--limit and other parameters are only recommended inputs and do not constitute a reason to skip this confirmation—the only skip condition is that the client's current sentence explicitly says "scrape directly/no need to confirm"; after skipping, the scope assumptions used this time must be stated in the receipt.
Consent before use
Routes B / C go through the Official Account backendunofficial interfaces, which carries ToS risk. On first use of this skill, or when the version declared in this section changes, the following statement must first be displayed and the client's explicit consent obtained before continuing:
Some routes of this skill (Route B / Route C) use unofficial interfaces of the WeChat Official Account backend, which are not documented in official WeChat documentation and carry ToS risk; use only to archive accounts you manage yourself, and do not use them to scrape other people's Official Accounts. (Statement version v1)
- After the customer agrees, write
{accepted: true, acceptedAt: <ISO 时间戳>, disclaimerVersion: "v1"}intoconsent.jsonunder the private configuration directory (at the same level asconfig.yml:~/.config/soia-skills/soia-pkm-clip-wechat-account/consent.json)。 - Already exists and
disclaimerVersionmatches the current version (v1) → skip the display and continue directly, mentioning only once in the log "Terms of Use already confirmed (<acceptedAt>)". - Customer refuses → stop, do not perform any scraping actions.
- Route A (official API) uses interfaces documented in the official documentation and is not subject to this consent constraint.
Route A · Official API
Interface basis (verified 2026-07 against developers.weixin.qq.com + WeChat Open Community; field names have been written into the script comments)
| Interface | Method | Request | Key response fields | Basis |
|---|---|---|---|---|
| Get access_token | GET | /cgi-bin/token?grant_type=client_credential&appid=&secret= |
access_token, expires_in |
Official documentation(complete request/response examples obtained) |
| Get successful publish list | POST | /cgi-bin/freepublish/batchget?access_token=,body {offset,count,no_content} |
total_count,item_count,item[].article_id,item[].update_time,item[].content.news_item[].{title,author,digest,content,content_source_url,thumb_media_id,show_cover_pic,need_open_comment,only_fans_can_comment,url,is_deleted} |
Official documentation(the full example body could not be obtained at scrape time; field names have been cross-checked and confirmed against multiple independent posts in the WeChat Open Community——it is recommended to first use --dry-run --limit 3 for a real-world calibration test) |
| Get permanent material list | POST | /cgi-bin/material/batchget_material?access_token=,body {type:"news",offset,count}(count is officially marked as 1-20) |
total_count,item_count,item[].media_id,item[].update_time,item[].content.news_item[].{title,thumb_media_id,show_cover_pic,author,digest,content,url,content_source_url} |
Official documentation(complete request/response examples obtained) |
⚠️ Coverage limitations (must read first)
freepublish/batchgetOnly returns articles sent through the "publish" action in the drafts box. Content that only went through "mass send" but was never clicked as "publish", as well as legacy graphic messages from before the drafts box feature launched, currently haveno API at all that can retrieve them——this is not an implementation flaw of this skill, but a platform limitation cross-confirmed by multiple posts in the WeChat Open Community.material/batchget_material(type=news) returns permanent graphic materials; according to community feedback, once an article is officially "published" through the drafts box, it may disappear from this material list. The two interfaces are in acomplementary relationship, and even taking the union does not equal "all published history".- The documentation for "publish capability" related interfaces notes:As of 2025-07, the calling permissions for these interfaces will be revoked for personal entity accounts, unverified enterprise entity accounts, and accounts that do not support verification. Most personal subscription accounts will likely fail the entire route, and verified service accounts/subscription accounts are required (the same account threshold as
soia-pkm-publish-wechat-draft'sdraft/add). - Conclusion: Route A is better suited for "spot checks/verification/incremental sync of already published content"; do not expect it to be a "full backup". To read all history (including old articles manually mass-sent), prefer Route C, with Route B as a fallback.
Credentials and limitations
WECHAT_APP_ID/WECHAT_APP_SECRET: the acquisition path and IP whitelist configuration are the same assoia-pkm-publish-wechat-draftSKILL.md "How to obtain and configure a WeChat Official Account AppID / AppSecret" section, not repeated here——the two skills use the same set of Official Account development credentials.- Private configuration auto-detection:
$SOIA_PKM_CLIP_WECHAT_ACCOUNT_CONFIG_FILE(compatible alias$SOIA_PKM_CLIP_GZH_CONFIG_FILE, as well as the original$SOIA_PKM_CLIP_GZH_ENV_FILE)>~/.config/soia-skills/soia-pkm-clip-wechat-account/config.yml. The config file uses YAMLenv:mapping, see example inconfig.example.yml; keys don't go into the vault or into this open-source skill repo.
Usage
python3 scripts/fetch_api.py [--out <vault内相对目录>] [--limit N] [--dry-run] \
[--vault <path>] [--account-name <公众号显示名>] [--force] [--page-size 20]
--dry-run: only fetch the list and print it, no file writing—for the first run, strongly recommend dry-run first, verifytotal_count/item_countand whether the number of articles retrieved matches expectations (especially thefreepublish/batchgetfield has not been 100% verified against the official example body).--account-name: both interfaces don't return the official account nickname, used for filename/source info; if not provided, "公众号" is used as a placeholder in the filename.--force: articles already archived byurlare skipped by default; only with this will they be overwritten and rewritten.
Path C · Official account backend interface (recommended: read all history of your own account)
Reuse mp.weixin.qq.com backend(author logs into the backend, not the reader-side profile_ext) interface; like Path B, it can read all history (including old articles sent manually via mass messaging), but credentials only require capturing one pair of token/Cookie, which is less error-prone than Path B which requires pairing three tickets (key/pass_ticket/appmsg_token).
Path C internally has two sub-paths,scripts/fetch_mp.py automatically switches based on whether you pass --name/--fakeid :
Default (not passing --name/--fakeid) |
Passing --name or --fakeid |
|
|---|---|---|
| interface | appmsgpublish(author-side "Publish Records") |
searchbiz(name→fakeid) + appmsg?action=list_ex(fakeid→list) |
| Whose account is read | The account you're currently logged into, no need to specify name/fakeid | The account corresponding to the specified name/fakeid (can be another account you also manage) |
| Use cases | Most common case: just want to back up your own account | You're logged into the backend with some account, but want to read another account you also manage and whose name/fakeid you know |
| Verification status | Already tested: ran successfully on a real account on 2026-07-09,ret=0,total_count=152 |
source-code-level verification, no official documentation found, marked "pending user testing calibration" |
Why the default path doesn't use searchbiz:searchbiz : it does fuzzy search of official account list by name, designed for "finding someone else's account"; searching with your own official account's name often fails to find yourself (returns empty list), so it's not suitable as the default path for "I'm already logged in, I am this account".appmsgpublish directly reads the publish records of the account corresponding to the current login state, so this problem doesn't exist.
Interface basis (source-code-level verification + appmsgpublish already tested)
| Interface | Method | Request | Response key fields | Basis |
|---|---|---|---|---|
| appmsgpublish (default, read own account) | GET | /cgi-bin/appmsgpublish,params sub=list&begin=<0,20,40,...>&count=20&type=101_1&free_publish_type=1&sub_action=list_ex&token=&lang=zh_CN&f=json&ajax=1 |
base_resp.ret;publish_page(JSON string,json.loads followed by {total_count, publish_list[]}); each publish_list[].publish_info(is another layer of JSON string),json.loads followed by {appmsgex[]}, each appmsgex[] contains title/link/update_time/digest |
cv-cat/WechatOAApis — utils/wx_utils.py source-code-level verification,and verified on a real account by the user on 2026-07-09 |
| searchbiz (name → fakeid) | GET | /cgi-bin/searchbiz,params action=search_biz&begin=0&count=5&query=<公众号名>&token=&lang=zh_CN&f=json&ajax=1 |
base_resp.ret,list[].{alias,fakeid,nickname,round_head_img,service_type} |
wnma3mz/wechat_articles_spider — ArticlesUrls.py::official_info() and cv-cat/WechatOAApis — utils/wx_utils.py::get_fakeid_params() two independent repositories have completely identical parameters |
| appmsg (fakeid → article list) | GET | /cgi-bin/appmsg,params action=list_ex&begin=<0,5,10,...>&count=5&fakeid=<fakeid>&type=9&query=&token=&lang=zh_CN&f=json&ajax=1 |
base_resp.ret,app_msg_cnt,app_msg_list[].{aid,appmsgid,cover,digest,itemidx,link,title,update_time}(does not include the author field) |
wnma3mz/wechat_articles_spider — ArticlesUrls.py::__get_articles_data() |
appmsgpublish and appmsg?action=list_ex are not the same interface; the response structure and pagination step are different—they should be interfaces called by the old and new frontend versions of the MP backend respectively, and the script has two independent implementations (list_own_account() vs fetch_article_list()), which cannot be mixed.
⚠️ Limitations and Risks (Must Read First)
-
Unofficial interface: Like Route B, it has no official documentation backing it and may be adjusted at any time.
appmsgpublishAlthough it has been verified by actual testing, it is likewise not an interface included in official documentation. -
Only for archiving official accounts that you manage yourself:
--nameAfter a match, it will print nickname/fakeid and prompt for confirmation. If it is not your own account, interrupt with Ctrl+C; the default path reads the currently logged-in account itself, so there is no risk of mistaking the account. -
token/Cookie will expire: Community reports say it usually expires within a few hours, with no refresh mechanism. After expiration,
base_resp.retwill return non-zero (the script already gives targeted prompts according to the table below; both subpaths share the same set of prompts, cross-checked by community reverse engineering, no official documentation found, and it still falls under "pending user actual-test calibration"):retMeaning Handling 200003invalid session token/cookie has expired; log in to the backend again, capture packets, and replace them 200013freq control Rate limiting has been triggered; community reports usually require waiting a long time, so do not rerun immediately 200040invalid csrf token The token and Cookie are suspected to have not been captured from the same login session -
Neither subpath list interface returns the author field: At implementation time,
authorfalls back to--account-name/WECHAT_ACCOUNT_NAME, which is not guaranteed to equal the article's real byline author. If you need the exact author, manually check the original article page. -
Rate-limiting risk: By default, the script requests at an interval of
--sleep 3seconds per page/article; community reports say that even with 5 seconds/page throttling, scraping nearly a thousand articles may still triggerfreq control. For batch scraping a full year of history, it is recommended to use--limitto run in batches.
Credential acquisition
Log in to the mp.weixin.qq.com backend, open any article edit page or article list page, press F12 to open the browser developer tools' "Network" panel, and fromthe address bar URL copy the token parameter, and from the request headers copy the complete Cookie string. Fill it into the private config.example.yml according to config.yml 's env.WECHAT_MP_TOKEN / env.WECHAT_MP_COOKIE。
⚠️ Note token should be the one in the address bar URL (the numeric string),notthe ticket in the "Network" panel whose request parameter name is appmsg_token —the two are different tickets,appmsgpublish recognizes the address bar token。
Usage
# 默认:不传 --name/--fakeid,读你自己当前登录的账号(appmsgpublish,推荐)
python3 scripts/fetch_mp.py [--out <目录>] [--limit N] [--dry-run] \
[--vault <path>] [--account-name <显示名>] [--force] [--sleep 3] [--page-size 20]
# 传 --name 或 --fakeid:读指定/别人的号(searchbiz + appmsg?action=list_ex)
python3 scripts/fetch_mp.py --name <公众号名> [--out <目录>] [--limit N] [--dry-run] \
[--vault <path>] [--account-name <显示名>] [--force] [--sleep 3] [--page-size 5]
- Do not pass
--name/--fakeid(default, recommended): read your own currently logged-in account, useappmsgpublish,--page-size, default 20. - Pass
--nameand--fakeid(choose one of the two): usesearchbiz+appmsg?action=list_ex,--page-size, default 5;--namewill first runsearchbizsearch and take the first matching result; whenfakeidis already known, pass--fakeiddirectly to skip the search step, which is more reliable. --dry-run: only page through the list and print titles + links,does not scrape the body or write files——Faster than Route B's dry-run (Route B's dry-run still fetches all article bodies); for the first run, it's recommended to do a dry-run first to verify whether the article count and titles match expectations.--page-sizeWhen not passed, the default value is automatically taken according to the sub-path being used (20 for your own account / 5 for a specified account); both are stable values that have been verified/community-tested, so don't increase them.- After each list item gets
link, the script directly GETs the article's public page and parses#js_content(the same body-fetching approach as Route B /soia-pkm-clip-wechat-articlesingle-article archiving, no extra cookie needed).
Route B · Logged-in Cookie
Interface basis (unofficial, community reverse-engineered, cross-checked)
GET https://mp.weixin.qq.com/mp/profile_ext
?action=getmsg&__biz=<biz>&f=json&offset=<offset>&count=10&is_ok=1
&scene=124&uin=<uin,通常固定777>&key=<key>&pass_ticket=<pass_ticket>
&wxtoken=&appmsg_token=<appmsg_token>&x5=0
Header: Cookie: <登录态 Cookie>
响应:{"ret":0,"errmsg":"ok","msg_count":N,"can_msg_continue":0|1,
"general_msg_list":"<JSON 字符串,需二次 json.loads>","next_offset":N,...}
general_msg_list 解析后:
{"list":[{"comm_msg_info":{"datetime":<unix ts>},
"app_msg_ext_info":{"title","author","content_url","is_multi",
"multi_app_msg_item_list":[同字段的合集子项]}}]}
Basis sources: cross-checked reverse-engineering records of the same interface from multiple independent technical blogs (Cnblogs/CSDN/Zhihu, 2019-2024); the field names are consistent with each other, butnone are official documentation, and developers.weixin.qq.com does not include this interface. The script has been implemented accordingly and this is written into the code comments, but it is still marked as "pending user real-world calibration" — on the first run, be sure to --dry-run --limit 3。
Note: the original requirements given to the user included a reference implementation link (
zjp1997720/wechat-article-search); in actual testing, that project actually calls Sogou WeChat Search (weixin.sogou.com), and does not use cookies to scrapeprofile_exttechnical route, which does not match the goal of "reading all history", so Route B did not copy it and instead switched to the aboveprofile_extsolution that has been independently reproduced by multiple parties.
⚠️ Limitations and risks (be sure to read first)
- Unofficial interface: there is no official documentation backing it, and WeChat may adjust the implementation at any time and make it stop working; if problems occur, there is no official support channel.
- Only for archiving official accounts that you manage yourself: multiple reference materials all note that this kind of reverse-engineered call is "recommended only for learning and research purposes"; scraping the historical articles of others' official accounts may violate WeChat's Terms of Service. This script is designed as self-service backup for your own account; do not use it to scrape other people's accounts.
- The ticket will expire:
key/pass_ticket/appmsg_tokenis a session ticket; community reports range from a few hours to a few days; there is no refresh mechanism, so after it expires and errors occur, you must log in again, capture packets, and replace it. - Rate-limiting risk: by default, the script waits
--sleep 1.5seconds between each request; don't make it too fast; for batch scraping a full year of history, it is recommended to run in batches (with--limit), and don't pull hundreds of articles all at once.
Credential acquisition
Log in to the mp.weixin.qq.com backend, open any historical article of that official account (or profile_ext?action=home), find same-domain requests in the browser developer tools' "Network" panel, copy __biz/key/pass_ticket/appmsg_tokenfrom the request URL, and copy the complete Cookie string from the request headers. Fill it into the private config.example.yml according to config.yml。
Usage
python3 scripts/fetch_cookie.py --biz <__biz> [--out <目录>] [--limit N] [--dry-run] \
[--vault <path>] [--account-name <显示名>] [--force] [--sleep 1.5]
- The list interface has 10 items per page (the community-tested upper limit); the script automatically paginates by
can_msg_continue/next_offset. - After each list item gets
content_url, the script directly GETs the article's public page and parses#js_content(the same body-fetching approach assoia-pkm-clip-wechat-articlesingle-article archiving, no extra cookie needed). --dry-runIt will still page through the entire list, fetch all article bodies, and then print (without writing files); when the volume is large, it takes a long time — if you want to quickly verify the list itself, first look at the per-page printed output in stderr拿到 N 条,累计 M。
Implementation specification (shared by all three routes)
- Transit area, not the destination: what lands in the vaultInbox layer, not the
soia-pkm-organize-article-mocfinal location after being filed back—organizestockpile it here before launch, then file it back to the 40/50 zones together with the "Works Library Restructuring". - Default output directory:
Inbox/gzh-articles/<年>/(a relative path within the vault, the generic default in the code). This vault is recommended to be set as your own staging area, for example<vault-inbox-dir>/gzh-articles/—use--out <vault-relative-dir>or theenv.OBSIDIAN_GZH_OUTin private config to override (the default deliberately does not hardcode specific Chinese paths, complying with this repository's SKILL_SPEC.md "no hardcoded personal paths" rule). - Filename:
<出版日期>-公众号-<账号显示名>-<标题>.md, and when a name conflict occurs, add a short suffix based on URL features. - frontmatter:
tags:[公众号原创, 待归位]、source: 公众号自有、url、title、author、published_at、captured_at、route: api|cookie|mp-backend、content_complete、topics: [](topicsis one item this skill adds beyond the user's original field list, to facilitate subsequentorganizedirectly filling in the category, leaving it as an empty array when there is no content, without affecting other fields). - Body: HTML → Markdown goes through stdlib
html.parser(zero third-party dependencies, consistent with the repository's other scripts), and 100% layout fidelity is not guaranteed; complex inline styles/official-account-specific components will be flattened into plain text + image links.content_complete: falseindicates that body extraction failed or is empty, and the original link needs to be manually verified. - Idempotent: all three scripts recursively search under
urlbyout_dirto see whether it has already been archived, and if it already exists, they skip by default (--forceonly then overwrites), so running the three paths alternately will not land the same article twice.
After archiving
following the clip family conventions, AI can subsequently:
- Add a "## Summary" (30-80 characters) to each article.
- Determine/backfill frontmatter
topics。 - after the works library restructuring is settled, hand it to
organizeto batch file back fromInbox/gzh-articles.
Closed-loop position
★clip-gzh(批量收) → organize(归位/分类) → distill(收藏→观点) → compose(观点→文) → publish(发)
and soia-pkm-clip-wechat-article(single-article archiving) are complementary: clip-wechat handles "seeing a forwarded link and saving an article on the fly"; clip-gzh handles "batch migrating the historical backlog from my own official account". The two are not completely consistent in their landing conventions (clip-gzh has extra route/content_complete fields), so they are not merged for now, and whether to converge them into one set will be evaluated together when the works library is restructured.