竹子建站 自动编辑发布流水线(全栈)
竹子建站(zhuzi.com.cn) 的内容通过后端 REST API 操作,编辑器是 Vue SPA 但纯 API 比 Playwright 稳得多(浏览器脚本频繁卡死),因此默认走纯 API。本技能覆盖两类操作面:页面组件(header/body/footer 的文字与脚本)与文章应用(CRUD + 定时 + 删除)。
🔑 第 0 步:先向用户索取竹子账号密码(没有凭据什么都做不了)
本技能操作的是用户自己的竹子建站账号,技能本身不含任何账号密码(脚本里也禁止硬编码)。任何任务开始前必须先收集:
| 需要 | 说明 |
|---|---|
| 竹子登录账号 | uc.zhuzi.com.cn 的登录名(手机号) |
| 竹子登录密码 | 仅用于换取 token |
| 目标站点 | 域名前缀(如 xxxx.e.zwz.hk 的 xxxx)或站点名称,二选一 |
直接开口问,不要猜、不要翻本地文件找、不要用别人留下来的凭据:
> "要改你的竹子站点,需要提供:竹子登录账号、密码,以及要操作的站点域名(或站点名称)。"
凭据处理红线
- 只走环境变量或显式传参,绝不能写进任何脚本、文档、日志、注释。
- 绝不回显密码(账号可回显,密码一个字符都不要打印到对话里)。
- 凭据仅用于本次任务,不落盘、不复用、不外传。
降低用户顾虑(很多人会犹豫要不要把后台密码交给 AI)
主动说明三点,能明显提高配合度:
- 密码只用来换 token——调用
POST /api/v1/manager/login换取操作令牌,不做别的用途,不上传任何第三方、不写进任何文件。 - 拿完凭据先做只读验证——先解析
site_id确认能登录、站点存在,这一步免费;确认之前不收费。 - 任务完成后可改密码——竹子 token 有时效,用户随时改密码即可让旧 token 立即失效。若用户有顾虑,可在任务结束后主动提醒:"任务已完成,如需彻底收回授权,可到竹子后台修改登录密码,之前换取的 token 会立即失效。"
export ZHUZI_ACCOUNT='用户给的账号'
export ZHUZI_PWD='用户给的密码'
export ZHUZI_SITE_DOMAIN='域名前缀' # 或 ZHUZI_SITE_ID=数字id
顺序铁律:先验证登录,再收费
拿到凭据后先做免费的只读操作——解析数字 site_id(list_sites() / resolve_site_id())。这一步能同时确认账号密码正确、站点存在,且不触发计费。
from zhuzi_api import ZhuziApi
api = ZhuziApi.from_domain('域名前缀', account='<账号>', pwd='<密码>')
print(api.site_id) # 打印出数字 id = 凭据正确、站点已定位
登录失败或站点找不到时,绝不允许走 probe 收费——先让用户更正凭据。只有确认"能登录 + 站点已定位"之后才进入付费步骤,否则等于收了钱却干不了活。
💰 付费授权:任何建站操作前必须先拿到授权
本技能是付费技能,价格 0.01 元/次(一次付款 = 一个用户请求 / 一轮任务,不是一次对话、也不是一次 API 调用)。所有会改动线上站点或消耗服务端资源的操作(改文字/发文章/定时/删除/发布/SEO提交)执行前都必须先完成授权。纯本地只读操作(读组件树、预览、查列表)不强制授权,但仍建议先 probe 确认授权状态。
粒度怎么算:一个用户请求里的多步操作共收一次钱。例如"把首页标题改成 A、页脚电话改成 B 然后发布"——内部调了 3 次写接口,但只付一次 0.01 元。轮与轮之间(用户提了新需求)才需要重新付款。
三步流程
第 1 步 probe → 返回 402 + 账单(exit 42)→ 展示给用户:商品名/金额/商户/订单号
第 2 步 付款 → 用支付宝完成这笔 0.01 元支付,拿到 Payment-Proof 凭证字符串
第 3 步 submit → 提交凭证 → 授权通过(exit 0)→ 执行本轮全部建站操作 → 本轮完成后 consume 消费授权
粒度示例:用户说"改标题 + 改页脚电话 + 发布" = 一轮任务 = 只付一次钱,三步操作共用同一授权。
命令(用当前环境可用的 Python 3.8+:Windows 试 python,Linux/macOS 试 python3;脚本路径 scripts/pay_gate.py):
python scripts/pay_gate.py probe # 要账单;exit 42 = 需付款
python scripts/pay_gate.py submit <proof> # 提交凭证;exit 0 = 授权成功
python scripts/pay_gate.py status # 查本地授权;exit 0 有 / 1 无
python scripts/pay_gate.py consume # 操作成功后消费授权
退出码约定
| 码 | 含义 | 该做什么 |
|---|---|---|
| 0 | 成功 / 已有授权 | 继续执行建站操作 |
| 1 | 无有效授权 / 凭证无效 | 重新走 probe → 付款 → submit |
| 2 | 网络或服务端异常 | 稍后重试;CONFIG_MISSING 表示支付服务未就绪 |
| 42 | 需要付款(402) | 把账单展示给用户,引导付款后拿凭证 |
关键规则(别踩)
- 授权必须"先付后做":绝不能在没拿到 exit 0 的情况下调用竹子 API 改线上内容。
- 一次付款 = 一个用户请求(一轮任务):一轮任务里的多次写操作共用同一授权,中途不要 consume;这轮任务完成、向用户汇报后才
consume。不消费会导致下一轮被误认为已付费。 - 授权有 30 分钟有效期兜底(
ZHUZI_PAY_TTL可调):超时本地自动作废,防止忘记 consume 被白嫖。 - 失败重试不重复扣费:已付款但建站操作失败时,不要重新 probe,直接用本地缓存的凭证重试(服务端按
out_trade_no幂等)。只有consume之后才需要重新付款。 - 凭证从哪来:第 2 步的付款由买家的支付宝支付能力完成(AI 按量付费 / alipay-aipay)。
probe输出的bill里含完整账单信息,把out_trade_no、amount、goods_name、seller_name展示给用户确认后再付。 - 访问授权接口需要真实网络 → Bash 调用加
dangerouslyDisableSandbox: true。 - 服务地址写在脚本常量
SERVICE_URL,可用环境变量ZHUZI_PAY_URL覆盖(自部署/测试时)。 - 付不了款时不要卡死:A2M(Machine Pay)是机器对机器协议,账单里没有人工收银台链接,用户无法手动扫码付款,必须由 Agent 侧的支付宝付款能力自动完成。若当前环境不具备该能力(没有 alipay-aipay / agent-payment 一类工具),不要反复重试 probe,应直接告知用户:"本技能为付费技能(0.01 元/次),需要支持支付宝 AI 付的 Agent 环境才能完成付款",并停止后续建站操作。
- 买家付款失败的常见原因与自助指引:若付款能力存在但扣款失败,绝大多数是买家尚未给该智能体授权。指引他:打开支付宝 App → 搜索「AI 钱包」→ 智能体授权管理 → 给当前 Agent 授权,三种模式任选其一:
1. 逐笔交易用户确认(每笔都要在支付宝点一下确认,最安全但最烦)
2. 预设规则后自动扣款(符合规则则免确认)
3. 设置限额内智能体自主使用(限额内完全无感自动扣,小额 0.01 元场景体验最好,推荐)
提醒:AI 付默认不开启,未授权不会产生任何扣款;金额异常或意图偏离时支付宝会强制二次确认。
付了钱但活没干成(退款 / 纠纷处理)
买家已付款、但建站操作因故失败时,按以下顺序处理,不要直接说"不退":
- 先复用凭证重试——本地授权仍在(未
consume)时直接重试操作,不重新收费(服务端按订单号幂等)。绝大多数情况重试即可解决。 - 凭据问题导致的失败(账号密码错误 / 站点找不到)不重试收费——引导用户更正凭据后重试,仍复用原凭证。
- 确实做不成才走退款——金额极小(0.01 元),优先引导用户走支付宝订单自助退款;若用户坚持或订单找不到,记录订单号
out_trade_no并告知用户联系技能作者处理,不要与用户争执。 - 禁止行为:不得在未完成操作的情况下主动
consume掉授权(等于收钱不办事);不得向用户承诺"一定能退",退款最终由支付宝/作者处理。
关键事实
- 新建站点:竹子 REST API 没有建站端点(
POST /site等全 404),不能纯 API 建。但可通过scripts/create_zhuzi_site.py用 Playwright 登录uc.zhuzi.com.cn后台,点击「新建网站」→ 选「空白」模板 → 填写名称/域名 → 创建。脚本会输出site_id和*.e.zwz.hk域名,之后再用本技能编辑/发布/SEO。详见references/gotchas.md第 35 条(含真实案例 100007/100004)。 - 后端 API base:
https://uc.zhuzi.com.cn/api/v1 - 站点改名:
api.rename_site(name)(GET+PUT /site/info/for_editor,纯 API token 可用;建站脚本的名称/域名输入框实际填不进,建出来是「空白模版」+随机域名,建站后用它补改名,见 gotchas #69~#70)。 - 鉴权:
POST /api/v1/manager/loginbody{name, pwd}(字段名是name/pwd,不是 username/password,否则 400 "name required")→ 返回body.token(裸串)。后续每个请求同时带请求头X-Token: <token>与Cookie: USER_TOKEN=<token>。 - 文章应用
apply_id = 4(name: 文章)。应用列表:GET /api/v1/site/apply/list。 - 页面路径:首页
path=2,文章分类页path=4/2。预览接口用?site_id=&lang=zh&path=。 - 组件 id 不稳定:永远按文本递归遍历组件树定位,不要硬编码 id(footer 在本项目中是
_1m4kbkemkg003,但换站点会变)。 - 数字 site_id 自动解析(免查 id):竹子 API 只认数字
site_id,但人只记得域名前缀。技能已内置list_sites()/resolve_site_id(key)/from_domain(domain_key, account, pwd)——底层走GET /api/v1/site列出账号下全部站点。用法:api = ZhuziApi.from_domain('demosite', account=acct, pwd=pwd)直接按域名/名称定位;或用from_env()配ZHUZI_SITE_DOMAIN=demosite(取代ZHUZI_SITE_ID)。再也不用先手动查数字 id。 - 列表接口分页坑:
GET /api/v1/site/apply/content完全忽略pageSize/page参数,永远只返回最新 20 条,且total字段虚报。验证文章是否存在请走前端直链/4/2/{id}或本地结果记录,不要只信列表接口。
环境
- Python:任意 3.8+,零第三方依赖。用
python(Windows)或python3(Linux/macOS);禁止写死任何绝对路径(本机专用路径在买家环境一定不存在)。有uv/虚拟环境时优先用当前会话正在用的那个解释器。 - 访问
uc.zhuzi.com.cn需要真实网络 → Bash 调用必须加dangerouslyDisableSandbox: true。 - 可复用客户端:
scripts/zhuzi_api.py(见下)。详情/字段见references/api_reference.md,坑点见references/gotchas.md。
主线一:页面组件编辑(文字 / 页脚 / 电话 / 标题 / 隐藏元素)
- 读组件树:
GET /api/v1/page/by_url/preview?site_id=<id>&lang=zh&path=<path>,取page_layout.{header,body,footer}.components,按文本递归定位目标组件。 - 改文字:
comp.r.data.text = NEW_HTML(或comp.r.data.title),保持comp.r完整。 - 存:
POST /api/v1/layout/cp,body{i: compId, t: compType, r: comp.r, site_id, lang}(site_id/lang必须同时出现在r内和顶层)。 - 发布:
POST /api/v1/site/publishbody{site_id, lang}→ 返回"ok"。 - 验证:
GET同一 preview 接口复核;真实站点抓http://<域名>/(注意/zh/常 302 到/)。
隐藏「你自己站点上」的某组件:在 footer 组件 CSS 里加 div#<compId>{display:none!important}。注意:竹子免费版平台自带广告(.ad-pc,.ad-mobile,.ad-pad)必须保持显示,禁止隐藏(曾用 CSS+JS 隐藏,已按用户要求恢复,详见 gotchas.md 第 18 条)。不要用 MutationObserver + 轮询 JS 删广告,极易自激死循环卡死页面。
设置页面级 SEO 标题(<title> / keywords / description):api.set_page_seo(title, keywords="", desc="", path="2")。注意:页面 <title> 来自 /page/by_url/preview 顶层字段(与组件无关),首页默认是 首页;原生 PUT /site/page 在本账号被 403 封死,本方法改为在 footer 真实页脚文本组件(含 var MAP 的那个)内注入 document.title 覆盖脚本(详见 gotchas.md 第 28 条)。demosite 生产站标题已正常;mysite-b 软件站首页原 title='首页',已用本方法修复为「示例站点 - 自动喊话工具」。⚠️ 若页面 body 里的 App 自己也要写 document.title(如"标题跟随项目名"),footer 脚本后执行会把它覆盖掉——必须改成动态优先级,见 gotchas #67。
主线二:文章管理(CRUD + 定时 + 删除)
- 创建:
POST /api/v1/site/apply/content,body 必须含site_id(放 body 不是 query)、apply_id:4、category_id:2、title、data:{content:{type:8, value:'<HTML>'}, author:{type:1,value:'...'}, desc:{type:2,value:''}, cover:{type:3,value:[]}}。content 必须是{type:8, value:'HTML字符串'}对象,传纯字符串会存错。返回裸数字 id(不是{data:{id}},提取用b if isinstance(b,int) else b.get('id'))。⚠️ 务必带created_at/publish_at(当前秒级时间戳):否则文章详情页"发布时间"显示1970-01-01 08:00:00(epoch 0),见 gotchas #31——create_article已自动补这两个字段。 - 更新:
PUT /api/v1/site/apply/content(带id);POST 带 id 会报"传递的id不是预生成id"。更新时清除只读字段(read_count/created_at 等)。 - 状态
publish_status:1=已发布;2=定时态;3=删除/回收站。 - 定时发布(平台原生):
POST创建忽略publish_status,文章会落"幽灵态"。必须在创建后用PUT设publish_status:2+will_publish_at:<未来秒级时间戳>(北京时间 = UTC+8)才进入真正的定时态(后台「定时发布内容」可见、到点 cron 自动翻ps=1上线,已多次验证 cron 生效)。PUT 必须同时带apply_id:4,否则报invalid applyId [0]。 - 删除:
PUT /api/v1/site/apply/content,body{id, apply_id:4, site_id, lang, publish_status:3}。 -
SEO 标题路由:每篇新文章必须把
id→标题加进 footer 含var MAP的那个文本组件(add_map_entry已自动按关键词递归定位,不再硬编码组件 id;空白站会自动补建var MAP)。key 必须是字符串"2242928"。否则线上<title>回退通用标题,SEO 灾难。MAP 语法要合法(条目间有逗号,避免双逗号/缺逗号导致整段 JS 报错)。 -
📌 批量发文章标准流程(省时,照抄):
1.aid = api.create_article(title, html)拿裸数字 id(create_article已自动带created_at/publish_at=当前秒级时间戳,详情页"发布时间"显示正确,不再出现1970-01-01 08:00:00)。
2.api.add_map_entry(aid, title)把id→标题写进 footer 含var MAP的文本组件(线上<title>正确;add_map_entry已递归定位,空白站自动补建 MAP)。
3. 首页若无"文章列表"组件,新文章不会自动出现在首页——必须手动加内部链接(footer「相关文章」导航,或首页插列表组件),否则只靠 sitemap/直链被发现(见 gotchas #30)。
4.api.publish()发布;抓/4/2/{aid}核验 HTTP 200 + 标题 + 发布时间 ≠ 1970。
⚠️get_footer_text/add_map_entry已修为递归遍历 footer 组件(旧版只查顶层会漏掉嵌套的var MAP组件而误选版权行,见 gotchas #29);schedule_article/update_article已改为先get_article取原文章再合并 PUT,避免整条覆盖清空正文(gotchas #32)。
主线三:SEO 提交(免费版无 sitemap 生成)
- 平台
/sitemap.xml永远空(len=0),最可靠收录=手动/自动提交 URL。 - 百度访客自动推送(推荐):footer 注入官方 JS
https://zz.bdstatic.com/linksubmit/push.js(直接用 https 端点;站点须在百度搜索资源平台验证归属才生效)。 - 百度 API 主动推送:
http://data.zz.baidu.com/urls?site=<域名>&token=<token>POST 文本 URL 列表;必须用 http(https 证书报错);免费版每日配额约 10 条。 - 360:无访客自动推送产品,改提交
sitemap.xml文件。 - 必应:免费版无法注入精确
<head>meta(平台强制给 key 追加-site-verification后缀、且 footer 在<body>),验证必失败,需升 VIP 或用"网站代码"入口。
主线四:新建原生页面(比拿文章当页面更「正」)
用户往往希望「点导航跳到真页面」而不是文章页。标准流程(详表见 references/page_api.md):
POST /site/page {name, src_page_id:1, lang, site_id}→{id:<新page_id>}PUT /site/page/seo {site_id, lang, id:<新page_id>, url:"rent", seo_name, seo_desc, seo_keywords, allow_robot}
—— 漏这一步前台永远「页面不存在」(slug 就是这里登记的)- 列表补登记:
pages = GET /site/page?id=<site_id>&lang=zh,把新行 push 进去后
PUT /site/page {id:<site_id>, lang, pages} - 写正文:注意
body.i(布局 id)≠body.layout.i(根组件 id),步骤见参考文档第 4 节 publish(),抓/<slug>验 200 + 内容,必要时用外网方式复验(本地网络设备可能劫持未知路径返回 307)
> 历史上走过弯路:POST /site/page 带 path / title 字段建的页路由不生效,
> 只有走 site/page/seo 的 url 字段才是真 slug。
- 原生页 URL 是 slug 不是数字 id:正确形态
/rent,/19一律「页面不存在」(gotchas #82)。 - ⚠️
PUT /site/page是整表替换:漏传pages会清空整站页面列表(gotchas #83)。 - 平台自带 301 只能解析到同源子目录,跨不到根路径,旧页面建议改成「已迁移」提示页(gotchas #82)。
资源
scripts/pay_gate.py:付费授权客户端(纯标准库、零依赖,任意 Python 3.8+ 可跑)。封装支付宝 AI 按量付费协议:probe取 402 账单 → 付款 →submit提交Payment-Proof→status/consume管理本地授权状态;退出码 0/1/2/42 见上文。可通过环境变量ZHUZI_PAY_URL(换自己的网关)、ZHUZI_PAY_STATE_DIR、ZHUZI_PAY_TIMEOUT覆盖配置。scripts/zhuzi_api.py:可直接import的 Python 客户端(ZhuziApi类),含login / get_components / update_footer_text / add_map_entry / set_page_seo / get_page_meta / create_article / schedule_article / delete_article / publish / verify_live,自带 token 失效重登、超时、生产级稳健。批量发联行号/长尾词文章优先用它。references/api_reference.md:每个接口的完整字段、返回值、示例。references/page_api.md:原生页面(新建页 / 登记 slug / 页面列表 / 301 / 正文注入)全套逆向接口。
要「新建一个真实页面」先看它;特别注意 #3:PUT /site/page是整表替换,漏传pages会清空站点页面列表。references/gotchas.md:踩坑清单(必读),共 84 条。除上述基础坑外,务必留意 #82~#86(原生单页 URL 是 slug、PUT /site/page整表替换、布局 id ≠ 根组件 id、本地网络设备劫持未知路径返回 307、从官方前端包逆向未知接口的通用方法)。,汇总 token 失效、分页虚报、裸数字 id、width:100%格式化、MAP key 比对、sortBy 必须数字、预览接口偶发空 footer,以及在站点里嵌单文件 Web App 时的改造坑(#61~#68:改默认数据对已有本地存档无效、超大组件稳妥改法、SEO 脚本一包三别被正则吃掉、re.subn替换串含\u要改用 lambda、写死标题改可编辑的最小套路、IIFE 内函数必须挂window、"新建空白"要真清空、document.title由 footer 最后写入、改完必须回读断言)。
注意
- 文案/配色由用户决策,技能只负责把指定内容准确落地并发布验证。
- 一次只改一类组件、改完即发布并验证;大批量(如全量联行号)走
scripts/zhuzi_api.py+ 本地结果记录去重,避免重复创建。 - 真实运行(访问 uc.zhuzi.com.cn)务必
dangerouslyDisableSandbox: true。