BILIBILI / 每周图文
理解会话续期与图文投稿
从一次正常扫码授权,到图片上传、Opus 投稿、回执保存和平台状态确认。
本项目由青龙调用 Python 脚本,未开放 Bilibili HTTP 代理。下列路径属于 B站上游平台;worker.py bilibili 是服务器脚本入口,不能直接当作 Item 的 HTTP API 调用。
1. 登录与认证
调用顺序:扫码授权 → nav 核验指定 UID → 按需续期 → 上传图片 → 持久保存投稿预留 → 提交一次 → 查询稿件状态。Cookie 和令牌只在自己的服务器私有配置中保存。这个文档站没有凭据输入框,也不会向平台发请求。
| 凭据 | 用途 | 保存方式 |
|---|---|---|
SESSDATA | 网页登录会话。 | 受保护配置,不写日志或 Git。 |
bili_jct | Cookie 中的 CSRF 值;写接口另传到表单或查询参数。 | 与对应会话一同更新。 |
DedeUserID | 登录返回的账户编号;必须与指定发布 UID 相符。 | 由服务器验证,不接受静默改绑。 |
refresh_token | 与该次会话配套的续期令牌,不是 Cookie 字段。 | 正常授权时保存,轮换后原子更新。 |
https://passport.bilibili.com/x/passport-login/web/qrcode/generate生成短时授权请求返回 data.url 和 data.qrcode_key。用自己的 B站 App 扫码确认,再以该 key 查询下面的接口;不要将真实授权链接或 key 发布到公共页面。
https://passport.bilibili.com/x/passport-login/web/qrcode/poll查询参数:qrcode_key观察到的 data.code:86101 等待扫码,86090 等待手机确认,86038 已过期,0 授权完成。成功时同时接收 Set-Cookie 与 data.refresh_token,再核验指定账号。二维码约三分钟有效;网页已登录不等于这次服务器授权已完成。
https://api.bilibili.com/x/web-interface/nav只读 · 带自己的会话 Cookie{"code": 0, "data": {"isLogin": true, "mid": 42}}这是脱敏字段示例。必须同时检查 code == 0、isLogin == true、mid == 指定发布 UID,以及登录 Cookie 中的 UID。失败立即停止,不上传到错误账号。
只读 Python 示例:检查自己的会话,不输出 Cookie
import json
import requests
from pathlib import Path
cfg = json.loads(Path('/etc/bismih-cron-task/config.json').read_text())
with requests.Session() as s:
s.trust_env = False
r = s.get('https://api.bilibili.com/x/web-interface/nav',
headers={'User-Agent': 'Mozilla/5.0',
'Referer': 'https://www.bilibili.com/',
'Cookie': cfg['bili_cookie']},
allow_redirects=False, timeout=(5, 15))
r.raise_for_status()
value = r.json()
identity = value.get('data') or {}
print({'logged_in': value.get('code') == 0 and identity.get('isLogin') is True,
'expected_account': str(identity.get('mid')) == str(cfg['bili_expected_uid'])})仅供服务器管理员在受保护环境中运行。网络异常请输出固定错误码,不打印包含认证材料的原始请求或响应。
2. Cookie 按需续期
Cookie 已失效且没有配套刷新令牌时,需要正常重新授权;不能从 Cookie 补造令牌。已有令牌也不代表会话永不过期。
| 步骤 | 平台请求 | 输入与结果 |
|---|---|---|
| 检查 | GET passport.bilibili.com/x/passport-login/web/cookie/info | 带会话 Cookie,可传 csrf。返回 data.refresh 和毫秒 timestamp;false 时不刷新。 |
| 取得短时口令 | GET www.bilibili.com/correspond/1/{correspondPath} | 对 refresh_{timestamp} 使用对应公钥及 RSA-OAEP / SHA-256 生成十六进制路径,从 HTML 的 div#1-name 读取 refresh_csrf。 |
| 轮换 | POST passport.bilibili.com/x/passport-login/web/cookie/refresh | 表单:旧 csrf、refresh_csrf、source=main_web、旧 refresh_token。接收新 Cookie 和新 refresh_token。 |
| 先验证并保存 | GET api.bilibili.com/x/web-interface/nav | 核对新会话属于指定 UID,原子持久化新 Cookie、新令牌及待确认的旧令牌。 |
| 最后确认 | POST passport.bilibili.com/x/passport-login/web/confirm/refresh | 表单:新 csrf 和 旧 refresh_token;这一步会使旧令牌对应的会话失效。 |
刷新 POST 前写私有备份与 inflight journal;结果不明时停止再次轮换,保留恢复材料。若只是最终确认失败,下一次只继续确认,不能重新开始整个刷新链。未通过新账号验证前,不确认旧令牌失效。
协议参考:Bilibili Cookie 刷新流程。这是社区整理的网页协议,非承诺长期稳定的官方开放 API。本项目在 bili_refresh.py 中实现并保留脱敏结果。
3. 逐张上传正文图片
https://api.bilibili.com/x/article/creative/article/upcover写入 · Cookie · multipart/form-data| 字段 | 位置 | 含义 |
|---|---|---|
csrf | 表单 | 当前会话的 bili_jct。 |
binary | 文件 | 已校验图片,带文件名和对应图片 MIME。 |
{"code": 0, "data": {"url": "https://article.biliimg.com/example-image.jpg"}}返回值必须为成功并带允许域名的图片 URL;对协议相对地址、HTTP 前缀做规范化后仍检查域名。本地记录图片宽、高和 KiB 大小。每张以 weekly-illust-{date}:bili-image:{illust_id} 保存回执,后续重试跳过已确认上传的图片。
4. 只提交一次 Opus 图文
https://api.bilibili.com/x/dynamic/feed/create/opus写入 · Cookie · application/json查询参数为 csrf 和 gaia_source=main_web。正文由 worker.article_payload() 构造;下面展示关键结构,完整 JSON 示例可从学习规格下载。
{
"raw_content": "",
"opus_req": {
"upload_id": "由服务端生成的本次上传标识",
"scene": 12,
"opus": {
"opus_source": 2, "title": "Pixiv 每周插画榜 · 示例日期",
"content": {"paragraphs": ["文本与图片段落对象,见完整示例"]},
"article": {"category_id": 15, "originality": 0,
"reproduced": 1, "biz_tags": ["Pixiv", "插画", "周榜"]},
"pub_info": {"editor_version": "eva3-4.0.0"}
},
"meta": {"app_meta": {"mobi_app": "web", "from": "create.creative.h5"}},
"option": {"aigc": 2, "close_comment": 0,
"up_choose_comment": 0, "private_pub": 2}
}
}这是结构讲解,paragraphs 中的说明字符串不能直接投稿。完整示例包含文本 nodes、图片 pics 和首图 cover;文本 font_level 使用字符串 regular,不能用整数 1。其他枚举沿用当前已验证编辑器载荷,不凭字段名猜测语义。
发请求前以 weekly-illust-{date}:bilibili-article 保存 pending。成功时保存 dyn_id_str/dyn_id 和 dyn_rid/aid;不使用实时生成的新 upload_id 替代本地防重复记录。
{"code": 0, "data": {"dyn_id_str": "123456789", "dyn_rid": 456789}}收到唯一编号只代表平台接受了提交。POST 断线、非 JSON 或缺少稿件编号时,保留 pending,进入待核对;不能自动再发一篇。明确的结构化拒绝才释放该次投稿预留。
5. 提交与公开状态分别确认
https://api.bilibili.com/x/article/creative/article/view只读 · Cookie · 查询参数 aidaid 使用刚保存的文章编号。当前实现仅在 code:0 且 data.state 为 0 或 9 时确认发布;其他值保持待核对。例如本次实测先返回 -2,后来才取得明确发布状态,不能把首次提交回执当成公开成功。
每日预检会读取同一条已提交回执;只有与最近那次运行编号匹配的发布确认,才能让 Item 转绿。原始投稿审计记录保留,即使其当时状态为 needs_review;新确认不会重发文章。
| 错误或阶段 | 处理 |
|---|---|
-101 | 登录失效,停止写入并恢复正常授权。 |
-111 | CSRF 校验失败,核对该次会话对应的 bili_jct。 |
86095(刷新) | 核对 refresh_csrf 及会话与 refresh_token 的配套关系。 |
| HTTP 403 / 412 / 429、验证页面 | 停止本轮,不用无限重试或轮换地址掩盖失败。 |
| 有投稿回执,公开状态未确认 | 只读核对,保留回执;Item 显示红灯“待平台确认”。 |
| POST 结果不明 | 保留 pending 和恢复材料,管理员核对后处理。 |
6. 脚本入口与定时监控
此命令会真实上传和投稿。仅在已部署青龙容器中由管理员调用;本次文档发布不会执行它。
python3 /ql/data/scripts/bismih-job.py bilibili生产每周一北京时间 09:40 运行,使用同一份已采集周榜。当前没有周榜则停止空白发布;已有投稿回执则读取确认,不再次提交。
| 脚本接口 | 返回或职责 |
|---|---|
bilibili_auth.run('generate') / run('poll') | 管理员生成官方短时二维码、接收授权并验证原发布 UID。异常账号待确认,不自动改绑。 |
bili_refresh.maintain(config_path) | 在共享配置锁中按需续期;返回 ok、code 和刷新令牌是否存在,不返回凭据值。 |
worker.bilibili() | 上传、投稿及查询;成功返回 summary 和 metrics,失败交由工作引擎记录。 |
publisher_health.py --preflight | 每日预检与按需续期,同时对已提交稿件做只读状态确认。 |
正常会话维护的脱敏返回示意:
{"ok": true, "code": "SESSION_VALID", "refresh_token_present": true}这不表示本次发生了令牌轮换。只有平台要求刷新时才执行刷新链;真正轮换并确认后返回 SESSION_REFRESHED。维护记录与私有恢复材料不公开。
每日 07:40 预检、每 5 分钟读取本机状态。绿灯要求青龙启用且周计划正确、每日预检未超过 36 小时、最近应执行的周任务成功或同一稿件已通过发布状态核对。失败、超期或待确认不能因心跳更新变绿。
实现对照:bilibili_auth.py、bili_refresh.py、worker.py 和 publisher_health.py。基线实测于 2026-10-26;上游为网页协议,平台字段可能变化。阅读本文不需要登录;源码仓库保持私有。