总日志数
-
总文件数
-
AI 解析数
-
| 时间 | 标题 | 大小 | 链接 | 操作 |
|---|
| 时间 | 标题 | 模型 | Tokens | 操作 |
|---|
每行一个选项,空行自动忽略。
微信推文解析器 API 提供微信公众号推文的解析、图片代理、正文截图和 R2 对象存储上传能力。基于 Cloudflare Workers + Hono 框架构建,所有 API 端点位于 /api/v1/ 路径下。
https://xiaozhao-img-cache.pages.dev
https://xiaozhao-img-cache.hatu.work
所有 /api/v1/* 端点均要求 API Key 认证。支持以下两种方式:
# 方式一:自定义请求头(推荐) curl -H "X-API-Key: sk-xxxxxxxxxxxxxxxx" ... # 方式二:Bearer Token curl -H "Authorization: Bearer sk-xxxxxxxxxxxxxxxx" ...
| 错误码 | HTTP 状态码 | 说明 |
|---|---|---|
UNAUTHORIZED | 401 | API Key 无效或缺失 |
INVALID_URL | 400 | 微信推文链接格式无效 |
PARSE_FAILED | 500 | 文章解析失败(壹伴 API 异常或 session 过期) |
UPLOAD_FAILED | 500 | R2 上传失败 |
MISSING_IMAGE | 400 | 请求中缺少图片数据 |
PROCESS_FAILED | 500 | 完整处理流程失败 |
// 错误响应统一格式
{
"success": false,
"error": "错误描述(中文)",
"code": "ERROR_CODE" // 机器可读错误码,用于程序判断
}
由于微信图片存在严格的跨域 (CORS) 和防盗链限制,所有 API 返回数据中的微信图片 URL 均已自动替换为代理域名。
// 替换前(微信原始 CDN,浏览器跨域受限): http://mmecoa.qpic.cn/mmecoa_jpg/6r7I0M2XKy7.../0?wx_fmt=jpeg https://mmbiz.qpic.cn/sz_mmbiz_jpg/abc.../0?wx_fmt=jpeg // 替换后(代理域名,可直接在前端使用): https://weixinimg.hatu.work/mmecoa_jpg/6r7I0M2XKy7.../0?wx_fmt=jpeg https://weixinimg.hatu.work/sz_mmbiz_jpg/abc.../0?wx_fmt=jpeg // 规则说明: // 1. 仅替换域名部分(scheme + host),路径和参数完全保留 // 2. 支持 http:// 和 https:// 两种协议 // 3. 覆盖所有微信图片域名:*.qpic.cn, *.qlogo.cn // 4. 影响字段:cdn_url, cdn_url_1_1, cdn_url_235_1, content_noencode 中所有 img 标签
wx_image_proxy)"中修改。解析微信推文,返回结构化文章数据。所有图片 URL 已替换为代理域名。同时缓存文章内容用于后续渲染。
mp.weixin.qq.com。支持短链和长链。curl -X POST https://xiaozhao-img-cache.pages.dev/api/v1/parse \
-H "Content-Type: application/json" \
-H "X-API-Key: sk-xxxxxxxx" \
-d '{"url":"https://mp.weixin.qq.com/s/3MZZ1jmh7IRPLTVj9-li-A"}'
{
"success": true,
"data": {
"title": "文章标题",
"author": "作者名称",
"nick_name": "公众号名称",
"desc": "推文摘要描述",
"source_url": "阅读原文链接(可能为空字符串)",
"cdn_url": "https://weixinimg.hatu.work/mmecoa_jpg/...(主封面图)",
"cdn_url_1_1": "https://weixinimg.hatu.work/...(1:1 比例封面图)",
"cdn_url_235_1": "https://weixinimg.hatu.work/...(2.35:1 比例封面图)",
"content_noencode": "<section>正文 HTML...</section>(所有 img src 已代理)",
"article_id": "m1abc2def3",
"render_url": "https://xiaozhao-img-cache.pages.dev/render/m1abc2def3"
}
}
上传图片至 Cloudflare R2 对象存储。支持两种上传模式。
data:image/png;base64, 前缀或纯 Base64cover.png),默认自动生成image/png, image/jpeg, image/webp# JSON Base64 方式
curl -X POST https://xiaozhao-img-cache.pages.dev/api/v1/upload \
-H "Content-Type: application/json" \
-H "X-API-Key: sk-xxxxxxxx" \
-d '{"image_base64":"data:image/png;base64,iVBOR...","title":"文章截图"}'
# 二进制方式
curl -X POST https://xiaozhao-img-cache.pages.dev/api/v1/upload \
-H "Content-Type: image/png" \
-H "X-API-Key: sk-xxxxxxxx" \
-H "X-Title: 文章截图" \
-H "X-Filename: my_screenshot.png" \
--data-binary @screenshot.png
{
"success": true,
"data": {
"url": "https://xiaozhao-img-cache.hatu.work/articles/api_xxx.png",
"key": "articles/api_xxx.png",
"size": 123456 // 文件大小(字节)
}
}
一站式处理:解析推文 + 生成自动截图页面。返回文章数据和可直接访问的截图 URL。
curl -X POST https://xiaozhao-img-cache.pages.dev/api/v1/process \
-H "Content-Type: application/json" \
-H "X-API-Key: sk-xxxxxxxx" \
-d '{"url":"https://mp.weixin.qq.com/s/xxxxx"}'
{
"success": true,
"data": {
"article": {
"title": "文章标题",
"author": "作者",
"nick_name": "公众号名称",
"desc": "摘要",
"source_url": "",
"cdn_url": "https://weixinimg.hatu.work/...",
"cdn_url_1_1": "https://weixinimg.hatu.work/...",
"cdn_url_235_1": "https://weixinimg.hatu.work/...",
"content_noencode": "正文HTML(图片已代理)",
"article_id": "m1abc2def3"
},
"image": null, // 若提供 image_base64 则返回 { url, key, size }
"render_url": "https://.../render/m1abc2def3",
"screenshot_url": "https://.../render/m1abc2def3?mode=screenshot",
"_usage": {
"screenshot_flow": "打开 screenshot_url 自动完成截图并上传至 R2",
"puppeteer_detect": "通过 document.title 检测: SCREENSHOT_DONE:url",
"js_detect": "通过 window.__screenshot_result 获取结果",
"postmessage": "监听 postMessage: { type: screenshot_done, data }"
}
}
}
image_base64,返回上传结果 {url, key, size};否则为 null由于 Cloudflare Workers 无法运行无头浏览器,截图通过客户端渲染完成。推荐使用 Puppeteer 或 Playwright 配合 screenshot_url 实现全自动截图。
const puppeteer = require('puppeteer');
async function captureArticle(wxUrl, apiKey) {
const BASE = 'https://xiaozhao-img-cache.pages.dev';
// 1. 调用 /api/v1/process 获取 screenshot_url
const resp = await fetch(BASE + '/api/v1/process', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': apiKey },
body: JSON.stringify({ url: wxUrl }),
});
const { data } = await resp.json();
console.log('文章标题:', data.article.title);
// 2. 用 Puppeteer 打开 screenshot_url(自动截图 + 上传)
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 900, height: 1200 });
await page.goto(data.screenshot_url, { waitUntil: 'networkidle2' });
// 3. 等待截图完成(通过 document.title 检测)
await page.waitForFunction(
() => document.title.startsWith('SCREENSHOT_DONE:')
|| document.title.startsWith('SCREENSHOT_ERROR:'),
{ timeout: 60000 }
);
// 4. 获取结果
const result = await page.evaluate(() => window.__screenshot_result);
await browser.close();
if (result.success) {
console.log('截图 URL:', result.data.url);
return result.data;
} else {
throw new Error(result.error);
}
}
// 使用示例
captureArticle(
'https://mp.weixin.qq.com/s/3MZZ1jmh7IRPLTVj9-li-A',
'sk-xxxxxxxx'
).then(console.log);
const { chromium } = require('playwright');
async function captureWithPlaywright(wxUrl, apiKey) {
const BASE = 'https://xiaozhao-img-cache.pages.dev';
// 1. 解析文章
const resp = await fetch(BASE + '/api/v1/process', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': apiKey },
body: JSON.stringify({ url: wxUrl }),
});
const { data } = await resp.json();
// 2. 打开自动截图页面
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(data.screenshot_url);
// 3. 等待 title 变化(包含完成或错误标记)
await page.waitForFunction(
() => document.title.startsWith('SCREENSHOT_DONE:')
|| document.title.startsWith('SCREENSHOT_ERROR:'),
{ timeout: 60000 }
);
// 4. 读取结果
const result = await page.evaluate(() => window.__screenshot_result);
await browser.close();
return result;
}
import requests, json, time
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
BASE = "https://xiaozhao-img-cache.pages.dev"
API_KEY = "sk-xxxxxxxx"
def capture_article(wx_url):
# 1. 调用 API
resp = requests.post(f"{BASE}/api/v1/process",
headers={"Content-Type": "application/json", "X-API-Key": API_KEY},
json={"url": wx_url})
data = resp.json()["data"]
# 2. Selenium 打开截图页面
driver = webdriver.Chrome()
driver.get(data["screenshot_url"])
# 3. 等待完成
WebDriverWait(driver, 60).until(
lambda d: d.title.startswith("SCREENSHOT_DONE:")
or d.title.startswith("SCREENSHOT_ERROR:"))
# 4. 读取结果
result = driver.execute_script("return window.__screenshot_result")
driver.quit()
return result
| 方式 | 说明 | 适用场景 |
|---|---|---|
document.title | 成功时为 SCREENSHOT_DONE:<url>,失败为 SCREENSHOT_ERROR:<msg> | Puppeteer/Playwright/Selenium |
window.__screenshot_result | 对象 { done, success, data, error } | Puppeteer/Playwright (page.evaluate) |
postMessage | 向 opener/parent 发送 { type: 'screenshot_done', data } | iframe 嵌入或 window.open |
管理端 API 同样支持 API Key 认证或管理员 Cookie Session。
# 登录获取 Session
POST /api/admin/login { "username": "admin", "password": "admin" }
# 返回: { "success": true, "token": "xxx" }
# 同时设置 Cookie: admin_session=xxx
# 退出
POST /api/admin/logout
# 检查登录状态
GET /api/admin/check → { "authenticated": true/false }
# 获取所有配置(敏感字段已脱敏)
GET /api/admin/config
# 更新单个配置
PUT /api/admin/config
Body: { "key": "wx_image_proxy", "value": "https://weixinimg.hatu.work" }
# 重新生成 API Key(旧 Key 立即失效)
POST /api/admin/config/regenerate-api-key
| 配置键 | 说明 | 默认值 |
|---|---|---|
yiban_session | 壹伴编辑器 Session(用于调用壹伴 API 解析文章) | 预设值 |
wx_image_proxy | 微信图片代理域名(替换 *.qpic.cn 等域名) | https://weixinimg.hatu.work |
r2_access_key_id | R2 S3 兼容 API 的 Access Key | 预设值 |
r2_secret_access_key | R2 S3 兼容 API 的 Secret Key | 预设值 |
r2_endpoint | R2 S3 兼容 API 端点 | 预设值 |
r2_bucket_name | R2 存储桶名称 | xiaozhao-cache |
r2_custom_domain | R2 自定义域名(文件 URL 前缀) | https://xiaozhao-img-cache.hatu.work |
api_key | API 调用密钥 | 首次自动生成 |
admin_username | 管理员用户名 | admin |
admin_password | 管理员密码 | admin |
max_image_width | 截图最大宽度(px) | 800 |
image_quality | 截图质量(0-1) | 0.92 |
# 查看日志(分页 + 筛选)
GET /api/admin/logs?page=1&pageSize=20
&type=info|error|warn|api # 按类型筛选
&action=parse_article # 按操作名称筛选(模糊匹配)
&keyword=奥比中光 # 按详情关键词搜索(模糊匹配)
# 导出日志(支持筛选条件)
GET /api/admin/logs/export?format=json # JSON 格式导出
GET /api/admin/logs/export?format=csv # CSV 格式导出
&type=api&keyword=xxx # 可选筛选参数
# 删除单条日志
DELETE /api/admin/logs/:id
# 清空所有日志
DELETE /api/admin/logs
# 查看文件列表
GET /api/admin/files?page=1&pageSize=20
# 删除文件(同时删除 R2 存储和索引)
DELETE /api/admin/files/:key
# key 需 URL 编码,如: articles%2Farticle_xxx.png
GET /api/admin/stats
→ { "total_logs": 42, "total_files": 15 }
以下端点供前端 Web UI 调用,支持 Admin Session Cookie 或 API Key 认证。
# 解析文章(Web UI 使用)
POST /api/article/parse { "url": "https://mp.weixin.qq.com/s/xxx" }
# 上传截图(Web UI 使用)
POST /api/article/upload-image
{
"image_base64": "data:image/png;base64,...",
"title": "文章标题",
"source_url": "原始链接"
}
# 文件渲染
GET /render/:id # 文章预览页
GET /render/:id?mode=screenshot # 自动截图页(无需人工操作)
AI 解析采用两阶段架构,将图片处理和 AI 推理拆分为两个独立请求,避免 Cloudflare Pages 的 100s 请求超时限制(Error 524)。
┌─────────────────────────────────────────────────────────────┐
│ Phase 1: POST /api/article/ai-parse │
│ ① 提取文章中所有图片 URL │
│ ② 全并行下载图片(每张 15s 超时,整体 50s 截止) │
│ ③ 上传图片到 R2 对象存储 │
│ ④ 保存中间状态到 KV(10分钟有效),返回 task_id │
│ 耗时:通常 1-10 秒 │
└─────────────────────┬───────────────────────────────────────┘
│ task_id
┌─────────────────────▼───────────────────────────────────────┐
│ Phase 2: POST /api/article/ai-parse/complete │
│ ① 从 KV 读取中间状态(已下载的图片 R2 URL) │
│ ② 并行读取 AI 配置 │
│ ③ 构建多模态消息(文本 + R2 图片 URL) │
│ ④ 调用 AI API(85s 超时保护) │
│ ⑤ 解析 JSON 响应,保存结果到 KV │
│ 耗时:通常 20-80 秒 │
└─────────────────────────────────────────────────────────────┘
// 请求
POST /api/article/ai-parse
{ "article_id": "m1abc2def3" } // 来自 /api/article/parse 的 ID
// 响应
{
"success": true,
"data": {
"task_id": "mnj0u7xb075kq3", // Phase 2 使用
"phase": "prepared",
"images_processed": 1, // 成功上传到 R2 的图片数
"images_total": 1, // 文章中总图片数
"article_title": "...",
"total_size": 1366425, // 图片总字节数
"elapsed_seconds": 1.1 // Phase 1 服务端耗时
}
}
// 请求
POST /api/article/ai-parse/complete
{ "task_id": "mnj0u7xb075kq3" } // Phase 1 返回的 task_id
// 响应
{
"success": true,
"data": {
"result_id": "mnj0u8abc123",
"parsed": { /* 结构化招聘数据 */ },
"usage": {
"prompt_tokens": 5200,
"completion_tokens": 2153,
"total_tokens": 7353
},
"model": "moonshotai/kimi-k2.5",
"images_processed": 1,
"images_total": 1,
"elapsed_seconds": 72.9 // Phase 2 服务端耗时
}
}
// 请求(支持 article_id、url 或 html 三种输入方式)
POST /api/v1/ai-parse
-H "X-API-Key: sk-xxxxxxxx"
{ "article_id": "m1abc2def3" }
// 或 { "url": "https://mp.weixin.qq.com/s/xxx" }
// 或 { "html": "...", "title": "..." }
# 查看 AI 解析结果列表 GET /api/article/ai-results?page=1&pageSize=20 # 查看单条结果详情 GET /api/article/ai-results/:id # 删除单条结果 DELETE /api/article/ai-results/:id
| 环节 | 超时限制 | 说明 |
|---|---|---|
| 单张图片下载 | 15 秒 | 每张图片独立超时,不影响其他图片 |
| Phase 1 整体 | 50 秒 | 到达截止时间后收集已完成的图片继续 |
| Phase 2 AI 调用 | 85 秒 | 留 15s 余量给 KV 存储和响应返回 |
| Task 缓存有效期 | 10 分钟 | Phase 1 完成后 10 分钟内必须调用 Phase 2 |
async function aiParse(articleId) {
// Phase 1: 图片准备
const p1 = await fetch('/api/article/ai-parse', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ article_id: articleId })
}).then(r => r.json());
if (!p1.success) throw new Error(p1.error);
console.log('Phase 1 done:', p1.data.images_processed, 'images');
// Phase 2: AI 推理
const p2 = await fetch('/api/article/ai-parse/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ task_id: p1.data.task_id })
}).then(r => r.json());
if (!p2.success) throw new Error(p2.error);
return p2.data.parsed; // 结构化招聘数据
}
字段控制系统允许管理员配置 AI 解析时各字段的行为:哪些由 AI 自由提取,哪些必须从固定选项中选择。固定选项会自动注入到 AI 提示词中,确保输出标准化。
# 获取所有字段控制配置
GET /api/admin/field-controls
→ { "success": true, "data": [
{
"fieldPath": "employer.industry", // JSON 路径
"label": "企业所属行业", // 显示名称
"mode": "fixed_single", // ai | fixed_single | fixed_multi
"options": [{ "value": "互联网/科技" }, ...],
"required": true
}, ...
] }
# 更新全部字段控制(整体替换)
PUT /api/admin/field-controls
Body: { "controls": [ ... ] }
# 导入选项列表(追加或替换某个字段的选项)
POST /api/admin/field-controls/import-options
Body: {
"fieldPath": "employer.industry",
"options": ["半导体/芯片", "电子信息"],
"mode": "append" // append(追加) 或 replace(替换)
}
| 字段路径 | 名称 | 模式 | 预设选项 |
|---|---|---|---|
| 雇主字段 (employer) | |||
employer.name | 公司全称 | AI 自动 | - |
employer.shortName | 公司简称 | AI 自动 | - |
employer.industry | 企业所属行业 | 固定单选 | 互联网/科技、金融/证券/投资 等 15 个 |
employer.natureTag | 企业性质 | 固定单选 | 央企、地方国企、民企、外企、合资企业、港澳台企业 |
employer.scaleTag | 企业规模 | 固定单选 | 大型/中型/小型/微型企业 |
employer.qualityTag | 企业资质 | 固定多选 | 高新技术企业、专精特新、小巨人、独角兽、瞪羚企业、单项冠军 |
employer.statusTag | 企业地位 | 固定单选 | 行业龙头、细分龙头、头部企业、腰部企业、隐形冠军 |
| 资讯字段 (jobNews) | |||
jobNews.title | 资讯标题 | AI 自动 | - |
jobNews.type | 招聘类型 | 固定单选 | 校招、实习 |
jobNews.recruitType | 招聘类别 | AI 自动 | - |
jobNews.education | 学历要求 | AI 自动 | - |
jobNews.targetGroup | 面向群体 | AI 自动 | - |
jobNews.location | 工作地点 | AI 自动 | - |
jobNews.positionType | 岗位类型 | AI 自动 | - |
jobNews.summary | 资讯摘要 | AI 自动 | - |
// 固定选项字段会被自动注入到 AI 系统提示词末尾: // // 【重要:以下字段必须从给定的选项中选择,不允许自行发挥】 // - 企业所属行业(employer.industry)(单选,只能选一个): // 互联网/科技、金融/证券/投资、制造/汽车、... // - 企业性质(employer.natureTag)(单选,只能选一个): // 央企、地方国企、民企、外企、合资企业、港澳台企业 // - ... // // AI 必须从这些选项中选择,确保输出标准化。
BuffCore 从分点列举改为一段完整的话,以大学生求职者的视角总结核心亮点,并标注高亮关键词。
"buffCore": {
"summary": "作为科创板上市的3D视觉龙头企业,奥比中光为你提供行业领先的薪酬体系和完善的培训机制,技术岗可参与前沿AI视觉项目,更有海外工作机会助力国际化发展。2026届春招面向全国六大城市,免笔试直通面试,值得重点关注。",
"highlights": [
"科创板上市",
"3D视觉龙头",
"行业领先的薪酬体系",
"海外工作机会",
"免笔试"
]
}
"buffCore": [
{ "text": "核心亮点1", "bold": true, "highlight": false },
{ "text": "核心亮点2", "bold": false, "highlight": true }
]
// 前端自动检测格式: // 1. 新格式 (object): 渲染段落,highlights 中的关键词用黄色底色高亮 // 2. 旧格式 (array): 保持原有的分点列举渲染 // 两种格式都向后兼容,无需迁移历史数据。
yiban_session 会定期失效,解析失败时请在"配置"中更新。访问 yibanbianji.com 登录后从浏览器 Cookie 中获取 session 值。