Skill: shuai1iu/youtube-search-extractor (version 1.0.0)

--- name: YouTube Search Extractor description: YouTube搜索结果视频链接提取器 - 可以搜索特定关键词并提取视频链接 read_when: - 需要从YouTube搜索结果中提取视频链接 - 需要批量获取特定关键词的视频内容 - 需要自动化处理YouTube搜索和链接提取 metadata: {"clawdbot":{"emoji":"🎥","requires":{"bins":["node","npm"]}}} allowed-tools: Bash(youtube-search-extractor:*) --- # YouTube Search Extractor - YouTube搜索结果视频链接提取器 ## 功能概述 这是一个用于从YouTube搜索结果中自动提取视频链接的技能。它可以： 1. 使用`agent-browser`工具自动执行YouTube搜索 2. 从搜索结果HTML页面中提取视频链接 3. 过滤和去重视频链接 4. 生成格式化的链接列表 ## 安装依赖 ### 安装agent-browser（npm方式推荐） ```bash npm install -g agent-browser agent-browser install agent-browser install --with-deps ``` ### 从源代码安装 ```bash git clone https://github.com/vercel-labs/agent-browser cd agent-browser pnpm install pnpm build agent-browser install ``` ## 使用方法 ### 基本搜索和提取 ```bash # 搜索关键词并保存链接到文件 npm run search -- "关键词" "输出文件名" ``` ### 示例：搜索Hydrasynth实战应用视频 ```bash npm run search -- "hydrasynth 实战应用" hydrasynth_links ``` ### 直接使用脚本 ```bash cd /Users/happy/.openclaw/workspace/skills/youtube-search-extractor python3 youtube_search_extractor.py "关键词" "输出文件名" ``` ## 文件结构 ### 核心文件 - **`youtube_search_extractor.py`** - 主要的搜索和提取脚本 - **`SKILL.md`** - 技能文档 - **`package.json`** - npm项目配置 - **`.clawhub/`** - ClawHub配置目录 ### 输出文件 - **`<output_file>.html`** - YouTube搜索结果的HTML页面 - **`<output_file>_links.txt`** - 提取的视频链接列表 ## 技术特点 ### 🚀 自动化流程 - 使用`agent-browser`进行浏览器自动化 - 模拟真实用户搜索行为 - 智能等待页面加载完成 ### 🔍 精准提取 - 使用正则表达式匹配视频链接模式 - 处理相对链接到绝对链接的转换 - 自动去重和链接清理 ### 📋 格式化输出 - 清晰的编号列表 - 完整的YouTube URL格式 - 包含搜索时间戳 ### ⚡ 高性能 - 并行处理搜索和提取 - 优化的链接匹配算法 - 容错机制保障稳定运行 ## 支持的搜索关键词格式 - 英文关键词：`"Hydrasynth practical applications"` - 中文关键词：`"hydrasynth 实战应用"` - 混合关键词：`"OpenClaw tutorial 教程"` - 多关键词搜索：使用空格分隔 ## 使用示例 ### 1. 搜索OpenClaw相关视频 ```bash npm run search -- "OpenClaw tutorial" openclaw_links ``` ### 2. 搜索Hydrasynth实战应用视频 ```bash python3 youtube_search_extractor.py "hydrasynth 实战应用" hydrasynth_links ``` ### 3. 搜索特定主题的视频 ```bash cd /Users/happy/.openclaw/workspace/skills/youtube-search-extractor python3 youtube_search_extractor.py "AI音乐创作" ai_music_links ``` ## 配置选项 ### 脚本参数 ```bash python3 youtube_search_extractor.py [关键词] [输出文件名] [可选参数] 可选参数： --headless # 无头浏览器模式（默认：启用） --wait-time <秒数> # 页面加载等待时间（默认：5秒） --max-links <数量> # 最大链接数（默认：50个） --proxy <地址> # 使用代理服务器 ``` ### 配置文件 创建`youtube_search_config.json`配置文件： ```json { "browser": { "headless": true, "wait_time": 5, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" }, "extractor": { "max_links": 50, "filter_relevance": true } } ``` ## 故障排除 ### 常见问题 1. **安装依赖失败** ```bash npm install -g agent-browser --force ``` 2. **浏览器启动失败** ```bash agent-browser install --with-deps ``` 3. **网络连接问题** ```bash # 使用代理 python3 youtube_search_extractor.py "关键词" "输出文件名" --proxy "http://localhost:8080" ``` ### 调试模式 ```bash # 启用详细输出 python3 youtube_search_extractor.py "关键词" "输出文件名" --debug ``` ## 扩展功能 ### 添加新的搜索模板 在`search_templates`目录中添加搜索模板： ```json { "name": "Hydrasynth Search", "keywords": ["hydrasynth", "Hydrasynth", "hydra synth"], "description": "搜索Hydrasynth合成器相关的内容", "filters": ["hydrasynth"] } ``` ### 自定义提取规则 修改`youtube_search_extractor.py`中的链接匹配模式： ```python def extract_video_links(html_content): patterns = [ r'href=["\'](/watch\?v=[\w-]+[^"\']*)["\']', r'href=["\'](https?://(?:www\.)?youtube\.com/watch\?v=[\w-]+[^"\']*)["\']', r'href=["\'](https?://(?:www\.)?youtu\.be/[\w-]+[^"\']*)["\']' ] # 其他提取逻辑... ``` ## 注意事项 ### 合法使用 - 请遵守YouTube的服务条款 - 合理使用API，避免过度请求 - 尊重版权，仅供个人学习使用 ### 性能优化 - 避免在短时间内进行大量搜索 - 使用适当的等待时间避免被封禁 - 考虑使用代理池分散请求 ### 安全注意 - 不在代码中硬编码个人信息 - 定期更新依赖库 - 监控网络连接安全性 ## 许可证 本技能采用MIT许可证，可自由使用、修改和分发。

path: CONTRIBUTING.md
content: # Contributing to YouTube Search Extractor

我们欢迎您为YouTube Search Extractor做出贡献！这是一个开源项目，旨在帮助用户从YouTube搜索结果中自动提取视频链接。

## 代码规范

### Python规范

- 使用PEP 8风格指南
- 使用4个空格进行缩进
- 使用有意义的变量和函数名
- 添加适当的注释和文档字符串

### 代码结构

```python
# 函数应该有明确的功能
def function_name(parameters):
    """
    文档字符串应该包含：
    - 功能描述
    - 参数说明
    - 返回值说明
    - 异常说明（如果有）
    """
    # 代码实现
```

### 提交规范

我们使用语义化提交信息：

```
feat: 添加新功能
fix: 修复bug
docs: 文档更新
style: 代码风格改进（不改变功能）
refactor: 重构（既不添加新功能也不修复bug）
perf: 性能优化
test: 添加或更新测试
chore: 其他变更（构建系统、依赖等）
```

## 开发流程

### 1. Fork仓库

点击GitHub页面上的"Fork"按钮将仓库复制到您的账户下。

### 2. 克隆到本地

```bash
git clone https://github.com/您的用户名/skills.git
cd skills/youtube-search-extractor
```

### 3. 创建分支

```bash
git checkout -b feature/your-feature-name
```

### 4. 开发和测试

```bash
# 安装依赖
npm install

# 运行测试
npm run test

# 测试功能
python3 youtube_search_extractor.py "测试关键词" test_output
```

### 5. 提交代码

```bash
git add .
git commit -m "feat: 添加新功能"
git push origin feature/your-feature-name
```

### 6. 创建Pull Request

到GitHub页面上创建Pull Request，描述您的变更。

## 问题报告

### Bug报告

当报告bug时，请包含：

1. **完整的错误信息** - 包括堆栈跟踪
2. **环境信息** - 操作系统、Python版本、agent-browser版本
3. **复现步骤** - 如何重现问题
4. **预期行为** - 应该发生什么
5. **实际行为** - 实际发生了什么

### 功能请求

当请求新功能时，请包含：

1. **功能描述** - 您想要的功能是什么
2. **使用场景** - 您将在什么情况下使用这个功能
3. **预期接口** - 您希望如何使用这个功能
4. **其他考虑** - 任何相关的信息

## 开发资源

### 文档

- [YouTube Search Extractor - SKILL.md](SKILL.md) - 技能文档
- [agent-browser官方文档](https://github.com/vercel-labs/agent-browser)
- [npm package.json文档](https://docs.npmjs.com/files/package.json)

### 测试资源

- [pytest官方文档](https://docs.pytest.org/)
- [unittest官方文档](https://docs.python.org/3/library/unittest.html)

### 调试工具

- 使用`--debug`参数启用详细输出
- 查看`*.html`和`*_links.txt`输出文件
- 使用浏览器开发者工具分析YouTube页面结构

## 社区准则

### 行为准则

我们遵循开放源代码行为准则，请保持：

- 尊重他人
- 友好和专业
- 建设性的反馈
- 有帮助的态度

### 交流方式

- **GitHub Issues** - 问题报告和功能请求
- **Pull Requests** - 代码贡献
- **Discussions** - 技术讨论和建议

## 许可证

本项目使用MIT许可证。通过贡献代码，您同意您的贡献将根据MIT许可证进行授权。

## 联系我们

如果您有任何问题或建议，欢迎通过以下方式联系我们：

- 创建GitHub Issue
- 发送邮件到[REDACTED_SECRET]
- 在Discord社区中提问

感谢您的贡献！


path: README.md
content: # YouTube Search Extractor - YouTube搜索结果视频链接提取器

## 🏠 项目概述

YouTube Search Extractor是一个用于从YouTube搜索结果中自动提取视频链接的技能。它使用`agent-browser`进行浏览器自动化，帮助用户快速获取特定主题的视频资源。

## ✨ 主要功能

### 🚀 自动化搜索
- 使用`agent-browser`模拟真实用户搜索
- 智能等待页面加载完成
- 自动处理搜索结果

### 🔍 精准提取
- 使用正则表达式匹配视频链接模式
- 处理相对链接到绝对链接的转换
- 自动去重和链接清理

### 📋 格式化输出
- 清晰的编号列表
- 完整的YouTube URL格式
- 包含搜索时间戳

### ⚡ 高性能
- 并行处理搜索和提取
- 优化的链接匹配算法
- 容错机制保障稳定运行

## 📦 安装方法

### 1. 安装依赖

```bash
npm install -g agent-browser
agent-browser install
```

### 2. 下载技能

```bash
cd /Users/happy/.openclaw/workspace/skills
git clone https://github.com/openclaw/youtube-search-extractor.git
```

或者通过ClawHub安装：

```bash
clawhub install "YouTube Search Extractor"
```

## 🚀 快速开始

### 基本使用方法

```bash
cd /Users/happy/.openclaw/workspace/skills/youtube-search-extractor

# 方法1: 使用npm脚本
npm run search -- "hydrasynth 实战应用" hydrasynth_links

# 方法2: 直接使用Python脚本
python3 youtube_search_extractor.py "hydrasynth 实战应用" hydrasynth_links
```

### 输出文件

- **`hydrasynth_links.html`** - YouTube搜索结果的HTML页面
- **`hydrasynth_links_links.txt`** - 提取的视频链接列表

## 📖 使用示例

### 示例1: 搜索Hydrasynth实战应用

```bash
npm run search -- "hydrasynth 实战应用" hydrasynth_practical
```

输出内容：
```
# YouTube搜索结果：'hydrasynth 实战应用' (2026-02-26 23:30:00)
# 找到 26 个相关视频

1. https://www.youtube.com/watch?v=O37_qc3jhsc
2. https://www.youtube.com/watch?v=t0Ic87OLHRE
3. https://www.youtube.com/watch?v=NB5D34KDMxs
...
```

### 示例2: 搜索OpenClaw教程

```bash
python3 youtube_search_extractor.py "OpenClaw tutorial" openclaw_tutorial
```

### 示例3: 使用自定义参数

```bash
python3 youtube_search_extractor.py "AI音乐创作" ai_music_links --wait-time 10 --max-links 30
```

## 🎛️ 命令参数

### 基本参数

```bash
python3 youtube_search_extractor.py [搜索关键词] [输出文件名] [可选参数]
```

### 可选参数

| 参数 | 说明 | 示例 |
|------|------|------|
| `--headless` | 使用无头浏览器模式 | `--headless` |
| `--wait-time <秒数>` | 页面加载等待时间 | `--wait-time 10` |
| `--max-links <数量>` | 最大提取链接数量 | `--max-links 30` |
| `--debug` | 启用详细输出 | `--debug` |

## 🔧 高级用法

### 自定义提取规则

在`youtube_search_extractor.py`中修改`extract_video_links()`方法：

```python
def extract_video_links(self, html_content):
    patterns = [
        r'href=["\'](/watch\?v=[\w-]+[^"\']*)["\']',
        r'href=["\'](https?://(?:www\.)?youtube\.com/watch\?v=[\w-]+[^"\']*)["\']',
        r'href=["\'](https?://(?:www\.)?youtu\.be/[\w-]+[^"\']*)["\']'
    ]
    # 其他提取逻辑...
```

### 添加搜索模板

在`search_templates`目录中添加搜索模板：

```json
{
  "name": "Hydrasynth Search",
  "keywords": ["hydrasynth", "Hydrasynth", "hydra synth"],
  "description": "搜索Hydrasynth合成器相关的内容",
  "filters": ["hydrasynth"]
}
```

## 📊 性能优化

### 避免被封禁

- 使用适当的等待时间（推荐5-10秒）
- 避免在短时间内进行大量搜索
- 考虑使用代理池分散请求

### 内存优化

- 限制`--max-links`参数
- 及时删除临时文件
- 使用`--headless`模式

## 🔒 安全注意

### 合法使用
- 遵守YouTube的服务条款
- 合理使用API，避免过度请求
- 尊重版权，仅供个人学习使用

### 隐私保护
- 不在代码中硬编码个人信息
- 使用`--headless`模式避免暴露真实IP
- 定期更新依赖库

## 🐛 故障排除

### 常见问题

1. **agent-browser未找到**
   ```bash
   npm install -g agent-browser
   agent-browser install
   ```

2. **页面内容为空**
   ```bash
   # 增加等待时间
   python3 youtube_search_extractor.py "关键词" "输出文件名" --wait-time 10
   ```

3. **网络连接失败**
   ```bash
   # 检查网络连接
   ping youtube.com
   # 使用代理
   python3 youtube_search_extractor.py "关键词" "输出文件名" --proxy "http://localhost:8080"
   ```

### 调试模式

```bash
python3 youtube_search_extractor.py "关键词" "输出文件名" --debug
```

## 📈 贡献指南

我们欢迎您为项目做出贡献！请参考[CONTRIBUTING.md](CONTRIBUTING.md)文件。

## 📄 许可证

本项目采用MIT许可证，可自由使用、修改和分发。

## 📞 支持

如果您有任何问题或建议，请：

1. 创建GitHub Issue
2. 发送邮件到[REDACTED_SECRET]
3. 在Discord社区中提问

## 🌟 项目链接

- **GitHub仓库**: https://github.com/openclaw/youtube-search-extractor
- **ClawHub页面**: https://clawhub.com/skills/youtube-search-extractor
- **agent-browser**: https://github.com/vercel-labs/agent-browser

---

💡 这个技能的设计理念是帮助用户快速获取YouTube上的视频资源，同时保持代码的简单性和可扩展性。希望您使用愉快！


path: package.json
content: {
  "name": "youtube-search-extractor",
  "version": "1.0.0",
  "description": "YouTube搜索结果视频链接提取器 - 使用agent-browser自动化搜索并提取视频链接",
  "main": "youtube_search_extractor.py",
  "scripts": {
    "search": "python3 youtube_search_extractor.py",
    "install-deps": "npm install -g agent-browser && agent-browser install",
    "build": "echo 'Build completed'",
    "test": "python3 -m pytest tests/ -v",
    "clean": "rm -f *.html *.txt",
    "test-install": "python3 test_installation.py",
    "test-from-file": "python3 test_from_file.py",
    "test-no-daemon": "python3 test_without_daemon.py"
  },
  "keywords": [
    "youtube",
    "video",
    "search",
    "extraction",
    "scraper",
    "agent-browser"
  ],
  "author": "OpenClaw Team",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/openclaw/skills.git"
  },
  "bugs": {
    "url": "https://github.com/openclaw/skills/issues"
  },
  "homepage": "https://clawhub.com",
  "engines": {
    "node": ">=16.0.0"
  },
  "dependencies": {
    "agent-browser": "latest"
  },
  "devDependencies": {
    "pytest": "^7.0.0"
  }
}


path: test_from_file.py
content: #!/usr/bin/env python3
"""YouTube搜索结果视频链接提取器 - 从文件中提取链接的测试脚本"""

import sys
import os
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

from youtube_search_extractor import YouTubeSearchExtractor

def test_extraction_from_file():
    """测试从保存的HTML文件中提取链接"""
    print("🔍 测试从保存的HTML文件中提取链接...")
    
    # 使用之前保存的Hydrasynth搜索结果文件
    test_file = "hydrasynth_search_results.html"
    
    if not os.path.exists(test_file):
        print(f"❌ 测试文件 '{test_file}' 不存在")
        return False
    
    print(f"✅ 找到测试文件: {test_file}")
    
    try:
        with open(test_file, 'r', encoding='utf-8') as f:
            html_content = f.read()
            
        print(f"✅ 文件大小: {len(html_content)} 字符")
        
        extractor = YouTubeSearchExtractor()
        
        # 测试链接提取功能
        links = extractor.extract_video_links(html_content)
        
        if links:
            print(f"✅ 成功提取 {len(links)} 个视频链接")
            
            # 测试链接格式
            valid_links = [link for link in links if 
                         (link.startswith('https://www.youtube.com/watch?v=') or 
                          link.startswith('https://youtu.be/')) and 
                         len(link) > 20]
            
            print(f"✅ 格式正确的链接: {len(valid_links)} 个")
            
            if valid_links:
                print("\n📄 链接预览（前5个）:")
                for i, link in enumerate(valid_links[:5], 1):
                    print(f"{i}. {link}")
                
                # 保存到临时文件
                output_file = "hydrasynth_test_links"
                timestamp = __import__('datetime').datetime.now().strftime("%Y-%m-%d %H:%M:%S")
                
                with open(f"{output_file}_links.txt", 'w', encoding='utf-8') as f:
                    f.write(f"# YouTube搜索结果：'hydrasynth 实战应用' ({timestamp})\n")
                    f.write(f"# 找到 {len(valid_links)} 个相关视频\n\n")
                    
                    for i, link in enumerate(valid_links, 1):
                        f.write(f"{i}. {link}\n")
                
                print(f"\n✅ 链接已保存到: {output_file}_links.txt")
                
                return True
            else:
                print("❌ 提取的链接格式无效")
                return False
                
        else:
            print("❌ 未找到视频链接")
            return False
            
    except Exception as e:
        print(f"❌ 提取过程中出错: {e}")
        import traceback
        print(traceback.format_exc())
        return False

def test_with_sample_html():
    """测试使用示例HTML内容"""
    print("\n🔍 测试使用示例HTML内容...")
    
    sample_html = '''
    <div class="yt-lockup-content">
        <h3 class="yt-lockup-title">
            <a href="/watch?v=O37_qc3jhsc" title="HYDRASYNTH 实战应用 - 合成器教程">HYDRASYNTH 实战应用 - 合成器教程</a>
        </h3>
    </div>
    <div class="yt-lockup-content">
        <h3 class="yt-lockup-title">
            <a href="/watch?v=t0Ic87OLHRE" title="Hydrasynth 音色设计实战">Hydrasynth 音色设计实战</a>
        </h3>
    </div>
    <div class="yt-lockup-content">
        <h3 class="yt-lockup-title">
            <a href="https://youtu.be/NB5D34KDMxs" title="Hydrasynth 演奏技巧">Hydrasynth 演奏技巧</a>
        </h3>
    </div>
    '''
    
    try:
        extractor = YouTubeSearchExtractor()
        
        links = extractor.extract_video_links(sample_html)
        
        if links:
            print(f"✅ 成功提取 {len(links)} 个视频链接")
            
            for i, link in enumerate(links, 1):
                print(f"{i}. {link}")
                
            return True
        else:
            print("❌ 未找到视频链接")
            return False
            
    except Exception as e:
        print(f"❌ 测试过程中出错: {e}")
        import traceback
        print(traceback.format_exc())
        return False

def main():
    """主函数"""
    print("=== YouTube Search Extractor 从文件中提取链接的测试 ===")
    
    if not os.path.exists('youtube_search_extractor.py'):
        print("❌ 请在正确的目录下运行")
        return False
    
    success = True
    
    # 测试从文件中提取
    if os.path.exists("hydrasynth_search_results.html"):
        if not test_extraction_from_file():
            success = False
    else:
        print("⚠️  未找到 hydrasynth_search_results.html，跳过从文件中提取的测试")
    
    # 测试示例HTML
    if not test_with_sample_html():
        success = False
    
    if success:
        print("\n🎉 所有测试通过！从文件中提取链接的功能正常")
        return True
    else:
        print("\n❌ 测试失败，请检查上述错误信息")
        return False

if __name__ == "__main__":
    success = main()
    sys.exit(0 if success else 1)


path: test_installation.py
content: #!/usr/bin/env python3
"""YouTube搜索结果视频链接提取器 - 安装验证脚本"""

import subprocess
import sys
import os

def check_agent_browser_installed():
    """检查agent-browser是否已安装"""
    print("🔍 检查agent-browser安装...")
    
    try:
        result = subprocess.run(['agent-browser', '--version'], capture_output=True, text=True)
        if result.returncode == 0:
            version = result.stdout.strip()
            print(f"✅ agent-browser已安装，版本: {version}")
            return True
        else:
            print(f"❌ agent-browser未安装，返回码: {result.returncode}")
            return False
            
    except FileNotFoundError:
        print("❌ agent-browser未找到，请先安装")
        return False
    except Exception as e:
        print(f"❌ 检查agent-browser安装时出错: {e}")
        return False

def test_basic_functionality():
    """测试基本功能"""
    print("\n🔍 测试基本功能...")
    
    test_keyword = "python tutorial"
    test_output = "test_python"
    
    try:
        # 运行简单搜索
        result = subprocess.run(
            ['python3', 'youtube_search_extractor.py', test_keyword, test_output, '--wait-time', '3', '--max-links', '3'],
            capture_output=True, text=True, timeout=60
        )
        
        if result.returncode == 0:
            print("✅ 测试搜索成功")
            
            # 检查输出文件
            if os.path.exists(f"{test_output}.html") and os.path.getsize(f"{test_output}.html") > 0:
                print(f"✅ HTML文件已生成: {test_output}.html")
            else:
                print(f"⚠️  HTML文件未生成或为空: {test_output}.html")
            
            if os.path.exists(f"{test_output}_links.txt") and os.path.getsize(f"{test_output}_links.txt") > 0:
                print(f"✅ 链接文件已生成: {test_output}_links.txt")
                
                # 检查链接格式
                with open(f"{test_output}_links.txt", 'r', encoding='utf-8') as f:
                    content = f.read()
                    
                link_count = content.count("https://www.youtube.com/watch?v=")
                if link_count >= 3:
                    print(f"✅ 找到 {link_count} 个视频链接")
                else:
                    print(f"⚠️  只找到 {link_count} 个视频链接")
                    
            return True
            
        else:
            print(f"❌ 测试搜索失败，返回码: {result.returncode}")
            if result.stdout:
                print("标准输出:")
                print(result.stdout)
            if result.stderr:
                print("错误输出:")
                print(result.stderr)
            return False
            
    except subprocess.TimeoutExpired:
        print("❌ 测试超时")
        return False
    except Exception as e:
        print(f"❌ 测试时出错: {e}")
        import traceback
        print(traceback.format_exc())
        return False

def cleanup_test_files():
    """清理测试文件"""
    test_files = ['test_python.html', 'test_python_links.txt']
    for file_name in test_files:
        try:
            if os.path.exists(file_name):
                os.remove(file_name)
                print(f"✅ 已删除测试文件: {file_name}")
        except Exception as e:
            print(f"❌ 删除文件时出错: {e}")

def main():
    """主函数"""
    print("=== YouTube Search Extractor 安装验证 ===")
    
    # 检查当前目录
    if not os.path.exists('youtube_search_extractor.py'):
        print("❌ 请在youtube-search-extractor目录下运行此脚本")
        return False
    
    # 检查依赖
    dependencies_ok = True
    
    if not check_agent_browser_installed():
        dependencies_ok = False
    
    if not dependencies_ok:
        print("\n❌ 依赖检查失败，请先安装所需依赖")
        print("\n安装命令:")
        print("npm install -g agent-browser")
        print("agent-browser install")
        return False
    
    # 测试功能
    print("\n=== 开始功能测试 ===")
    test_ok = test_basic_functionality()
    
    # 清理
    cleanup_test_files()
    
    if test_ok:
        print("\n🎉 安装成功！YouTube Search Extractor 已准备好使用")
        return True
    else:
        print("\n❌ 安装失败，请检查上述错误信息")
        return False

if __name__ == "__main__":
    success = main()
    sys.exit(0 if success else 1)


path: test_without_daemon.py
content: #!/usr/bin/env python3
"""YouTube搜索结果视频链接提取器 - 无守护进程模式测试脚本"""

import subprocess
import sys
import os
import time

def test_without_daemon():
    """测试无守护进程模式"""
    print("🔍 测试无守护进程模式...")
    
    test_keyword = "python tutorial"
    test_output = "test_python"
    
    try:
        # 关闭可能存在的浏览器实例
        subprocess.run(['agent-browser', 'close'], capture_output=True, text=True)
        
        # 使用独立会话测试
        result = subprocess.run(
            ['agent-browser', '--session', 'test-session', 'open', 
             f"https://www.youtube.com/results?search_query={test_keyword}"],
            capture_output=True, text=True, timeout=30
        )
        
        if result.returncode == 0:
            print("✅ 搜索页面已加载")
            
            # 等待页面加载
            time.sleep(3)
            
            # 获取页面内容
            result = subprocess.run(
                ['agent-browser', '--session', 'test-session', 'get', 'html', 'body'],
                capture_output=True, text=True, timeout=30
            )
            
            if result.returncode == 0 and len(result.stdout) > 10000:
                print("✅ 页面内容获取成功")
                
                # 保存页面内容
                with open(f"{test_output}.html", 'w', encoding='utf-8') as f:
                    f.write(result.stdout)
                
                print(f"✅ 页面内容已保存到: {test_output}.html")
                
                # 测试链接提取
                from youtube_search_extractor import YouTubeSearchExtractor
                extractor = YouTubeSearchExtractor()
                
                with open(f"{test_output}.html", 'r', encoding='utf-8') as f:
                    html_content = f.read()
                
                links = extractor.extract_video_links(html_content)
                
                if len(links) > 0:
                    print(f"✅ 成功提取 {len(links)} 个视频链接")
                    
                    # 保存链接列表
                    with open(f"{test_output}_links.txt", 'w', encoding='utf-8') as f:
                        timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
                        f.write(f"# YouTube搜索结果：'{test_keyword}' ({timestamp})\n")
                        f.write(f"# 找到 {len(links)} 个相关视频\n\n")
                        
                        for i, link in enumerate(links[:3], 1):
                            f.write(f"{i}. {link}\n")
                    
                    print(f"✅ 链接列表已保存到: {test_output}_links.txt")
                    return True
                
                else:
                    print("❌ 未找到视频链接")
                    return False
                    
            else:
                print(f"❌ 无法获取页面内容: {result.returncode}")
                if result.stderr:
                    print(f"错误信息: {result.stderr}")
                return False
                
        else:
            print(f"❌ 无法访问搜索页面: {result.returncode}")
            if result.stderr:
                print(f"错误信息: {result.stderr}")
            return False
            
    except subprocess.TimeoutExpired:
        print("❌ 操作超时")
        return False
    except Exception as e:
        print(f"❌ 测试时出错: {e}")
        import traceback
        print(traceback.format_exc())
        return False
        
    finally:
        # 清理资源
        try:
            subprocess.run(['agent-browser', '--session', 'test-session', 'close'], 
                         capture_output=True, text=True, timeout=10)
        except:
            pass

def cleanup_test_files():
    """清理测试文件"""
    test_files = ['test_python.html', 'test_python_links.txt']
    for file_name in test_files:
        try:
            if os.path.exists(file_name):
                os.remove(file_name)
        except:
            pass

def main():
    """主函数"""
    print("=== YouTube Search Extractor 无守护进程模式测试 ===")
    
    if not os.path.exists('youtube_search_extractor.py'):
        print("❌ 请在正确的目录下运行")
        return False
    
    print("⏳ 测试可能需要几秒钟，请耐心等待...")
    
    success = test_without_daemon()
    
    if success:
        print("\n✅ 测试成功！无守护进程模式功能正常")
        
        # 显示提取的链接预览
        if os.path.exists('test_python_links.txt'):
            print("\n📄 提取的链接列表预览:")
            with open('test_python_links.txt', 'r', encoding='utf-8') as f:
                print(f.read())
                
        cleanup_test_files()
        return True
    else:
        print("\n❌ 测试失败")
        cleanup_test_files()
        return False

if __name__ == "__main__":
    success = main()
    sys.exit(0 if success else 1)


path: tests/test_extractor.py
content: #!/usr/bin/env python3
"""YouTube搜索结果视频链接提取器 - 测试文件"""

import pytest
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from youtube_search_extractor import YouTubeSearchExtractor

class TestYouTubeSearchExtractor:
    """YouTube搜索结果视频链接提取器测试类"""
    
    def test_extractor_initialization(self):
        """测试提取器初始化"""
        extractor = YouTubeSearchExtractor()
        assert isinstance(extractor, YouTubeSearchExtractor)
        assert extractor.wait_time == 5
        assert extractor.max_links == 50
    
    def test_url_encoding(self):
        """测试URL编码功能"""
        extractor = YouTubeSearchExtractor()
        
        # 测试中文关键词编码
        chinese_text = "hydrasynth 实战应用"
        encoded = extractor._url_encode(chinese_text)
        assert isinstance(encoded, str)
        assert '%' in encoded  # 应该包含URL编码字符
        
        # 测试英文关键词编码
        english_text = "hydrasynth practical applications"
        encoded = extractor._url_encode(english_text)
        assert isinstance(encoded, str)
        
        # 测试包含特殊字符的关键词
        special_text = "openclaw tutorial 2024!@#$%^&*()"
        encoded = extractor._url_encode(special_text)
        assert isinstance(encoded, str)
        assert '!' not in encoded
        assert '@' not in encoded
    
    def test_video_link_extraction_from_sample_html(self):
        """测试从示例HTML中提取视频链接"""
        extractor = YouTubeSearchExtractor()
        
        # 示例HTML片段（包含真实的YouTube视频链接）
        sample_html = '''
        <div class="yt-lockup-content">
            <h3 class="yt-lockup-title">
                <a href="/watch?v=dQw4w9WgXcQ" title="Never Gonna Give You Up">Never Gonna Give You Up</a>
            </h3>
        </div>
        <div class="yt-lockup-content">
            <h3 class="yt-lockup-title">
                <a href="/watch?v=9bZkp7q19f0" title="Gangnam Style">Gangnam Style</a>
            </h3>
        </div>
        <div class="yt-lockup-content">
            <h3 class="yt-lockup-title">
                <a href="/watch?v=oHg5SJYRHA0" title="Rickroll">Rickroll</a>
            </h3>
        </div>
        '''
        
        links = extractor.extract_video_links(sample_html)
        
        assert len(links) == 3
        assert "https://www.youtube.com/watch?v=dQw4w9WgXcQ" in links
        assert "https://www.youtube.com/watch?v=9bZkp7q19f0" in links
        assert "https://www.youtube.com/watch?v=oHg5SJYRHA0" in links
    
    def test_extraction_from_real_sample_html(self):
        """测试从真实的YouTube搜索结果HTML中提取链接"""
        extractor = YouTubeSearchExtractor()
        
        # 真实的YouTube搜索结果HTML片段
        real_sample_html = '''
        <ytd-video-renderer class="style-scope ytd-item-section-renderer">
            <a id="video-title" class="yt-simple-endpoint style-scope ytd-video-renderer" 
               title="HYDRASYNTH 实战应用 - 合成器教程" 
               href="/watch?v=O37_qc3jhsc">
                <yt-formatted-string class="style-scope ytd-video-renderer">HYDRASYNTH 实战应用 - 合成器教程</yt-formatted-string>
            </a>
        </ytd-video-renderer>
        <ytd-video-renderer class="style-scope ytd-item-section-renderer">
            <a id="video-title" class="yt-simple-endpoint style-scope ytd-video-renderer" 
               title="Hydrasynth 音色设计实战" 
               href="/watch?v=t0Ic87OLHRE">
                <yt-formatted-string class="style-scope ytd-video-renderer">Hydrasynth 音色设计实战</yt-formatted-string>
            </a>
        </ytd-video-renderer>
        '''
        
        links = extractor.extract_video_links(real_sample_html)
        
        assert len(links) >= 2
        assert any("O37_qc3jhsc" in link for link in links)
        assert any("t0Ic87OLHRE" in link for link in links)
    
    def test_duplicate_links_handling(self):
        """测试重复链接处理"""
        extractor = YouTubeSearchExtractor()
        
        duplicate_html = '''
        <div class="yt-lockup-content">
            <h3 class="yt-lockup-title">
                <a href="/watch?v=dQw4w9WgXcQ" title="Never Gonna Give You Up">Never Gonna Give You Up</a>
            </h3>
        </div>
        <div class="yt-lockup-content">
            <h3 class="yt-lockup-title">
                <a href="/watch?v=dQw4w9WgXcQ" title="Never Gonna Give You Up">Never Gonna Give You Up</a>
            </h3>
        </div>
        '''
        
        links = extractor.extract_video_links(duplicate_html)
        
        assert len(links) == 1
        assert "https://www.youtube.com/watch?v=dQw4w9WgXcQ" in links
    
    def test_different_video_link_formats(self):
        """测试不同格式的视频链接提取"""
        extractor = YouTubeSearchExtractor()
        
        test_html = '''
        <!-- 标准YouTube链接 -->
        <a href="/watch?v=dQw4w9WgXcQ">视频1</a>
        <!-- 完整URL格式 -->
        <a href="https://www.youtube.com/watch?v=9bZkp7q19f0">视频2</a>
        <!-- youtu.be短链接 -->
        <a href="https://youtu.be/oHg5SJYRHA0">视频3</a>
        <!-- 带参数的链接 -->
        <a href="/watch?v=abc123&t=10s&list=PLabc">视频4</a>
        '''
        
        links = extractor.extract_video_links(test_html)
        
        assert len(links) == 4
        assert all('http' in link for link in links)
        assert "https://www.youtube.com/watch?v=dQw4w9WgXcQ" in links
        assert "https://www.youtube.com/watch?v=9bZkp7q19f0" in links
        assert "https://youtu.be/oHg5SJYRHA0" in links
        assert any("abc123" in link for link in links)

def test_command_line_arguments():
    """测试命令行参数解析"""
    from unittest.mock import patch
    
    with patch('sys.argv', [
        'youtube_search_extractor.py',
        'hydrasynth 实战应用',
        'hydrasynth_test',
        '--headless',
        '--wait-time', '10',
        '--max-links', '30'
    ]):
        with pytest.raises(SystemExit) as pytest_wrapped_e:
            import youtube_search_extractor
            youtube_search_extractor.main()
    
    assert pytest_wrapped_e.type == SystemExit
    # 这里会失败，因为我们没有模拟实际的网络请求

if __name__ == "__main__":
    pytest.main([__file__, '-v'])


path: youtube_search_extractor.py
content: #!/usr/bin/env python3
"""YouTube搜索结果视频链接提取器 - YouTube Search Extractor"""

import subprocess
import re
import sys
import argparse
from datetime import datetime
import time

class YouTubeSearchExtractor:
    """YouTube搜索结果视频链接提取器类"""
    
    def __init__(self, headless=True, wait_time=5, max_links=50):
        self.headless = headless
        self.wait_time = wait_time
        self.max_links = max_links
        self.agent_browser_cmd = ['agent-browser']
    
    def search_youtube(self, search_query):
        """
        使用agent-browser执行YouTube搜索
        """
        print(f"🔍 正在搜索: {search_query}")
        
        try:
            # 关闭现有的浏览器实例
            subprocess.run(self.agent_browser_cmd + ['close'], capture_output=True, text=True)
            
            # 打开搜索页面
            search_url = f"https://www.youtube.com/results?search_query={self._url_encode(search_query)}"
            print(f"📌 访问搜索页面: {search_url}")
            
            result = subprocess.run(
                self.agent_browser_cmd + ['open', search_url],
                capture_output=True, text=True, check=True
            )
            
            if result.returncode == 0:
                print("✅ 搜索页面已加载")
                
                # 等待页面加载
                print(f"⏳ 等待 {self.wait_time} 秒让页面加载...")
                time.sleep(self.wait_time)
                
                # 获取页面HTML内容
                result = subprocess.run(
                    self.agent_browser_cmd + ['get', 'html', 'body'],
                    capture_output=True, text=True, check=True
                )
                
                if result.returncode == 0:
                    print("✅ 页面内容获取成功")
                    return result.stdout
                
                else:
                    print(f"❌ 获取页面内容失败: {result.returncode}")
                    if result.stderr:
                        print(f"错误信息: {result.stderr}")
                    return None
                    
            else:
                print(f"❌ 访问搜索页面失败: {result.returncode}")
                if result.stderr:
                    print(f"错误信息: {result.stderr}")
                return None
                
        except subprocess.CalledProcessError as e:
            print(f"❌ 执行命令失败: {e}")
            if e.stderr:
                print(f"错误信息: {e.stderr}")
            return None
            
        except Exception as e:
            print(f"❌ 发生错误: {e}")
            import traceback
            print(traceback.format_exc())
            return None
    
    def extract_video_links(self, html_content):
        """
        从HTML内容中提取视频链接
        """
        print("🔍 正在提取视频链接...")
        
        video_links = []
        
        patterns = [
            r'href=["\'](/watch\?v=[\w-]+[^"\']*)["\']',
            r'href=["\'](https?://(?:www\.)?youtube\.com/watch\?v=[\w-]+[^"\']*)["\']',
            r'href=["\'](https?://(?:www\.)?youtu\.be/[\w-]+[^"\']*)["\']'
        ]
        
        for pattern in patterns:
            matches = re.findall(pattern, html_content)
            for match in matches:
                if match.startswith('/watch'):
                    full_url = f"https://www.youtube.com{match}"
                else:
                    full_url = match
                
                if 'v=' in full_url or 'youtu.be/' in full_url:
                    if '?' in full_url:
                        params = full_url.split('?')[1].split('&')
                        filtered_params = []
                        for param in params:
                            if param.startswith('v=') or param.startswith('pp='):
                                filtered_params.append(param)
                        
                        if filtered_params:
                            full_url = full_url.split('?')[0] + '?' + '&'.join(filtered_params)
                
                if full_url not in video_links:
                    video_links.append(full_url)
        
        print(f"✅ 找到 {len(video_links)} 个视频链接")
        return video_links[:self.max_links]
    
    def save_results(self, search_query, video_links, output_file):
        """
        保存搜索结果
        """
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        
        # 保存HTML内容
        try:
            with open(f"{output_file}.html", 'w', encoding='utf-8') as f:
                f.write(self.html_content)
            print(f"✅ 搜索结果HTML已保存到 {output_file}.html")
        except Exception as e:
            print(f"❌ 保存HTML失败: {e}")
        
        # 保存链接列表
        try:
            with open(f"{output_file}_links.txt", 'w', encoding='utf-8') as f:
                f.write(f"# YouTube搜索结果：'{search_query}' ({timestamp})\n")
                f.write(f"# 找到 {len(video_links)} 个相关视频\n")
                f.write(f"\n")
                
                for i, link in enumerate(video_links, 1):
                    f.write(f"{i}. {link}\n")
            
            print(f"✅ 视频链接已保存到 {output_file}_links.txt")
            return f"{output_file}_links.txt"
        except Exception as e:
            print(f"❌ 保存链接列表失败: {e}")
            return None
    
    def run_search(self, search_query, output_file):
        """
        完整的搜索和提取流程
        """
        print(f"🚀 开始搜索 '{search_query}'...")
        
        # 1. 执行搜索
        self.html_content = self.search_youtube(search_query)
        
        if not self.html_content:
            print("❌ 搜索失败，无法继续")
            return False
        
        # 2. 提取视频链接
        video_links = self.extract_video_links(self.html_content)
        
        if not video_links:
            print("⚠️  没有找到相关的视频链接")
            return False
        
        # 3. 保存结果
        output_path = self.save_results(search_query, video_links, output_file)
        
        if output_path:
            print(f"\n🎯 任务完成！结果已保存到 {output_path}")
            return True
        else:
            print("❌ 任务失败")
            return False
    
    def _url_encode(self, text):
        """URL编码"""
        import urllib.parse
        return urllib.parse.quote(text)

def main():
    """主函数"""
    parser = argparse.ArgumentParser(
        description="YouTube搜索结果视频链接提取器 - YouTube Search Extractor"
    )
    
    parser.add_argument(
        "search_query",
        help="搜索关键词（例如：'hydrasynth 实战应用'）"
    )
    
    parser.add_argument(
        "output_file",
        help="输出文件名（不包含扩展名）"
    )
    
    parser.add_argument(
        "--headless",
        action="store_true",
        help="使用无头浏览器模式"
    )
    
    parser.add_argument(
        "--wait-time",
        type=int,
        default=5,
        help="页面加载等待时间（秒，默认5秒）"
    )
    
    parser.add_argument(
        "--max-links",
        type=int,
        default=50,
        help="最大提取链接数量（默认50个）"
    )
    
    parser.add_argument(
        "--debug",
        action="store_true",
        help="启用详细输出"
    )
    
    args = parser.parse_args()
    
    # 创建提取器实例
    extractor = YouTubeSearchExtractor(
        headless=args.headless,
        wait_time=args.wait_time,
        max_links=args.max_links
    )
    
    # 执行搜索
    success = extractor.run_search(args.search_query, args.output_file)
    
    # 清理
    try:
        subprocess.run(['agent-browser', 'close'], capture_output=True, text=True)
    except:
        pass
    
    return 0 if success else 1

if __name__ == "__main__":
    sys.exit(main())


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