Skill: wenjie0112/wechat-video-downloader (version 1.0.0)

--- name: wechat-video-downloader description: 从微信公众号文章中提取并下载视频。使用场景：用户发送公众号文章链接并要求下载其中的视频，或提到"微信视频下载"、"公众号视频"、"mp.weixin.qq.com 视频"等关键词。自动识别页面中的视频播放器，提取真实视频 URL 并下载到本地。 --- # 微信视频下载器 从微信公众号文章中提取并下载嵌入的视频文件。 ## 快速开始 当用户提供微信公众号文章链接时，使用内置脚本下载视频： ```bash python ~/.openclaw/skills/wechat-video-downloader/scripts/download_wechat_video.py <文章 URL> [输出文件名] ``` ## 工作流程 ### 1. 识别触发场景 当用户请求包含以下特征时触发此技能： - 包含 `mp.weixin.qq.com` 的 URL - 提到"微信视频"、"公众号视频"、"下载视频"等关键词 - 发送公众号文章链接并要求获取其中的视频 ### 2. 检查文章是否包含视频 **方法 A：使用浏览器快照（推荐）** 打开文章页面并获取快照，查找视频播放按钮： ``` 1. 用 browser.open 打开文章 URL 2. 用 browser.snapshot 获取页面快照（refs="aria"） 3. 查找包含"播放视频"、"play"、"video"的 button 元素 4. 提取按钮的 ref 用于后续点击 ``` **方法 B：直接调用脚本** 如果确认文章包含视频，直接运行下载脚本： ```bash python ~/.openclaw/skills/wechat-video-downloader/scripts/download_wechat_video.py <URL> ``` ### 3. 下载视频 脚本自动执行以下步骤： 1. **打开页面** - 使用 browser.open 加载文章 2. **查找视频** - 通过 snapshot 定位播放按钮 3. **触发加载** - 点击播放按钮使视频元素加载 4. **提取 URL** - 通过 JavaScript 获取 `<video>` 元素的 src 5. **下载文件** - 使用 curl 添加正确的 User-Agent 和 Referer 请求头 6. **关闭页面** - 清理浏览器标签页 ### 4. 处理常见问题 **问题 1:403 Forbidden** 微信视频链接有时效性和来源验证，必须添加正确的请求头： ```bash curl -L -o output.mp4 \ -H "User-Agent: Mozilla/5.0 ..." \ -H "Referer: https://mp.weixin.qq.com/" \ "<video_url>" ``` **问题 2：找不到视频元素** - 确认文章确实包含视频（有些文章只有图片） - 等待页面完全加载（增加 sleep 时间） - 检查是否有多个视频，尝试不同的播放按钮 ref **问题 3：视频链接过期** 视频 URL 包含时效性 token，必须在点击播放后立即提取并下载，不能重复使用旧链接。 ## 脚本说明 ### 位置 `~/.openclaw/skills/wechat-video-downloader/scripts/download_wechat_video.py` ### 参数 - `article_url`（必需）- 微信公众号文章 URL - `output_filename`（可选）- 输出文件名，默认使用视频 ID ### 返回值 - 成功：返回 0，视频保存到当前目录 - 失败：返回 1，打印错误信息 ### 依赖 - Python 3.6+ - OpenClaw CLI（browser 命令） - curl ## 示例 ### 示例 1：基本下载 用户：下载这个公众号文章里的视频 https://mp.weixin.qq.com/s/xxx ```bash python ~/.openclaw/skills/wechat-video-downloader/scripts/download_wechat_video.py https://mp.weixin.qq.com/s/xxx ``` ### 示例 2：指定文件名 用户：把视频保存为"教学视频.mp4" ```bash python ~/.openclaw/skills/wechat-video-downloader/scripts/download_wechat_video.py https://mp.weixin.qq.com/s/xxx 教学视频.mp4 ``` ### 示例 3：手动流程（脚本失败时） 如果脚本无法自动处理，手动执行： ``` 1. browser.open targetUrl=<文章 URL> 2. browser.snapshot refs="aria" → 找到播放按钮 ref 3. browser.act request='{"kind":"click","ref":"e88"}' 4. sleep 3 5. browser.act request='{"kind":"evaluate","fn":"()=>document.querySelector(\"video\").src"}' 6. 用 curl 下载提取的 URL 7. browser.close 关闭页面 ``` ## 注意事项 1. **视频时效性** - 提取的 URL 包含认证 token，必须在短时间内使用 2. **网络速度** - 视频文件通常较大（100MB-1GB），下载需要时间 3. **存储空间** - 确保工作目录有足够空间 4. **版权尊重** - 仅下载用于个人学习或已获授权的内容 ## 支持的视频类型 - ✅ 微信公众号内置视频播放器（mpvideo.qpic.cn） - ✅ 腾讯视频嵌入（v.qq.com） - ⚠️ 第三方平台视频（可能需要额外处理） ## 故障排查 | 问题 | 可能原因 | 解决方案 | |------|----------|----------| | 403 Forbidden | 缺少 Referer 头 | 确保 curl 添加 `-H "Referer: https://mp.weixin.qq.com/"` | | 找不到视频 | 页面未完全加载 | 增加点击前的等待时间 | | 文件为空 | URL 过期 | 重新打开页面，立即提取并下载 | | 脚本无响应 | 浏览器未启动 | 先运行 `openclaw browser start` |

path: README.md
content: # 微信视频下载器 (WeChat Video Downloader)

从微信公众号文章中自动提取并下载嵌入的视频文件。

## ✨ 功能特点

- 🎯 **自动识别** - 智能定位文章中的视频播放器
- 🚀 **一键下载** - 自动提取真实视频 URL 并下载
- 🔓 **绕过验证** - 处理微信的 Referer 验证机制
- 📦 **开箱即用** - 基于 OpenClaw 平台，无需复杂配置

## 📋 前置要求

- Python 3.6+
- [OpenClaw](https://github.com/openclaw/openclaw) CLI 工具
- curl 命令

## 🚀 快速开始

### 1. 安装 OpenClaw

如果尚未安装 OpenClaw，请参考官方文档：
https://docs.openclaw.ai

### 2. 使用脚本下载视频

```bash
# 基本用法
python scripts/download_wechat_video.py <文章 URL>

# 指定输出文件名
python scripts/download_wechat_video.py <文章 URL> 我的视频.mp4
```

### 3. 示例

```bash
# 下载公众号文章中的视频
python scripts/download_wechat_video.py https://mp.weixin.qq.com/s/zCrty8DIPBwm0nxg1s07iA

# 保存为指定文件名
python scripts/download_wechat_video.py https://mp.weixin.qq.com/s/xxx 教学视频.mp4
```

## 💡 使用场景

- 下载微信公众号中的教学视频
- 保存讲座、课程录像
- 离线观看喜欢的内容
- 视频内容归档

## 🔧 工作原理

1. **打开页面** - 使用 OpenClaw browser 工具加载公众号文章
2. **定位视频** - 通过页面快照查找视频播放按钮
3. **触发加载** - 点击播放按钮使视频元素加载
4. **提取 URL** - 通过 JavaScript 获取 `<video>` 元素的真实源地址
5. **下载文件** - 使用 curl 添加正确的请求头下载视频
6. **清理资源** - 关闭浏览器标签页

## 📁 项目结构

```
wechat-video-downloader/
├── README.md                    # 项目说明
├── LICENSE                      # MIT 许可证
├── SKILL.md                     # OpenClaw 技能定义
└── scripts/
    └── download_wechat_video.py # 主下载脚本
```

## ⚠️ 注意事项

1. **视频时效性** - 提取的 URL 包含时效性 token，需立即使用
2. **文件大小** - 视频通常较大（100MB-1GB），确保有足够存储空间
3. **网络环境** - 需要稳定的网络连接
4. **版权尊重** - 仅下载用于个人学习或已获授权的内容

## 🐛 故障排查

| 问题 | 解决方案 |
|------|----------|
| 403 Forbidden | 脚本已自动处理，确保使用最新版本 |
| 找不到视频 | 确认文章包含视频（有些只有图片） |
| 下载的文件为空 | URL 可能过期，重试即可 |
| 脚本无响应 | 确保 OpenClaw browser 已启动 |

## 📝 支持的 video 类型

- ✅ 微信公众号内置视频（mpvideo.qpic.cn）
- ✅ 腾讯视频嵌入（v.qq.com）
- ⚠️ 其他第三方平台（可能需要额外配置）

## 🤝 贡献

欢迎提交 Issue 和 Pull Request！

## 📄 许可证

MIT License - 详见 LICENSE 文件

## 🔗 相关链接

- [OpenClaw 官方文档](https://docs.openclaw.ai)
- [OpenClaw GitHub](https://github.com/openclaw/openclaw)
- [ClawHub 技能市场](https://clawhub.ai)

---

**Made with ❤️ for OpenClaw Community**


path: scripts/download_wechat_video.py
content: #!/usr/bin/env python3
"""
微信视频下载器 - 从微信公众号文章中提取并下载视频

使用方法:
    python download_wechat_video.py <文章 URL> [输出文件名]

示例:
    python download_wechat_video.py https://mp.weixin.qq.com/s/xxx
    python download_wechat_video.py https://mp.weixin.qq.com/s/xxx 我的视频.mp4
"""

import sys
import os
import subprocess
import json
import time
from pathlib import Path


def run_browser_command(action, params=None):
    """执行浏览器控制命令"""
    cmd = ["openclaw", "browser", action]
    if params:
        for key, value in params.items():
            if isinstance(value, bool):
                cmd.append(f"--{key}")
            else:
                cmd.append(f"--{key}={value}")
    
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        print(f"浏览器命令失败：{result.stderr}")
        return None
    
    try:
        return json.loads(result.stdout)
    except:
        return result.stdout


def extract_video_url(target_id):
    """从页面中提取视频 URL"""
    js_code = """() => {
        const video = document.querySelector('video');
        if(video) {
            return {
                src: video.src,
                currentSrc: video.currentSrc,
                tagName: video.tagName
            };
        }
        return null;
    }"""
    
    result = run_browser_command("act", {
        "targetId": target_id,
        "request": json.dumps({"kind": "evaluate", "fn": js_code})
    })
    
    if result and "result" in result:
        return result["result"]
    return None


def download_video(video_url, output_path, referer="https://mp.weixin.qq.com/"):
    """下载视频文件"""
    user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
    
    cmd = [
        "curl", "-L",
        "-o", output_path,
        "-H", f"User-Agent: {user_agent}",
        "-H", f"Referer: {referer}",
        "-H", "Accept: video/webm,video/ogg,video/mp4,application/octet-stream",
        "--progress-bar",
        video_url
    ]
    
    print(f"开始下载视频到：{output_path}")
    result = subprocess.run(cmd)
    
    if result.returncode == 0:
        file_size = os.path.getsize(output_path)
        if file_size > 0:
            print(f"✅ 下载完成！文件大小：{file_size / 1024 / 1024:.1f}MB")
            return True
        else:
            print("❌ 下载的文件为空")
            if os.path.exists(output_path):
                os.remove(output_path)
            return False
    else:
        print(f"❌ 下载失败，退出码：{result.returncode}")
        return False


def download_wechat_video(article_url, output_filename=None):
    """主函数：下载微信公众号视频"""
    
    print(f"📱 打开微信公众号文章：{article_url}")
    
    # 1. 打开文章页面
    result = run_browser_command("open", {"targetUrl": article_url})
    if not result or "targetId" not in result:
        print("❌ 无法打开文章页面")
        return False
    
    target_id = result["targetId"]
    print(f"✓ 页面已打开 (targetId: {target_id})")
    
    # 等待页面加载
    time.sleep(2)
    
    # 2. 获取页面快照查找视频
    print("🔍 查找视频元素...")
    snapshot = run_browser_command("snapshot", {
        "targetId": target_id,
        "refs": "aria"
    })
    
    if not snapshot:
        print("❌ 无法获取页面快照")
        return False
    
    snapshot_str = str(snapshot)
    
    # 查找视频播放按钮
    video_refs = []
    for line in snapshot_str.split('\n'):
        if 'button' in line.lower() and ('播放' in line or 'video' in line.lower() or 'play' in line.lower()):
            if 'ref=' in line:
                ref = line.split('ref=')[1].split(']')[0]
                video_refs.append(ref)
    
    if not video_refs:
        print("❌ 未找到视频播放按钮")
        return False
    
    print(f"✓ 找到视频播放按钮 (refs: {video_refs})")
    
    # 3. 点击播放按钮触发视频加载
    video_ref = video_refs[0]
    print(f"▶️  点击播放按钮 (ref: {video_ref})")
    
    result = run_browser_command("act", {
        "targetId": target_id,
        "request": json.dumps({"kind": "click", "ref": video_ref})
    })
    
    if not result or not result.get("ok"):
        print("❌ 点击播放按钮失败")
        return False
    
    # 等待视频加载
    print("⏳ 等待视频加载...")
    time.sleep(3)
    
    # 4. 提取视频真实 URL
    print("🔗 提取视频 URL...")
    video_info = extract_video_url(target_id)
    
    if not video_info:
        print("❌ 无法提取视频 URL")
        return False
    
    video_url = video_info.get("src") or video_info.get("currentSrc")
    
    if not video_url:
        print("❌ 视频 URL 为空")
        return False
    
    print(f"✓ 获取到视频 URL")
    
    # 5. 确定输出文件名
    if not output_filename:
        vid = video_url.split("vid=")[-1].split("&")[0] if "vid=" in video_url else "wechat_video"
        output_filename = f"{vid}.mp4"
    
    output_path = Path(output_filename).resolve()
    
    # 6. 下载视频
    success = download_video(video_url, str(output_path))
    
    # 7. 关闭浏览器标签页
    run_browser_command("close", {"targetId": target_id})
    
    return success


def main():
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)
    
    article_url = sys.argv[1]
    output_filename = sys.argv[2] if len(sys.argv) > 2 else None
    
    success = download_wechat_video(article_url, output_filename)
    
    if success:
        print("\n🎉 视频下载完成!")
        sys.exit(0)
    else:
        print("\n💥 视频下载失败!")
        sys.exit(1)


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)