适用场景:在手机微信浏览公众号时,遇到感兴趣的文章,通过一键触发 Webhook,自动将文章内容发布到自己的 WordPress 站点,方便存档与二次阅读。
一、整体架构概览
整个系统由以下几个环节串联:
[手机微信] → 发现感兴趣文章
↓ 复制链接 / 一键分享
[iOS 快捷指令 / HTTP Shortcuts]
↓ POST 请求(附带文章 URL)
[HuggingFace Spaces - Flask Webhook 服务]
↓ 调用 wechat-article-exporter API 获取文章 HTML
↓ 替换图片地址(wsrv.nl 代理,解决防盗链)
[WordPress REST API]
↓ 创建草稿文章
[WordPress 后台] → 检查后发布
核心技术选型:
| 环节 | 技术方案 | 理由 |
|---|---|---|
| 文章内容获取 | wechat-article-exporter 公开 API | 无需维护爬虫,稳定,支持 html/markdown/text |
| 图片防盗链 | wsrv.nl 代理前缀 | 免费 CDN,自动转 WebP,无需下载上传 |
| Webhook 服务 | HuggingFace Spaces(Docker 模式) | 免费,公开 HTTPS URL,Python 全功能支持 |
| CI/CD | GitHub → Docker Hub → HF Space 自动同步 | 预构建镜像加速 HF 启动,代码集中管理 |
| 触发方式 | iOS 快捷指令(Shortcuts) | 微信内分享即触发,2 秒完成 |
| WordPress 发布 | REST API + Application Password | 原生支持,无需插件 |
二、前置准备
2.1 WordPress 开启 REST API 应用密码
- 登录 WordPress 后台,进入用户 → 个人资料
- 下滑找到应用密码区块
- 填写一个名称(如
wxpush),点击添加新应用密码 - 复制生成的密码(格式类似
xxxx xxxx xxxx xxxx xxxx xxxx),只显示一次,请妥善保存 - 确认站点已开启固定链接(设置 → 固定链接),REST API 才能正常工作
注意:应用密码中的空格保留即可,Python
requests的HTTPBasicAuth会自动处理。
2.2 注册所需账号
- HuggingFace 账号(用于部署 Spaces)
- Docker Hub 账号(用于存储预构建镜像)
- GitHub 账号(代码仓库 + CI/CD)
三、项目代码结构
在 GitHub 新建一个仓库,目录结构如下:
wxpush/
├── app.py # Flask Webhook 主程序
├── requirements.txt # Python 依赖
├── Dockerfile # Docker 镜像定义
└── .github/
└── workflows/
└── deploy.yml # GitHub Actions CI/CD 配置
四、核心代码实现
4.1 主程序 app.py
import os
import re
import requests
from flask import Flask, request, jsonify
from requests.auth import HTTPBasicAuth
app = Flask(__name__)
# 从环境变量读取配置(不要硬编码在代码里)
WEBHOOK_SECRET = os.environ.get("WEBHOOK_SECRET", "change-me")
WP_URL = os.environ.get("WP_URL", "https://your-wordpress.com")
WP_USER = os.environ.get("WP_USER", "admin")
WP_PASS = os.environ.get("WP_PASS", "")
EXPORTER_API = "https://wechat-article-exporter-lyart.vercel.app/api/public/v1/download"
def fetch_article(wx_url: str) -> dict:
"""
通过 wechat-article-exporter 公开 API 获取文章内容。
主路径:调用第三方 API(无需 API Key)。
备用路径:直接 requests 抓取微信页面。
"""
try:
resp = requests.get(
EXPORTER_API,
params={"url": wx_url, "format": "html"},
timeout=30
)
resp.raise_for_status()
data = resp.json()
return {"title": data.get("title", ""), "content": data.get("content", "")}
except Exception:
return fetch_article_fallback(wx_url)
def fetch_article_fallback(wx_url: str) -> dict:
"""备用抓取方式:直接解析微信文章页面 HTML"""
from bs4 import BeautifulSoup
headers = {
"User-Agent": (
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) "
"AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
)
}
resp = requests.get(wx_url, headers=headers, timeout=20)
soup = BeautifulSoup(resp.text, "html.parser")
title = soup.find("h1", id="activity-name")
content = soup.find("div", id="js_content")
return {
"title": title.get_text(strip=True) if title else "",
"content": str(content) if content else "",
}
def fix_images(html: str) -> str:
"""
将微信图片 CDN 地址替换为 wsrv.nl 代理地址,
解决防盗链问题,同时自动转换为 WebP 格式。
"""
def replace_src(match):
original_url = match.group(1)
proxied = f"https://wsrv.nl/?output=webp&url={original_url}"
return f'src="{proxied}"'
# 匹配微信图片 CDN 域名
html = re.sub(
r'src="(https://mmbiz\.qpic\.cn[^"]*)"',
replace_src,
html
)
# 同时处理 data-src(微信懒加载图片)
html = re.sub(
r'data-src="(https://mmbiz\.qpic\.cn[^"]*)"',
lambda m: f'src="https://wsrv.nl/?output=webp&url={m.group(1)}"',
html
)
return html
def post_to_wordpress(title: str, content: str) -> dict:
"""通过 WordPress REST API 创建草稿文章"""
endpoint = f"{WP_URL.rstrip('/')}/wp-json/wp/v2/posts"
payload = {
"title": title,
"content": content,
"status": "draft",
}
resp = requests.post(
endpoint,
json=payload,
auth=HTTPBasicAuth(WP_USER, WP_PASS),
timeout=30,
)
resp.raise_for_status()
return resp.json()
@app.route("/health", methods=["GET"])
def health():
"""健康检查接口,用于确认服务是否运行"""
return jsonify({"status": "ok"})
@app.route("/push", methods=["POST"])
def push():
"""
Webhook 主入口。
请求体(JSON):
{
"secret": "你的密钥",
"url": "https://mp.weixin.qq.com/s/xxxxxx"
}
"""
data = request.get_json(silent=True)
if not data:
return jsonify({"error": "invalid json"}), 400
# 验证密钥,防止他人滥用接口
if data.get("secret") != WEBHOOK_SECRET:
return jsonify({"error": "unauthorized"}), 401
wx_url = data.get("url", "").strip()
if not wx_url:
return jsonify({"error": "missing url"}), 400
# 1. 获取文章内容
article = fetch_article(wx_url)
# 2. 处理图片防盗链
article["content"] = fix_images(article["content"])
# 3. 发布到 WordPress
result = post_to_wordpress(article["title"], article["content"])
return jsonify({
"status": "ok",
"post_id": result.get("id"),
"post_url": result.get("link"),
"title": article["title"]
})
if __name__ == "__main__":
# HuggingFace Spaces 默认监听 7860 端口
app.run(host="0.0.0.0", port=7860)
4.2 依赖文件 requirements.txt
flask>=3.0.0
requests>=2.31.0
beautifulsoup4>=4.12.0
lxml>=5.0.0
gunicorn>=21.0.0
4.3 Dockerfile
# 使用官方 Python 精简镜像作为基础
FROM python:3.11-slim
# 设置工作目录
WORKDIR /app
# 先复制依赖文件,利用 Docker 层缓存
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 再复制应用代码
COPY app.py .
# HuggingFace Spaces 要求以非 root 用户运行
RUN useradd -m -u 1000 appuser
USER appuser
# 暴露端口(HF Spaces 固定使用 7860)
EXPOSE 7860
# 使用 gunicorn 生产级服务器启动
CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--workers", "2", "--timeout", "60", "app:app"]
五、CI/CD 配置(GitHub Actions)
5.1 配置 GitHub Secrets
在 GitHub 仓库 → Settings → Secrets and variables → Actions 中添加以下 Secret:
| Secret 名称 | 说明 |
|---|---|
DOCKER_USERNAME | Docker Hub 用户名 |
DOCKER_PASSWORD | Docker Hub 密码或 Access Token |
HF_TOKEN | HuggingFace Access Token(需要 write 权限) |
5.2 GitHub Actions 工作流 .github/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Log in to Docker Hub
run: |
echo "${{ secrets.DOCKER_PASSWORD }}" | \
docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: Build and push Docker image
run: |
IMAGE="${{ secrets.DOCKER_USERNAME }}/wxpush:latest"
docker build -t $IMAGE .
docker push $IMAGE
- name: Sync to HuggingFace Space
env:
HF_TOKEN: ${{ secrets.HF_TOKEN }}
HF_USERNAME: your-hf-username
SPACE_NAME: wxpush
run: |
git config user.email "[email protected]"
git config user.name "GitHub Actions"
git remote add hf \
https://$HF_USERNAME:[email protected]/spaces/$HF_USERNAME/$SPACE_NAME
git push hf main --force
5.3 为什么这样能加速 HuggingFace 构建?
HuggingFace Spaces Docker 模式必须通过仓库中的 Dockerfile 重新构建,无法直接运行预构建镜像。但通过将 Dockerfile 的基础镜像改写为 Docker Hub 上已预构建好的镜像,HF 构建时直接拉取镜像层,省去了重新安装所有依赖的时间:
# HuggingFace Space 仓库中的 Dockerfile(极简版)
# 直接复用 Docker Hub 上已构建好的镜像层
FROM your-dockerhub-username/wxpush:latest
# 只需做 HF 特定的配置
USER root
RUN useradd -m -u 1000 appuser 2>/dev/null || true
USER appuser
EXPOSE 7860
CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--workers", "2", "--timeout", "60", "app:app"]
六、HuggingFace Spaces 部署配置
6.1 创建 Space
- 登录 HuggingFace,点击头像 → New Space
- Space name 填写
wxpush - SDK 选择 Docker
- Visibility 选择 Private(防止 Webhook 接口被公开访问)
- 点击 Create Space
6.2 配置 Secrets(环境变量)
在 Space 页面 → Settings → Variables and secrets 中添加以下 Secret(选择 Secret 类型,不会明文显示):
| 变量名 | 示例值 | 说明 |
|---|---|---|
WEBHOOK_SECRET | my-secret-2026 | 自定义密钥,用于验证请求合法性 |
WP_URL | https://yourblog.com | WordPress 站点地址 |
WP_USER | admin | WordPress 用户名 |
WP_PASS | xxxx xxxx xxxx xxxx | WordPress 应用密码 |
6.3 确认服务运行
Space 构建完成后,访问 https://your-hf-username-wxpush.hf.space/health,若返回 {"status": "ok"} 则服务正常。
七、手机端触发配置
7.1 iOS 快捷指令(推荐)
在 iPhone 上创建以下快捷指令,实现从微信「分享」菜单一键触发:
- 打开「快捷指令」App,新建快捷指令
- 添加动作:获取剪贴板 或 接收来自分享表单的输入(类型选 URL)
- 添加动作:获取 URL 的内容
- URL 填写:
https://your-hf-username-wxpush.hf.space/push - 方法:
POST - 请求体:
JSON,填入:{ "secret": "my-secret-2026", "url": "快捷指令输入变量" }
- URL 填写:
- 添加动作:显示通知,内容填「文章已推送到 WordPress ✅」
- 保存快捷指令,命名为「推送到 WP」
使用方式:在微信文章页面 → 右上角「…」→ 复制链接 → 运行快捷指令(或通过分享菜单直接触发)。
7.2 Android(HTTP Shortcuts)
- 安装 HTTP Shortcuts App(Google Play 免费)
- 新建快捷方式,类型选 Regular Shortcut
- URL:
https://your-hf-username-wxpush.hf.space/push - 方法:POST,请求体 JSON:
{ "secret": "my-secret-2026", "url": "{clipboard}" } - 将快捷方式添加到桌面,微信复制链接后点击即可触发
八、关于 HuggingFace Spaces 休眠问题
免费版 HuggingFace Spaces 在约 15 分钟无请求后会进入休眠,冷启动需要 30-60 秒。对于本项目(个人偶发性推送)影响不大,因为:
- Webhook 请求会自动唤醒 Space
- 唤醒后再重试即可(iOS 快捷指令可加重试逻辑)
如需完全避免休眠,可考虑以下方案:
- 升级 HF Pro 账号:支持持久运行(约 $9/月)
- 改用 Railway 或 Render:有限免费时数,但无休眠
- UptimeRobot 定时 Ping:每 5 分钟请求
/health接口保持活跃(免费)
九、完整测试流程
服务部署完成后,可通过以下命令在电脑端先行测试:
# 测试健康检查
curl https://your-hf-username-wxpush.hf.space/health
# 测试推送一篇微信文章
curl -X POST https://your-hf-username-wxpush.hf.space/push \
-H "Content-Type: application/json" \
-d '{
"secret": "my-secret-2026",
"url": "https://mp.weixin.qq.com/s/你的文章ID"
}'
成功响应示例:
{
"status": "ok",
"post_id": 123,
"post_url": "https://yourblog.com/?p=123",
"title": "文章标题"
}
然后登录 WordPress 后台 → 文章 → 草稿,即可看到刚刚同步的文章,确认内容无误后手动发布。
十、常见问题排查
Q:返回 401 Unauthorized
A:检查请求中的 secret 字段是否与环境变量 WEBHOOK_SECRET 一致。
Q:WordPress 发布失败,返回 401
A:确认 WP_USER 和 WP_PASS 正确,且 WordPress 已生成应用密码(不是登录密码)。
Q:文章内容为空 A:wechat-article-exporter API 可能暂时不可用,程序会自动切换到 Fallback 模式直接抓取;若仍为空,可能该文章需要登录才能查看。
Q:图片显示为空白
A:检查 fix_images 函数是否正确替换了 data-src 属性,部分微信文章使用懒加载图片。
Q:HuggingFace Space 无法访问
A:若 Space 设置为 Private,需要在请求头中加入 HF Token,或改为 Public 并依靠 secret 字段鉴权。
本方案代码已在 Python 3.11 + Flask 3.x 环境下验证,wechat-article-exporter API 由第三方提供,如遇 API 失效请降级使用 Fallback 抓取模式。