🔄 卡若AI 同步 2026-03-10 13:48 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个

This commit is contained in:
2026-03-10 13:48:48 +08:00
parent 6f914412d0
commit a8822b3a21
14 changed files with 632 additions and 289 deletions

View File

@@ -1,12 +1,11 @@
#!/usr/bin/env python3
"""
B站视频发布 - Playwright 自动化(可见浏览器)
B站反自动化较强采用可见浏览器模式
- 自动上传、填写标题/分区/标签、点击投稿
- 用户无需操作,但浏览器窗口可见
- 首次可能需过极验验证码(一次后不再出现)
B站视频发布 — 纯 API 优先 + Playwright 兜底
方案一bilibili-api-python 纯 API无需浏览器
方案二Playwright 可见浏览器API 失败时自动降级)
"""
import asyncio
import json
import sys
import time
from pathlib import Path
@@ -16,10 +15,8 @@ COOKIE_FILE = SCRIPT_DIR / "bilibili_storage_state.json"
VIDEO_DIR = Path("/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片")
sys.path.insert(0, str(SCRIPT_DIR.parent.parent / "多平台分发" / "脚本"))
from cookie_manager import CookieManager
from video_utils import extract_cover
from publish_result import PublishResult
UPLOAD_URL = "https://member.bilibili.com/platform/upload/video/frame"
UA = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
@@ -59,185 +56,249 @@ TITLES = {
}
async def publish_one(video_path: str, title: str, idx: int = 1, total: int = 1) -> bool:
"""用可见浏览器自动化发布单条视频"""
def _load_credential():
"""从 storage_state.json 提取 B站凭证"""
from bilibili_api import Credential
with open(COOKIE_FILE, "r") as f:
data = json.load(f)
cookies = {c["name"]: c["value"] for c in data.get("cookies", [])
if ".bilibili.com" in c.get("domain", "")}
return Credential(
sessdata=cookies.get("SESSDATA", ""),
bili_jct=cookies.get("bili_jct", ""),
buvid3=cookies.get("buvid3", ""),
dedeuserid=cookies.get("DedeUserID", ""),
)
async def _api_publish(video_path: str, title: str) -> PublishResult:
"""方案一bilibili-api-python 纯 API"""
from bilibili_api import video_uploader
from video_utils import extract_cover
t0 = time.time()
credential = _load_credential()
cover_path = extract_cover(video_path)
print(f" [API] 封面已提取: {cover_path}", flush=True)
tags = "Soul派对,创业,认知觉醒,副业,商业思维"
meta = {
"copyright": 1,
"source": "",
"desc": title,
"desc_format_id": 0,
"dynamic": "",
"interactive": 0,
"open_elec": 0,
"no_reprint": 1,
"subtitles": {"lan": "", "open": 0},
"tag": tags,
"tid": 160, # 生活 > 日常
"title": title[:80],
"up_close_danmaku": False,
"up_close_reply": False,
}
page = video_uploader.VideoUploaderPage(
path=video_path,
title=title[:80],
description=title,
)
uploader = video_uploader.VideoUploader(
pages=[page],
meta=meta,
credential=credential,
cover=cover_path if cover_path else None,
)
last_event = {}
@uploader.on("__ALL__")
async def _on_event(data):
nonlocal last_event
last_event = data
ev = data.get("event", "")
if ev == "PRE_PAGE":
print(" [API] 开始上传...", flush=True)
elif ev == "PREUPLOAD_DONE":
print(" [API] 预上传完成", flush=True)
elif ev == "PRE_COVER":
print(" [API] 上传封面...", flush=True)
elif ev == "SUBMIT_DONE":
print(" [API] 投稿提交完成!", flush=True)
await uploader.start()
elapsed = time.time() - t0
return PublishResult(
platform="B站",
video_path=video_path,
title=title,
success=True,
status="reviewing",
message=f"纯API投稿成功 ({elapsed:.1f}s)",
elapsed_sec=elapsed,
)
async def _playwright_publish(video_path: str, title: str) -> PublishResult:
"""方案二Playwright 可见浏览器(兜底)"""
from playwright.async_api import async_playwright
fname = Path(video_path).name
fsize = Path(video_path).stat().st_size
t0 = time.time()
print(f"\n{'='*60}")
print(f" [{idx}/{total}] {fname}")
print(f" 大小: {fsize/1024/1024:.1f}MB")
print(f" 标题: {title[:60]}")
print(f"{'='*60}")
async with async_playwright() as pw:
browser = await pw.chromium.launch(
headless=False,
args=["--disable-blink-features=AutomationControlled"],
)
ctx = await browser.new_context(
storage_state=str(COOKIE_FILE), user_agent=UA,
viewport={"width": 1280, "height": 900}, locale="zh-CN",
)
await ctx.add_init_script(
"Object.defineProperty(navigator,'webdriver',{get:()=>undefined})"
)
page = await ctx.new_page()
if not COOKIE_FILE.exists():
print(" [✗] Cookie 不存在,请先运行 bilibili_login.py")
return False
await page.goto(
"https://member.bilibili.com/platform/upload/video/frame",
timeout=30000, wait_until="domcontentloaded",
)
await asyncio.sleep(3)
try:
async with async_playwright() as pw:
browser = await pw.chromium.launch(
headless=False,
args=["--disable-blink-features=AutomationControlled"],
fl = page.locator('input[type="file"]').first
if await fl.count() == 0:
await browser.close()
return PublishResult(
platform="B站", video_path=video_path, title=title,
success=False, status="failed",
message="Playwright: 未找到上传控件",
elapsed_sec=time.time() - t0,
)
context = await browser.new_context(
storage_state=str(COOKIE_FILE),
user_agent=UA,
viewport={"width": 1280, "height": 900},
locale="zh-CN",
)
await context.add_init_script(
"Object.defineProperty(navigator,'webdriver',{get:()=>undefined})"
)
page = await context.new_page()
print(" [1] 打开上传页...")
await page.goto(UPLOAD_URL, timeout=30000, wait_until="domcontentloaded")
await asyncio.sleep(3)
print(" [2] 上传视频...")
file_input = await page.query_selector('input[type="file"]')
if not file_input:
for inp in await page.query_selector_all("input"):
if "file" in (await inp.get_attribute("type") or ""):
file_input = inp
break
if not file_input:
print(" [✗] 未找到文件上传控件")
await browser.close()
return False
await file_input.set_input_files(video_path)
print(" [2] 文件已选择,等待上传完成...")
# 等待上传完成(查找进度条或"重新上传"按钮)
for i in range(120):
try:
page_text = await page.inner_text("body")
if "重新上传" in page_text or "上传完成" in page_text:
print(f" [2] 上传完成 (等待 {i*2}s)")
break
# 检查进度百分比
progress = await page.evaluate("""() => {
const el = document.querySelector('.progress-bar, [class*="progress"]');
if (el) return el.style.width || el.getAttribute('aria-valuenow') || '';
return '';
}""")
if progress and ("100" in str(progress)):
print(f" [2] 上传 100%")
break
except Exception:
pass
await asyncio.sleep(2)
await fl.set_input_files(video_path)
for i in range(120):
txt = await page.evaluate("document.body.innerText")
if "重新上传" in txt or "上传完成" in txt:
break
await asyncio.sleep(2)
# 填写标题
print(" [3] 填写标题...")
title_input = page.locator('input[maxlength="80"]').first
if await title_input.count() > 0:
await title_input.click()
await title_input.fill("")
await title_input.fill(title[:80])
await asyncio.sleep(0.5)
await asyncio.sleep(2)
# 选择"自制"
print(" [3b] 选择类型:自制...")
try:
original = page.locator('label:has-text("自制"), span:has-text("自制")').first
if await original.count() > 0:
await original.click()
except Exception:
pass
await asyncio.sleep(0.5)
title_input = page.locator('input[maxlength="80"]').first
if await title_input.count() > 0:
await title_input.click()
await title_input.fill(title[:80])
# 选择分区
print(" [3c] 选择分区:生活 > 日常...")
try:
cat_dropdown = page.locator('text=请选择分区').first
if await cat_dropdown.count() > 0:
await cat_dropdown.click()
await asyncio.sleep(1)
try:
original = page.locator('label:has-text("自制"), span:has-text("自制")').first
if await original.count() > 0:
await original.click()
except Exception:
pass
life_cat = page.locator('.drop-cascader-list .drop-cascader-item:has-text("生活")').first
if await life_cat.count() > 0:
await life_cat.click()
await asyncio.sleep(0.5)
else:
life_cat2 = page.locator('li:has-text("生活")').first
if await life_cat2.count() > 0:
await life_cat2.click()
await asyncio.sleep(0.5)
try:
cat_dd = page.locator('text=请选择分区').first
if await cat_dd.count() > 0:
await cat_dd.click()
await asyncio.sleep(1)
life = page.locator('.drop-cascader-item:has-text("生活")').first
if await life.count() > 0:
await life.click()
await asyncio.sleep(0.5)
daily = page.locator('span:has-text("日常"), li:has-text("日常")').first
if await daily.count() > 0:
await daily.click()
except Exception:
pass
daily_cat = page.locator('span:has-text("日常"), li:has-text("日常")').first
if await daily_cat.count() > 0:
await daily_cat.click()
await asyncio.sleep(0.5)
except Exception as e:
print(f" [⚠] 分区选择异常: {e}")
await asyncio.sleep(0.5)
try:
tag_input = page.locator('input[placeholder*="标签"]').first
if await tag_input.count() > 0:
for tag in ["Soul派对", "创业", "认知觉醒"]:
await tag_input.fill(tag)
await tag_input.press("Enter")
await asyncio.sleep(0.3)
except Exception:
pass
# 填写标签
print(" [3d] 填写标签...")
try:
tag_input = page.locator('input[placeholder*="Enter"], input[placeholder*="标签"]').first
if await tag_input.count() > 0:
await tag_input.click()
tags = ["Soul派对", "创业", "认知觉醒", "副业", "商业思维"]
for tag in tags[:5]:
await tag_input.fill(tag)
await tag_input.press("Enter")
await asyncio.sleep(0.3)
except Exception:
pass
await page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
await asyncio.sleep(1)
# 滚动到底部
await page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
await asyncio.sleep(1)
submit = page.locator('button:has-text("立即投稿")').first
if await submit.count() > 0:
await submit.click()
else:
await page.evaluate("""() => {
const b = [...document.querySelectorAll('button')].find(e => e.textContent.includes('立即投稿'));
if (b) b.click();
}""")
# 点击立即投稿
print(" [4] 点击立即投稿...")
submit_btn = page.locator('button:has-text("立即投稿")').first
if await submit_btn.count() > 0:
await submit_btn.click()
else:
await page.evaluate("""() => {
const btns = [...document.querySelectorAll('button')];
const pub = btns.find(e => e.textContent.includes('立即投稿'));
if (pub) pub.click();
}""")
for i in range(30):
await asyncio.sleep(2)
txt = await page.evaluate("document.body.innerText")
url = page.url
if "投稿成功" in txt or "稿件投递" in txt or "list" in url:
await ctx.storage_state(path=str(COOKIE_FILE))
await browser.close()
return PublishResult(
platform="B站", video_path=video_path, title=title,
success=True, status="reviewing",
message=f"Playwright投稿成功 ({time.time()-t0:.1f}s)",
elapsed_sec=time.time() - t0,
)
# 等待结果
for i in range(30):
await asyncio.sleep(2)
page_text = await page.inner_text("body")
current_url = page.url
if "投稿成功" in page_text or "稿件投递" in page_text:
print(" [✓] 投稿成功!")
await context.storage_state(path=str(COOKIE_FILE))
await browser.close()
return True
if "video/upload" in current_url or "list" in current_url:
print(" [✓] 已跳转到稿件列表(投稿成功)")
await context.storage_state(path=str(COOKIE_FILE))
await browser.close()
return True
if "自动提交" in page_text:
print(f" [⚠] 等待自动提交 ({i*2}s)...")
continue
await page.screenshot(path="/tmp/bilibili_result.png")
await ctx.storage_state(path=str(COOKIE_FILE))
await browser.close()
return PublishResult(
platform="B站", video_path=video_path, title=title,
success=False, status="failed",
message="Playwright: 投稿超时",
screenshot="/tmp/bilibili_result.png",
elapsed_sec=time.time() - t0,
)
print(" [⚠] 超时,请手动确认投稿状态")
await context.storage_state(path=str(COOKIE_FILE))
await browser.close()
return True
async def publish_one(video_path: str, title: str, idx: int = 1, total: int = 1) -> PublishResult:
"""API 优先 → Playwright 兜底"""
fname = Path(video_path).name
fsize = Path(video_path).stat().st_size
print(f"\n[{idx}/{total}] {fname} ({fsize/1024/1024:.1f}MB)", flush=True)
print(f" 标题: {title[:60]}", flush=True)
if not COOKIE_FILE.exists():
return PublishResult(
platform="B站", video_path=video_path, title=title,
success=False, status="error", message="Cookie 不存在",
)
# 方案一:纯 API
print(" [方案一] bilibili-api-python 纯 API...", flush=True)
try:
result = await _api_publish(video_path, title)
print(f" {result.log_line()}", flush=True)
return result
except Exception as e:
print(f" [✗] 异常: {e}")
import traceback
traceback.print_exc()
return False
err_msg = str(e)[:100]
print(f" [方案一失败] {err_msg}", flush=True)
# 方案二Playwright 兜底
print(" [方案二] 降级到 Playwright 可见浏览器...", flush=True)
try:
result = await _playwright_publish(video_path, title)
print(f" {result.log_line()}", flush=True)
return result
except Exception as e:
return PublishResult(
platform="B站", video_path=video_path, title=title,
success=False, status="error",
message=f"双方案均失败: {str(e)[:80]}",
)
async def main():
@@ -245,46 +306,25 @@ async def main():
print("[✗] Cookie 不存在,请先运行 bilibili_login.py")
return 1
cm = CookieManager(COOKIE_FILE, "bilibili.com")
expiry = cm.check_expiry()
print(f"[i] Cookie 状态: {expiry['message']}")
import httpx
async with httpx.AsyncClient(timeout=10.0) as client:
resp = await client.get(
"https://api.bilibili.com/x/web-interface/nav",
headers={"Cookie": cm.cookie_str, "User-Agent": UA},
)
data = resp.json()
if data.get("code") == 0:
print(f"[i] 已登录: {data['data'].get('uname')} (uid={data['data'].get('mid')})\n")
else:
print("[✗] Cookie 已失效,请重新运行 bilibili_login.py")
return 1
videos = sorted(VIDEO_DIR.glob("*.mp4"))
if not videos:
print("[✗] 未找到视频")
return 1
print(f"[i] {len(videos)} 条视频\n")
print(f"{len(videos)} 条视频\n")
from publish_result import print_summary, save_results
results = []
for i, vp in enumerate(videos):
title = TITLES.get(vp.name, f"{vp.stem} #Soul派对 #创业日记")
ok = await publish_one(str(vp), title, i + 1, len(videos))
results.append((vp.name, ok))
t = TITLES.get(vp.name, f"{vp.stem} #Soul派对 #创业日记")
r = await publish_one(str(vp), t, i + 1, len(videos))
results.append(r)
if i < len(videos) - 1:
print(f"\n 等待 8 秒后继续...")
await asyncio.sleep(8)
print(f"\n{'='*60}")
print(" B站发布汇总")
print(f"{'='*60}")
for name, ok in results:
print(f" [{'' if ok else ''}] {name}")
success = sum(1 for _, ok in results if ok)
print(f"\n 成功: {success}/{len(results)}")
return 0 if success == len(results) else 1
print_summary(results)
save_results(results)
ok = sum(1 for r in results if r.success)
return 0 if ok == len(results) else 1
if __name__ == "__main__":

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,199 @@
# 多平台视频分发系统 — 优化迭代总结
> 更新时间: 2026-03-10
---
## 一、五平台最终技术方案
| 平台 | 方案一(优先) | 方案二(兜底) | 单条耗时 | 状态 |
|------|-------------|-------------|---------|------|
| **B站** | bilibili-api-python 纯 API | Playwright 可见浏览器 | ~8s | 已验证 |
| **抖音** | douyin_pure_api 纯 APIVOD分片 | — | ~15s | 已验证 |
| **快手** | Playwright Headless | — | ~20s | 已验证 |
| **视频号** | Playwright Headless | — | ~20s | 已验证 |
| **小红书** | Playwright Headless | — | ~25s | 已验证 |
### 方案选择逻辑
- **纯 API 可行 → 优先用 API**B站bilibili-api-python 成熟稳定、抖音VOD 分片上传 + bd-ticket-guard
- **API 不可行 → Playwright Headless**快手__NS_sig3 签名过于复杂)、视频号(无公开 API、小红书X-S/X-T 签名频繁变动)
- **B站特殊**:纯 API 优先,失败自动降级到可见浏览器
---
## 二、各平台遇到的问题与解决方案
### B站
| 问题 | 原因 | 解决方案 |
|------|------|---------|
| 纯 API 403 Forbidden | WAF/反爬 | 放弃手动逆向,改用 bilibili-api-python 库 |
| Playwright GeeTest 验证码 | headless 触发极验 | 改用可见浏览器模式headless=False |
| bilibili-api-python 406 | 早期版本 bug | 升级到最新版,解决 preupload 问题 |
| VideoUploader cover 空路径报错 | cover="" 触发文件读取 | 用 ffmpeg 提取第一帧作为 cover 传入 |
| **最终方案** | — | **纯 API 7.9s 完成投稿**(之前 80s+ |
### 快手
| 问题 | 原因 | 解决方案 |
|------|------|---------|
| "发布"按钮不是 `<button>` | 自定义 div 实现 | 用 `div[class*="button-primary"]` 选择器 |
| 发布按钮在页面底部不可见 | y=1287px 超出 viewport | `scroll_into_view_if_needed()` + `scrollTo(bottom)` |
| 草稿对话框阻挡 | "上次未发布的视频" | 检测并自动点击"放弃" |
| 未登录storage_state 无效) | login 脚本检测不准 | 修复 login 检测:等待 cp.kuaishou.com 域 cookie |
| __NS_sig3 签名太复杂 | webpack + JSVMP 混淆 | 放弃纯 APIPlaywright 方案已足够稳定 |
### 视频号
| 问题 | 原因 | 解决方案 |
|------|------|---------|
| 无公开上传 API | 微信封闭生态 | 直接用 Playwright Headless |
| "直接发表"按钮不可见 | 弹窗动画延迟 | `force=True` + JS 点击兜底 |
| 描述框不易定位 | contenteditable div 动态加载 | 点击"添加描述" → 定位 `[contenteditable="true"]:visible` |
| 原创声明弹窗 | 默认触发 | 检测并点击"直接发表" |
### 小红书
| 问题 | 原因 | 解决方案 |
|------|------|---------|
| API 返回 HTML 而非 JSON | session cookie 机制变更 | 放弃纯 API改 Playwright |
| "发布"按钮被 tooltip 拦截 | data-tippy-root 覆盖 | JS 清除 tooltip + `force=True` |
| 发布按钮 disabled 状态 | 标题/描述未正确填入 | 精确选择器 + 等待 disabled 消失 |
| 发布后不跳转 | 页面重置而非跳转 | 检测"拖拽视频到此"作为成功标志 |
| 标题截断 | 小红书标题限 20 字 | `title[:20]` 自动截断 |
### 抖音
| 问题 | 原因 | 解决方案 |
|------|------|---------|
| a_bogus 签名 | JSVMP 虚拟机保护 | 已有 douyin_pure_api.py 解决 |
| Cookie 2-4h 过期 | 抖音 session 机制 | cookie_manager 定期检查 + 告警 |
| AWS4-HMAC-SHA256 | 文件上传需双层签名 | 已在 pure_api 中实现 |
---
## 三、本次优化内容
### 3.1 B站方案升级最大改进
```
旧方案: Playwright 可见浏览器 → 80s/条,需桌面环境
新方案: bilibili-api-python 纯 API → 8s/条,无需浏览器
性能提升: 10x
```
### 3.2 统一发布结果结构
新增 `publish_result.py` 模块,所有平台统一返回 `PublishResult` 数据结构:
```python
@dataclass
class PublishResult:
platform: str # "B站" | "快手" | "视频号" | "小红书" | "抖音"
video_path: str
title: str
success: bool
status: str # "published" | "reviewing" | "failed" | "error"
message: str # 人类可读的结果描述
error_code: str # 机器可读的错误码NOT_LOGGED_IN 等)
screenshot: str # 截图路径
content_url: str # 发布后的链接
elapsed_sec: float # 耗时
timestamp: str # ISO 时间戳
```
### 3.3 发布日志持久化
每次发布结果自动追加到 `publish_log.json`JSON Lines 格式),支持:
- 历史查询
- 失败重试
- 统计分析
### 3.4 汇总表输出
```
========================================================================
发布结果汇总
========================================================================
[✓] B站 | 信任不是求来的 发三个月邮件拿下德… | reviewing
[✓] 快手 | 懒人也能赚钱?动作简单、有利可图… | published
[✓] 视频号 | 易经两小时学个七七八八… | reviewing
[✓] 小红书 | 后端花170万搭体系… | published
------------------------------------------------------------------------
成功: 4/4 | 耗时: 72.7s
========================================================================
```
---
## 四、各平台 API 可行性分析
| 平台 | 纯 API | 难度 | 稳定性 | 当前方案 | 升级可能 |
|------|--------|------|--------|---------|---------|
| B站 | **可行** | 低 | 高 | 已升级纯API | — |
| 抖音 | **可行** | 高 | 中 | 已有纯API | 维护 bd-ticket-guard |
| 快手 | 可行但难维护 | 高 | 低 | Playwright | __NS_sig3 变动太频繁 |
| 小红书 | 可行官方开放API | 中 | 中高 | Playwright | 可申请开发者资质 |
| 视频号 | **不可行** | — | — | Playwright | 微信未开放上传API |
---
## 五、文件结构
```
03_卡木/木叶_视频内容/
├── 多平台分发/脚本/
│ ├── distribute_all.py # 一键多平台分发(入口)
│ ├── publish_result.py # 统一结果结构 + 日志
│ ├── cookie_manager.py # Cookie 管理(检查/加载/过期)
│ ├── video_utils.py # 封面提取 + 视频元数据
│ ├── publish_log.json # 发布历史日志(自动生成)
│ └── requirements.txt
├── B站发布/脚本/
│ ├── bilibili_publish.py # 纯API优先 + Playwright兜底
│ └── bilibili_login.py # 扫码登录
├── 快手发布/脚本/
│ ├── kuaishou_publish.py # Playwright Headless
│ └── kuaishou_login.py # 扫码登录
├── 视频号发布/脚本/
│ ├── channels_publish.py # Playwright Headless
│ └── channels_login.py # 微信扫码登录
├── 小红书发布/脚本/
│ ├── xiaohongshu_publish.py # Playwright Headless
│ └── xiaohongshu_login.py # 扫码/手机号登录
└── 抖音发布/脚本/
├── douyin_pure_api.py # 纯APIVOD分片上传
└── douyin_login.py # Playwright扫码登录
```
---
## 六、使用方式
```bash
# 检查所有平台 Cookie 状态
python3 distribute_all.py --check
# 一键分发到所有已登录平台
python3 distribute_all.py
# 只分发到指定平台
python3 distribute_all.py --platforms B站 快手
# 分发单条视频
python3 distribute_all.py --video /path/to/video.mp4
# 自定义视频目录
python3 distribute_all.py --video-dir /path/to/videos/
```
---
## 七、后续优化方向
1. **小红书官方 API**:申请开发者资质后可升级为纯 API
2. **定时发布**:各平台支持设置发布时间(已有"定时发布"选项)
3. **失败自动重试**:基于 publish_log.json 的失败记录自动重试
4. **Cookie 过期预警**:集成到飞书/微信通知
5. **并行分发**:不同平台间并行上传(当前为串行)

View File

@@ -23,6 +23,7 @@ VIDEO_DIR = Path("/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_out
sys.path.insert(0, str(SCRIPT_DIR))
from cookie_manager import CookieManager, check_all_cookies
from publish_result import PublishResult, print_summary, save_results
PLATFORM_CONFIG = {
"抖音": {
@@ -59,14 +60,14 @@ PLATFORM_CONFIG = {
def check_cookies():
"""检查所有平台 Cookie 状态"""
print("=" * 60)
print(" 多平台 Cookie 状态")
print("=" * 60)
results = check_all_cookies(BASE_DIR)
available = []
for platform, info in results.items():
icons = {"ok": "", "warning": "", "expiring_soon": "", "expired": "", "missing": "", "error": ""}
icons = {"ok": "", "warning": "", "expiring_soon": "",
"expired": "", "missing": "", "error": ""}
icon = icons.get(info["status"], "?")
print(f" [{icon}] {platform}: {info['message']}")
if info["status"] in ("ok", "warning"):
@@ -76,7 +77,6 @@ def check_cookies():
def load_platform_module(name: str, config: dict):
"""动态加载平台发布模块"""
script_path = config["script"]
if not script_path.exists():
return None
@@ -87,8 +87,7 @@ def load_platform_module(name: str, config: dict):
return module
async def distribute_to_platform(platform: str, config: dict, videos: list) -> dict:
"""分发到单个平台"""
async def distribute_to_platform(platform: str, config: dict, videos: list) -> list[PublishResult]:
print(f"\n{'#'*60}")
print(f" 开始分发到 [{platform}]")
print(f"{'#'*60}")
@@ -96,41 +95,47 @@ async def distribute_to_platform(platform: str, config: dict, videos: list) -> d
cookie_path = config["cookie"]
if not cookie_path.exists():
print(f" [✗] {platform} 未登录,跳过")
return {"platform": platform, "status": "skipped", "reason": "未登录"}
return [PublishResult(platform=platform, video_path=str(v), title="",
success=False, status="error", message="未登录") for v in videos]
try:
cm = CookieManager(cookie_path, config["domain"])
if not cm.is_valid():
print(f" [✗] {platform} Cookie 已过期,跳过")
return {"platform": platform, "status": "skipped", "reason": "Cookie过期"}
return [PublishResult(platform=platform, video_path=str(v), title="",
success=False, status="error", message="Cookie过期") for v in videos]
except Exception as e:
print(f" [✗] {platform} Cookie 加载失败: {e}")
return {"platform": platform, "status": "error", "reason": str(e)}
return [PublishResult(platform=platform, video_path=str(v), title="",
success=False, status="error", message=str(e)) for v in videos]
module = load_platform_module(platform, config)
if not module:
print(f" [✗] {platform} 脚本不存在: {config['script']}")
return {"platform": platform, "status": "error", "reason": "脚本不存在"}
return [PublishResult(platform=platform, video_path=str(v), title="",
success=False, status="error", message="脚本不存在") for v in videos]
success = 0
results = []
total = len(videos)
for i, vp in enumerate(videos):
title = getattr(module, "TITLES", {}).get(vp.name, f"{vp.stem} #Soul派对")
try:
ok = await module.publish_one(str(vp), title, i + 1, total)
if ok:
success += 1
r = await module.publish_one(str(vp), title, i + 1, total)
if isinstance(r, PublishResult):
results.append(r)
else:
results.append(PublishResult(
platform=platform, video_path=str(vp), title=title,
success=bool(r), status="reviewing" if r else "failed",
message="旧接口兼容",
))
except Exception as e:
print(f" [✗] {vp.name} 异常: {e}")
results.append(PublishResult(
platform=platform, video_path=str(vp), title=title,
success=False, status="error", message=str(e)[:80],
))
if i < total - 1:
await asyncio.sleep(3)
return {
"platform": platform,
"status": "done",
"success": success,
"total": total,
}
return results
async def main():
@@ -148,18 +153,15 @@ async def main():
if not available:
print("\n[✗] 没有可用的平台,请先登录各平台")
print(" 抖音: python3 ../抖音发布/脚本/douyin_login.py")
print(" B站: python3 ../B站发布/脚本/bilibili_login.py")
print(" 视频号: python3 ../视频号发布/脚本/channels_login.py")
print(" 小红书: python3 ../小红书发布/脚本/xiaohongshu_login.py")
print(" 快手: python3 ../快手发布/脚本/kuaishou_login.py")
for p, c in PLATFORM_CONFIG.items():
print(f" {p}: python3 {c['script']}")
return 1
targets = args.platforms if args.platforms else available
targets = [t for t in targets if t in available]
if not targets:
print(f"\n[✗] 指定的平台均不可用")
print("\n[✗] 指定的平台均不可用")
return 1
video_dir = Path(args.video_dir) if args.video_dir else VIDEO_DIR
@@ -180,27 +182,19 @@ async def main():
print(f" 总任务: {len(videos) * len(targets)}")
print()
all_results = []
all_results: list[PublishResult] = []
for platform in targets:
config = PLATFORM_CONFIG[platform]
result = await distribute_to_platform(platform, config, videos)
all_results.append(result)
platform_results = await distribute_to_platform(platform, config, videos)
all_results.extend(platform_results)
print(f"\n\n{'='*60}")
print(f" 多平台分发汇总")
print(f"{'='*60}")
for r in all_results:
if r["status"] == "done":
print(f" [{r['platform']}] 成功 {r['success']}/{r['total']}")
elif r["status"] == "skipped":
print(f" [{r['platform']}] 跳过 ({r['reason']})")
else:
print(f" [{r['platform']}] 错误 ({r.get('reason', '未知')})")
print_summary(all_results)
save_results(all_results)
total_success = sum(r.get("success", 0) for r in all_results if r["status"] == "done")
total_tasks = sum(r.get("total", 0) for r in all_results if r["status"] == "done")
print(f"\n 总计: {total_success}/{total_tasks}")
return 0
ok = sum(1 for r in all_results if r.success)
total = len(all_results)
print(f" 日志已保存: {SCRIPT_DIR / 'publish_log.json'}")
return 0 if ok == total else 1
if __name__ == "__main__":

View File

@@ -0,0 +1,60 @@
#!/usr/bin/env python3
"""
统一发布结果模块 — 所有平台的 publish_one 都返回此结构。
"""
import json
import time
from dataclasses import dataclass, field, asdict
from datetime import datetime
from pathlib import Path
from typing import Optional
RESULT_LOG = Path(__file__).parent / "publish_log.json"
@dataclass
class PublishResult:
platform: str
video_path: str
title: str
success: bool
status: str # "published" | "reviewing" | "failed" | "error"
message: str = ""
error_code: Optional[str] = None
screenshot: Optional[str] = None
content_url: Optional[str] = None
elapsed_sec: float = 0.0
timestamp: str = field(default_factory=lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
def to_dict(self) -> dict:
return {k: v for k, v in asdict(self).items() if v is not None}
def log_line(self) -> str:
icon = "" if self.success else ""
return f"[{icon}] {self.platform} | {Path(self.video_path).name} | {self.status} | {self.message}"
def save_results(results: list[PublishResult]):
"""追加写入 JSON Lines 日志"""
with open(RESULT_LOG, "a", encoding="utf-8") as f:
for r in results:
f.write(json.dumps(r.to_dict(), ensure_ascii=False) + "\n")
def print_summary(results: list[PublishResult]):
"""控制台打印汇总表"""
if not results:
return
print("\n" + "=" * 72)
print(" 发布结果汇总")
print("=" * 72)
for r in results:
icon = "" if r.success else ""
name = Path(r.video_path).stem[:30]
print(f" [{icon}] {r.platform:<6} | {name:<32} | {r.status}")
if not r.success and r.message:
print(f" └─ {r.message[:60]}")
ok = sum(1 for r in results if r.success)
print("-" * 72)
print(f" 成功: {ok}/{len(results)} | 耗时: {sum(r.elapsed_sec for r in results):.1f}s")
print("=" * 72 + "\n")

View File

@@ -33,10 +33,10 @@ def get_video_info(video_path: str) -> dict:
def extract_cover(video_path: str, output_path: str = "", timestamp: str = "00:00:00.500") -> str:
"""提取视频第一帧作为封面JPEG"""
"""提取视频第一帧作为封面JPEG,默认存 /tmp"""
if not output_path:
stem = Path(video_path).stem
output_path = str(Path(video_path).parent / f"{stem}_cover.jpg")
stem = Path(video_path).stem[:40]
output_path = f"/tmp/{stem}_cover.jpg"
cmd = [
"ffmpeg", "-y", "-i", video_path,

View File

@@ -5,12 +5,16 @@
"""
import asyncio
import sys
import time
from pathlib import Path
SCRIPT_DIR = Path(__file__).parent
COOKIE_FILE = SCRIPT_DIR / "xiaohongshu_storage_state.json"
VIDEO_DIR = Path("/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片")
sys.path.insert(0, str(SCRIPT_DIR.parent.parent / "多平台分发" / "脚本"))
from publish_result import PublishResult
UA = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
@@ -50,17 +54,18 @@ TITLES = {
}
async def publish_one(video_path: str, title: str, idx: int = 1, total: int = 1) -> bool:
async def publish_one(video_path: str, title: str, idx: int = 1, total: int = 1) -> PublishResult:
from playwright.async_api import async_playwright
fname = Path(video_path).name
fsize = Path(video_path).stat().st_size
t0 = time.time()
print(f"\n[{idx}/{total}] {fname} ({fsize/1024/1024:.1f}MB)", flush=True)
print(f" 标题: {title[:60]}", flush=True)
if not COOKIE_FILE.exists():
print(" [✗] Cookie 不存在", flush=True)
return False
return PublishResult(platform="小红书", video_path=video_path, title=title,
success=False, status="error", message="Cookie 不存在")
try:
async with async_playwright() as pw:
@@ -86,9 +91,11 @@ async def publish_one(video_path: str, title: str, idx: int = 1, total: int = 1)
txt = await page.evaluate("document.body.innerText")
if "登录" in (await page.title()) and "上传" not in txt:
print(" [✗] 未登录,请重新运行 xiaohongshu_login.py", flush=True)
await browser.close()
return False
return PublishResult(platform="小红书", video_path=video_path, title=title,
success=False, status="error",
message="未登录,请重新运行 xiaohongshu_login.py",
error_code="NOT_LOGGED_IN", elapsed_sec=time.time()-t0)
print(" [2] 上传视频...", flush=True)
fl = page.locator('input[type="file"]').first
@@ -97,9 +104,12 @@ async def publish_one(video_path: str, title: str, idx: int = 1, total: int = 1)
print(" [2] 文件已选择", flush=True)
else:
await page.screenshot(path="/tmp/xhs_no_input.png")
print(" [✗] 未找到上传控件", flush=True)
await browser.close()
return False
return PublishResult(platform="小红书", video_path=video_path, title=title,
success=False, status="error",
message="未找到上传控件",
screenshot="/tmp/xhs_no_input.png",
elapsed_sec=time.time()-t0)
# 等待上传完成(封面生成完毕)
for i in range(90):
@@ -177,25 +187,33 @@ async def publish_one(video_path: str, title: str, idx: int = 1, total: int = 1)
await page.screenshot(path="/tmp/xhs_result.png")
txt = await page.evaluate("document.body.innerText")
url = page.url
elapsed = time.time() - t0
if "发布成功" in txt or "已发布" in txt:
print(" [✓] 发布成功!", flush=True)
status, msg = "published", "发布成功"
elif "审核" in txt:
print(" [✓] 已提交审核", flush=True)
status, msg = "reviewing", "已提交审核"
elif "笔记" in url or "manage" in url:
print(" [✓] 已跳转(发布成功)", flush=True)
status, msg = "reviewing", "已跳转到笔记管理(发布成功)"
elif "拖拽视频到此" in txt or ("上传视频" in txt and "封面" not in txt):
print(" [✓] 页面已重置(发布成功)", flush=True)
status, msg = "published", "页面已重置(发布成功)"
else:
print(" [⚠] 查看截图: /tmp/xhs_result.png", flush=True)
status, msg = "reviewing", "已提交,请确认截图"
result = PublishResult(
platform="小红书", video_path=video_path, title=title,
success=True, status=status, message=msg,
screenshot="/tmp/xhs_result.png", elapsed_sec=elapsed,
)
print(f" {result.log_line()}", flush=True)
await ctx.storage_state(path=str(COOKIE_FILE))
await browser.close()
return True
return result
except Exception as e:
print(f" [✗] 异常: {e}", flush=True)
return False
return PublishResult(platform="小红书", video_path=video_path, title=title,
success=False, status="error",
message=f"异常: {str(e)[:80]}", elapsed_sec=time.time()-t0)
async def main():

View File

@@ -5,12 +5,16 @@
"""
import asyncio
import sys
import time
from pathlib import Path
SCRIPT_DIR = Path(__file__).parent
COOKIE_FILE = SCRIPT_DIR / "kuaishou_storage_state.json"
VIDEO_DIR = Path("/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片")
sys.path.insert(0, str(SCRIPT_DIR.parent.parent / "多平台分发" / "脚本"))
from publish_result import PublishResult
UA = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
@@ -50,17 +54,18 @@ TITLES = {
}
async def publish_one(video_path: str, title: str, idx: int = 1, total: int = 1) -> bool:
async def publish_one(video_path: str, title: str, idx: int = 1, total: int = 1) -> PublishResult:
from playwright.async_api import async_playwright
fname = Path(video_path).name
fsize = Path(video_path).stat().st_size
t0 = time.time()
print(f"\n[{idx}/{total}] {fname} ({fsize/1024/1024:.1f}MB)", flush=True)
print(f" 标题: {title[:60]}", flush=True)
if not COOKIE_FILE.exists():
print(" [✗] Cookie 不存在", flush=True)
return False
return PublishResult(platform="快手", video_path=video_path, title=title,
success=False, status="error", message="Cookie 不存在")
try:
async with async_playwright() as pw:
@@ -86,9 +91,11 @@ async def publish_one(video_path: str, title: str, idx: int = 1, total: int = 1)
txt = await page.evaluate("document.body.innerText")
if "立即登录" in txt and "发布作品" not in txt:
print(" [✗] 未登录,请重新运行 kuaishou_login.py", flush=True)
await browser.close()
return False
return PublishResult(platform="快手", video_path=video_path, title=title,
success=False, status="error",
message="未登录,请重新运行 kuaishou_login.py",
error_code="NOT_LOGGED_IN", elapsed_sec=time.time()-t0)
# 处理"上次未发布的视频"草稿提示
discard = page.locator('text=放弃').first
@@ -103,9 +110,11 @@ async def publish_one(video_path: str, title: str, idx: int = 1, total: int = 1)
await fl.set_input_files(video_path)
print(" [2] 文件已选择", flush=True)
else:
print(" [✗] 未找到上传控件", flush=True)
await browser.close()
return False
return PublishResult(platform="快手", video_path=video_path, title=title,
success=False, status="error",
message="未找到上传控件", error_code="NO_UPLOAD_INPUT",
elapsed_sec=time.time()-t0)
# 等待上传完成
for i in range(90):
@@ -170,23 +179,31 @@ async def publish_one(video_path: str, title: str, idx: int = 1, total: int = 1)
await page.screenshot(path="/tmp/kuaishou_result.png")
txt = await page.evaluate("document.body.innerText")
url = page.url
elapsed = time.time() - t0
if "发布成功" in txt or "已发布" in txt:
print(" [✓] 发布成功!", flush=True)
status, msg = "published", "发布成功"
elif "审核" in txt:
print(" [✓] 已提交审核", flush=True)
elif "manage" in url or "list" in url:
print(" [✓] 已跳转(发布成功)", flush=True)
status, msg = "reviewing", "已提交审核"
elif "manage" in url or "list" in url or "作品管理" in txt:
status, msg = "reviewing", "已跳转到作品管理(发布成功)"
else:
print(" [⚠] 查看截图: /tmp/kuaishou_result.png", flush=True)
status, msg = "reviewing", "已提交,请确认截图"
result = PublishResult(
platform="快手", video_path=video_path, title=title,
success=True, status=status, message=msg,
screenshot="/tmp/kuaishou_result.png", elapsed_sec=elapsed,
)
print(f" {result.log_line()}", flush=True)
await ctx.storage_state(path=str(COOKIE_FILE))
await browser.close()
return True
return result
except Exception as e:
print(f" [✗] 异常: {e}", flush=True)
return False
return PublishResult(platform="快手", video_path=video_path, title=title,
success=False, status="error",
message=f"异常: {str(e)[:80]}", elapsed_sec=time.time()-t0)
async def main():

File diff suppressed because one or more lines are too long

View File

@@ -1,16 +1,20 @@
#!/usr/bin/env python3
"""
视频号发布 - Headless Playwright
上传 → 填描述 → 发表。视频号反自动化较弱headless 可正常运行
上传 → 填描述 → 发表。视频号无公开APIPlaywright为唯一方案
"""
import asyncio
import sys
import time
from pathlib import Path
SCRIPT_DIR = Path(__file__).parent
COOKIE_FILE = SCRIPT_DIR / "channels_storage_state.json"
VIDEO_DIR = Path("/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片")
sys.path.insert(0, str(SCRIPT_DIR.parent.parent / "多平台分发" / "脚本"))
from publish_result import PublishResult
UA = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
@@ -50,17 +54,18 @@ TITLES = {
}
async def publish_one(video_path: str, title: str, idx: int = 1, total: int = 1) -> bool:
async def publish_one(video_path: str, title: str, idx: int = 1, total: int = 1) -> PublishResult:
from playwright.async_api import async_playwright
fname = Path(video_path).name
fsize = Path(video_path).stat().st_size
t0 = time.time()
print(f"\n[{idx}/{total}] {fname} ({fsize/1024/1024:.1f}MB)", flush=True)
print(f" 标题: {title[:60]}", flush=True)
if not COOKIE_FILE.exists():
print(" [✗] Cookie 不存在", flush=True)
return False
return PublishResult(platform="视频号", video_path=video_path, title=title,
success=False, status="error", message="Cookie 不存在")
try:
async with async_playwright() as pw:
@@ -168,21 +173,29 @@ async def publish_one(video_path: str, title: str, idx: int = 1, total: int = 1)
await page.screenshot(path="/tmp/channels_result.png")
txt = await page.evaluate("document.body.innerText")
url = page.url
elapsed = time.time() - t0
if "发表成功" in txt or "已发表" in txt or "成功" in txt:
print(" [✓] 发表成功!", flush=True)
elif "/platform/post/list" in url or "platform" in url:
print(" [✓] 已跳转(发表成功)", flush=True)
status, msg = "published", "发表成功"
elif "/platform/post/list" in url or ("内容管理" in txt and "视频" in txt):
status, msg = "reviewing", "已跳转到内容管理(发表成功)"
else:
print(" [⚠] 查看截图确认: /tmp/channels_result.png", flush=True)
status, msg = "reviewing", "已提交,请确认截图"
result = PublishResult(
platform="视频号", video_path=video_path, title=title,
success=True, status=status, message=msg,
screenshot="/tmp/channels_result.png", elapsed_sec=elapsed,
)
print(f" {result.log_line()}", flush=True)
await ctx.storage_state(path=str(COOKIE_FILE))
await browser.close()
return True
return result
except Exception as e:
print(f" [✗] 异常: {e}", flush=True)
return False
return PublishResult(platform="视频号", video_path=video_path, title=title,
success=False, status="error",
message=f"异常: {str(e)[:80]}", elapsed_sec=time.time()-t0)
async def main():

View File

@@ -1 +1 @@
{"cookies": [{"name": "sessionid", "value": "BgAAhwULqGfw5gClpw57W0xp6S%2Bv1xxG%2BtoX5MZUGea90WpFZ6g2CZzSm9%2FgiC3wDqiqTpyuGduYwaeCxtofVE6i7hzfyyZmtkIUkktbCus%3D", "domain": "channels.weixin.qq.com", "path": "/", "expires": 1807677423.042462, "httpOnly": false, "secure": true, "sameSite": "None"}, {"name": "wxuin", "value": "3873206396", "domain": "channels.weixin.qq.com", "path": "/", "expires": 1807677423.042529, "httpOnly": false, "secure": true, "sameSite": "None"}], "origins": [{"origin": "https://channels.weixin.qq.com", "localStorage": [{"name": "finder_uin", "value": ""}, {"name": "__ml::page_51552276-5296-41a8-a2f7-a8b0ab36bc16", "value": "{\"pageId\":\"PostList\",\"accessId\":\"39b9bbbc-2e13-48c4-be00-91a70adf6859\",\"step\":2,\"refAccessId\":\"0b8cc40b-a714-4283-9a05-6085c50a799c\",\"refPageId\":\"PostCreate\"}"}, {"name": "__ml::hb_ts", "value": "1773120796513"}, {"name": "__ml::page_edf1bbb5-ccc0-465b-9912-4cd0dcaf1b65", "value": "{\"pageId\":\"PostCreate\",\"accessId\":\"9a803740-2527-4199-8e3d-8c358c8e75e0\",\"step\":1}"}, {"name": "__ml::aid", "value": "\"5749fb2e-51db-48f2-bab1-0d77038fb31a\""}, {"name": "__ml::page_8a0d0b1a-65d6-4a5c-b9f0-fde73bdfa9db", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"8f302ca1-c107-42bc-a227-bee6cc8fb44e\",\"step\":1}"}, {"name": "__rx::aid", "value": "\"5749fb2e-51db-48f2-bab1-0d77038fb31a\""}, {"name": "__ml::page", "value": "[\"72a13cf3-369b-4424-b69d-7ed0deebcc4f\",\"a2988245-e0e8-476b-85dc-106b7c6f5288\",\"ba0e3072-ab8d-43e2-ba6c-7ac71cd8611c\",\"8a0d0b1a-65d6-4a5c-b9f0-fde73bdfa9db\",\"654f47c4-50e7-47ab-b504-f4b90d806cb0\",\"1398bdf4-70b2-409a-b516-4d77923e0f18\",\"edf1bbb5-ccc0-465b-9912-4cd0dcaf1b65\",\"7fc5f55c-5b2c-49c0-ab88-7a31c2c6035a\",\"e2bab444-0f1a-47b2-b1f7-b3acb2ff73ce\",\"b7782d0f-29e4-4123-8184-7c5084b8c2d1\",\"51552276-5296-41a8-a2f7-a8b0ab36bc16\",\"fb0cd2dc-c646-4f56-8c37-c674f100db33\",\"5f9b5814-959c-4caa-9c5f-4cb1d0e9953e\"]"}, {"name": "__ml::page_ba0e3072-ab8d-43e2-ba6c-7ac71cd8611c", "value": "{\"pageId\":\"Home\",\"accessId\":\"ebadf8a0-665a-4c1a-840a-6917618f7414\",\"step\":1}"}, {"name": "finder_login_token", "value": ""}, {"name": "__ml::page_a2988245-e0e8-476b-85dc-106b7c6f5288", "value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"74bd878b-c591-4a54-b9f1-168fb21538c4\",\"step\":1}"}, {"name": "__ml::page_e2bab444-0f1a-47b2-b1f7-b3acb2ff73ce", "value": "{\"pageId\":\"PostCreate\",\"accessId\":\"31466641-845f-430f-9853-8c51b07d8721\",\"step\":1}"}, {"name": "__ml::page_72a13cf3-369b-4424-b69d-7ed0deebcc4f", "value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"bd5e50a0-fc11-477c-9cc9-9c76e2d15205\",\"step\":1}"}, {"name": "finder_username", "value": "v2_060000231003b20faec8c5e48919cbd5cb05e53db077dd1924028a806c10cffd891eb5a80ce7@finder"}, {"name": "__ml::page_b7782d0f-29e4-4123-8184-7c5084b8c2d1", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"bf8c3e8f-bd8d-445d-93a3-ff942287906d\",\"step\":2,\"refAccessId\":\"0ec1591b-605a-4783-91f5-4c4b56baf2cd\",\"refPageId\":\"MicroPost\"}"}, {"name": "_finger_print_device_id", "value": "6fd704941768442b12a996d2652fc61e"}, {"name": "__ml::page_fb0cd2dc-c646-4f56-8c37-c674f100db33", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"ece1a13f-8fe9-4b07-ad56-9428b1a4c49a\",\"step\":2,\"refAccessId\":\"15108cd8-2794-4c73-b959-5fbb3f3c8d83\",\"refPageId\":\"MicroPost\"}"}, {"name": "MICRO_VISITED_NAME", "value": "{\"content\":7}"}, {"name": "__ml::page_5f9b5814-959c-4caa-9c5f-4cb1d0e9953e", "value": "{\"pageId\":\"PostList\",\"accessId\":\"b6ec417c-3b78-4538-93c1-adf298573f3c\",\"step\":2,\"refAccessId\":\"66744497-c4ab-4d3f-b3fc-ac9d0635c405\",\"refPageId\":\"PostCreate\"}"}, {"name": "UvFirstReportLocalKey", "value": "1773072000000"}, {"name": "__ml::page_7fc5f55c-5b2c-49c0-ab88-7a31c2c6035a", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"e21b55a2-3558-45b7-bdc1-5bef0d9976a3\",\"step\":1}"}, {"name": "__ml::page_1398bdf4-70b2-409a-b516-4d77923e0f18", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"e3254f30-b8a3-44d2-9511-9f087c6e0f7e\",\"step\":1}"}, {"name": "finder_ua_report_data", "value": "{\"browser\":\"Chrome\",\"browserVersion\":\"143.0.0.0\",\"engine\":\"Webkit\",\"engineVersion\":\"537.36\",\"os\":\"Mac OS X\",\"osVersion\":\"10.15.7\",\"device\":\"desktop\",\"darkmode\":0}"}, {"name": "__ml::page_654f47c4-50e7-47ab-b504-f4b90d806cb0", "value": "{\"pageId\":\"PostCreate\",\"accessId\":\"1843fcab-97c8-4b58-9d1a-c75a67ab6432\",\"step\":1}"}, {"name": "finder_route_meta", "value": "micro.content/post/list;micro.content/post/create;1;1773120814084"}]}]}
{"cookies": [{"name": "sessionid", "value": "BgAAhwULqGfw5gClpw57W0xp6S%2Bv1xxG%2BtoX5MZUGea90WpFZ6g2CZzSm9%2FgiC3wDqiqTpyuGduYwaeCxtofVE6i7hzfyyZmtkIUkktbCus%3D", "domain": "channels.weixin.qq.com", "path": "/", "expires": 1807677423.042462, "httpOnly": false, "secure": true, "sameSite": "None"}, {"name": "wxuin", "value": "3873206396", "domain": "channels.weixin.qq.com", "path": "/", "expires": 1807677423.042529, "httpOnly": false, "secure": true, "sameSite": "None"}], "origins": [{"origin": "https://channels.weixin.qq.com", "localStorage": [{"name": "finder_uin", "value": ""}, {"name": "__ml::page_a32df29f-5920-415a-a2cd-12ec324ba685", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"1ef23431-eaaf-46b4-8029-bcb651e5e122\",\"step\":1}"}, {"name": "__ml::page_51552276-5296-41a8-a2f7-a8b0ab36bc16", "value": "{\"pageId\":\"PostList\",\"accessId\":\"39b9bbbc-2e13-48c4-be00-91a70adf6859\",\"step\":2,\"refAccessId\":\"0b8cc40b-a714-4283-9a05-6085c50a799c\",\"refPageId\":\"PostCreate\"}"}, {"name": "__ml::hb_ts", "value": "1773121625477"}, {"name": "__ml::page_edf1bbb5-ccc0-465b-9912-4cd0dcaf1b65", "value": "{\"pageId\":\"PostCreate\",\"accessId\":\"9a803740-2527-4199-8e3d-8c358c8e75e0\",\"step\":1}"}, {"name": "__ml::aid", "value": "\"5749fb2e-51db-48f2-bab1-0d77038fb31a\""}, {"name": "__ml::page_8a0d0b1a-65d6-4a5c-b9f0-fde73bdfa9db", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"8f302ca1-c107-42bc-a227-bee6cc8fb44e\",\"step\":1}"}, {"name": "__ml::page_d5bd19e5-ae9b-486e-95d8-0b4d8baabfb6", "value": "{\"pageId\":\"PostCreate\",\"accessId\":\"e6ad3eae-f209-4ef4-871d-ef447601fc31\",\"step\":1}"}, {"name": "__rx::aid", "value": "\"5749fb2e-51db-48f2-bab1-0d77038fb31a\""}, {"name": "__ml::page", "value": "[\"72a13cf3-369b-4424-b69d-7ed0deebcc4f\",\"a2988245-e0e8-476b-85dc-106b7c6f5288\",\"ba0e3072-ab8d-43e2-ba6c-7ac71cd8611c\",\"8a0d0b1a-65d6-4a5c-b9f0-fde73bdfa9db\",\"654f47c4-50e7-47ab-b504-f4b90d806cb0\",\"1398bdf4-70b2-409a-b516-4d77923e0f18\",\"edf1bbb5-ccc0-465b-9912-4cd0dcaf1b65\",\"7fc5f55c-5b2c-49c0-ab88-7a31c2c6035a\",\"e2bab444-0f1a-47b2-b1f7-b3acb2ff73ce\",\"b7782d0f-29e4-4123-8184-7c5084b8c2d1\",\"51552276-5296-41a8-a2f7-a8b0ab36bc16\",\"fb0cd2dc-c646-4f56-8c37-c674f100db33\",\"5f9b5814-959c-4caa-9c5f-4cb1d0e9953e\",\"a32df29f-5920-415a-a2cd-12ec324ba685\",\"d5bd19e5-ae9b-486e-95d8-0b4d8baabfb6\"]"}, {"name": "__ml::page_ba0e3072-ab8d-43e2-ba6c-7ac71cd8611c", "value": "{\"pageId\":\"Home\",\"accessId\":\"ebadf8a0-665a-4c1a-840a-6917618f7414\",\"step\":1}"}, {"name": "finder_login_token", "value": ""}, {"name": "__ml::page_a2988245-e0e8-476b-85dc-106b7c6f5288", "value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"74bd878b-c591-4a54-b9f1-168fb21538c4\",\"step\":1}"}, {"name": "__ml::page_e2bab444-0f1a-47b2-b1f7-b3acb2ff73ce", "value": "{\"pageId\":\"PostCreate\",\"accessId\":\"31466641-845f-430f-9853-8c51b07d8721\",\"step\":1}"}, {"name": "__ml::page_72a13cf3-369b-4424-b69d-7ed0deebcc4f", "value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"bd5e50a0-fc11-477c-9cc9-9c76e2d15205\",\"step\":1}"}, {"name": "finder_username", "value": "v2_060000231003b20faec8c5e48919cbd5cb05e53db077dd1924028a806c10cffd891eb5a80ce7@finder"}, {"name": "__ml::page_b7782d0f-29e4-4123-8184-7c5084b8c2d1", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"bf8c3e8f-bd8d-445d-93a3-ff942287906d\",\"step\":2,\"refAccessId\":\"0ec1591b-605a-4783-91f5-4c4b56baf2cd\",\"refPageId\":\"MicroPost\"}"}, {"name": "__ml::page_fb0cd2dc-c646-4f56-8c37-c674f100db33", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"ece1a13f-8fe9-4b07-ad56-9428b1a4c49a\",\"step\":2,\"refAccessId\":\"15108cd8-2794-4c73-b959-5fbb3f3c8d83\",\"refPageId\":\"MicroPost\"}"}, {"name": "_finger_print_device_id", "value": "6fd704941768442b12a996d2652fc61e"}, {"name": "MICRO_VISITED_NAME", "value": "{\"content\":8}"}, {"name": "__ml::page_5f9b5814-959c-4caa-9c5f-4cb1d0e9953e", "value": "{\"pageId\":\"PostList\",\"accessId\":\"b6ec417c-3b78-4538-93c1-adf298573f3c\",\"step\":2,\"refAccessId\":\"66744497-c4ab-4d3f-b3fc-ac9d0635c405\",\"refPageId\":\"PostCreate\"}"}, {"name": "UvFirstReportLocalKey", "value": "1773072000000"}, {"name": "__ml::page_7fc5f55c-5b2c-49c0-ab88-7a31c2c6035a", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"e21b55a2-3558-45b7-bdc1-5bef0d9976a3\",\"step\":1}"}, {"name": "__ml::page_1398bdf4-70b2-409a-b516-4d77923e0f18", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"e3254f30-b8a3-44d2-9511-9f087c6e0f7e\",\"step\":1}"}, {"name": "finder_ua_report_data", "value": "{\"browser\":\"Chrome\",\"browserVersion\":\"143.0.0.0\",\"engine\":\"Webkit\",\"engineVersion\":\"537.36\",\"os\":\"Mac OS X\",\"osVersion\":\"10.15.7\",\"device\":\"desktop\",\"darkmode\":0}"}, {"name": "__ml::page_654f47c4-50e7-47ab-b504-f4b90d806cb0", "value": "{\"pageId\":\"PostCreate\",\"accessId\":\"1843fcab-97c8-4b58-9d1a-c75a67ab6432\",\"step\":1}"}, {"name": "finder_route_meta", "value": "micro.content/post/create;index;1;1773121627741"}]}]}

View File

@@ -259,3 +259,4 @@
| 2026-03-09 22:23:01 | 🔄 卡若AI 同步 2026-03-09 22:22 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个 |
| 2026-03-10 12:30:08 | 🔄 卡若AI 同步 2026-03-10 12:30 | 更新:水桥平台对接、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 |
| 2026-03-10 12:54:57 | 🔄 卡若AI 同步 2026-03-10 12:54 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 |
| 2026-03-10 13:34:41 | 🔄 卡若AI 同步 2026-03-10 13:34 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 |

View File

@@ -262,3 +262,4 @@
| 2026-03-09 22:23:01 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-09 22:22 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-03-10 12:30:08 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-10 12:30 | 更新:水桥平台对接、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-03-10 12:54:57 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-10 12:54 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-03-10 13:34:41 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-10 13:34 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |