可以用AI指挥AI,帮你理清楚到底怎么写,闲着无聊给你用GPT整理了一个:
请实现一个用于个人学习和兴趣项目的 YouTube 浏览器自动化工具。
项目目标
使用 Playwright 或 Selenium 控制一个真实可见的浏览器,实现以下流程:
打开 YouTube。
输入用户提供的搜索关键词。
执行搜索。
等待搜索结果加载。
向下滚动指定次数。
收集当前页面中可见的视频搜索结果链接。
去重。
随机选择一个视频。
打开这个视频。
等待指定时间,或者保持浏览器打开让用户接管。
将本次选择结果写入本地日志。
优先使用 Python + Playwright sync API 。如果实现方更熟悉 Selenium,也可以使用 Selenium,但 Playwright 优先。
重要边界和限制
这个项目仅用于个人 hobby、学习浏览器自动化、内容探索。
默认行为应该是:
单次运行。
低频使用。
打开真实可见浏览器窗口。
用户可以随时观察和接管浏览器。
推荐技术栈
Python 3.11+
Playwright sync API
argparse
random
json / jsonlines
pytest
dataclasses 或 pydantic,可选
如果使用 Playwright,依赖可以类似:
pip install playwright pytest
playwright install chromium
CLI 设计
实现一个命令行入口,例如:
python main.py --keyword "lofi music" --scrolls 3 --watch-seconds 60 --seed 42 --keep-open
参数要求:
参数
是否必需
默认值
说明
--keyword
是
无
YouTube 搜索关键词
--scrolls
否
3
搜索结果页向下滚动次数
--watch-seconds
否
60
打开视频后等待多少秒
--seed
否
None
随机种子,用于复现随机选择
--keep-open
否
False
结束后是否保持浏览器打开
--headless
否
False
默认不要无头模式,只有用户明确指定才启用
--log-file
否
sessions.jsonl
日志文件路径
推荐文件结构
youtube_random_clicker/
README.md
requirements.txt
main.py
yt_browser.py
selector.py
logger.py
models.py
tests/
test_selector.py
test_logger.py
test_dedup.py
核心模块设计
1. main.py
负责:
解析 CLI 参数。
调用浏览器模块。
调用随机选择模块。
调用日志模块。
处理异常并输出清晰错误信息。
主流程:
parse args
-> launch browser
-> open youtube
-> search keyword
-> scroll results
-> extract video results
-> deduplicate results
-> select random video
-> open selected video
-> log session
-> wait watch-seconds
-> close browser or keep open
2. yt_browser.py
负责所有浏览器自动化逻辑。
需要实现的函数:
def launch_browser (headless: bool = False ):
...
def open_youtube (page ):
...
def search_youtube (page, keyword: str ):
...
def scroll_results (page, scrolls: int ):
...
def extract_video_results (page ) -> list [VideoResult]:
...
def open_video (page, video: VideoResult ):
...
建议 Playwright 选择器:
搜索框:
input[name="search_query"]
搜索结果视频:
ytd-video-renderer a#video-title
视频链接过滤:
href 包含 "/watch?v="
注意:
不要使用过深 DOM 路径。
不要依赖 nth-child。
YouTube 页面结构可能变化,所以代码要有清晰错误处理。
如果 cookie 弹窗出现,可以做简单、保守的处理;不要写复杂绕过逻辑。
等待搜索结果时,可以使用 page.wait_for_selector("ytd-video-renderer", timeout=15000)。
滚动可以使用 page.mouse.wheel(0, 1200)。
每次滚动后等待 1 到 2 秒即可,不要做“真人模拟”逻辑。
3. models.py
定义数据结构。
from dataclasses import dataclass
@dataclass
class VideoResult :
title: str
url: str
可以扩展字段:
@dataclass
class VideoResult :
title: str
url: str
video_id: str | None = None
4. selector.py
负责去重和随机选择,不依赖浏览器,方便单元测试。
需要实现:
def deduplicate_videos (videos: list [VideoResult] ) -> list [VideoResult]:
...
def select_random_video (videos: list [VideoResult], seed: int | None = None ) -> VideoResult:
...
行为要求:
如果 videos 为空,抛出清晰异常,例如 ValueError("No video results found")。
如果传入 seed,随机结果必须可复现。
去重按 URL 去重即可。
保持原始顺序,第一次出现的结果保留。
5. logger.py
负责写 JSONL 日志。
每次运行写入一行 JSON:
{
"timestamp" : "2026-07-08T13:30:00-07:00" ,
"keyword" : "lofi music" ,
"scrolls" : 3 ,
"seed" : 42 ,
"selected_title" : "Example Video Title" ,
"selected_url" : "https://www.youtube.com/watch?v=abc123" ,
"watch_seconds" : 60
}
需要实现:
def log_session (
log_file: str ,
keyword: str ,
scrolls: int ,
seed: int | None ,
selected_video: VideoResult,
watch_seconds: int ,
) -> None :
...
要求:
使用 UTF-8。
每条日志一行。
不覆盖旧日志。
ensure_ascii=False。
Playwright 实现参考逻辑
可以按下面的伪代码实现,但请整理成模块化代码:
from playwright.sync_api import sync_playwright
import random
import time
import json
from datetime import datetime
def run (keyword: str , scrolls: int = 3 , watch_seconds: int = 60 , seed: int | None = None ):
rng = random.Random(seed)
with sync_playwright() as p:
browser = p.chromium.launch(headless=False )
page = browser.new_page()
page.goto("https://www.youtube.com" , wait_until="domcontentloaded" )
search_box = page.locator("input[name='search_query']" )
search_box.fill(keyword)
search_box.press("Enter" )
page.wait_for_selector("ytd-video-renderer" , timeout=15000 )
for _ in range (scrolls):
page.mouse.wheel(0 , 1200 )
time.sleep(1.5 )
videos = page.locator("ytd-video-renderer a#video-title" ).evaluate_all("""
els => els.map(a => ({
title: a.innerText,
href: a.href
})).filter(v => v.href && v.href.includes('/watch?v='))
""" )
unique = []
seen = set ()
for v in videos:
if v["href" ] not in seen:
seen.add(v["href" ])
unique.append(v)
if not unique:
raise RuntimeError("No video results found" )
selected = rng.choice(unique)
page.goto(selected["href" ], wait_until="domcontentloaded" )
log = {
"timestamp" : datetime.now().isoformat(),
"keyword" : keyword,
"scrolls" : scrolls,
"seed" : seed,
"selected_title" : selected["title" ],
"selected_url" : selected["href" ],
"watch_seconds" : watch_seconds,
}
with open ("sessions.jsonl" , "a" , encoding="utf-8" ) as f:
f.write(json.dumps(log, ensure_ascii=False ) + "\n" )
time.sleep(watch_seconds)
browser.close()
错误处理要求
请处理以下情况:
keyword 为空:
YouTube 无法打开:
找不到搜索框:
搜索结果超时:
没有提取到视频:
输出 No video results found。
随机选择失败:
日志写入失败:
测试要求
单元测试
重点测试不依赖浏览器的部分。
test_selector.py
测试:
空列表时抛出 ValueError。
单个视频时返回该视频。
多个视频时能随机选择。
固定 seed 时结果可复现。
不同 seed 可能产生不同结果。
test_dedup.py
测试:
重复 URL 被去重。
不重复 URL 保留。
空列表返回空列表。
去重后保留第一次出现的视频标题。
test_logger.py
测试:
能创建 JSONL 日志文件。
能追加多条日志。
每行都是合法 JSON。
中文关键词和标题不会被 ASCII 转义。
日志字段完整。
浏览器测试
浏览器 E2E 测试可以少量实现,不要在 CI 中高频运行。
建议加一个手动测试脚本或 pytest marker:
pytest -m browser
浏览器测试覆盖:
能打开 YouTube。
能找到搜索框。
搜索后能看到至少一个 ytd-video-renderer。
能提取至少一个视频链接。
README 要求
README 至少包含:
项目说明。
合规边界说明。
安装步骤。
Playwright 浏览器安装步骤。
CLI 使用示例。
参数说明。
日志格式。
测试运行方式。
常见问题。
README 中需要明确写:
This project is for personal learning and hobby use only.
It is not a view bot.
It does not automate likes, comments, subscriptions, accounts, proxies, CAPTCHA bypass, stealth, or repeated watch loops.
推荐 README 示例命令
pip install -r requirements.txt
playwright install chromium
python main.py --keyword "lofi music" --scrolls 3 --watch-seconds 60
python main.py --keyword "python tutorial" --scrolls 2 --watch-seconds 30 --seed 42
python main.py --keyword "jazz piano" --keep-open
验收标准
实现完成后,项目应满足:
可以通过 CLI 输入关键词。
可以启动真实可见 Chromium 浏览器。
可以打开 YouTube。
可以搜索关键词。
可以滚动搜索结果页。
可以提取视频结果。
可以去重。
可以随机选择一个视频。
可以打开该视频。
可以等待指定秒数。
可以选择保持浏览器打开。
可以写入 JSONL 日志。
核心纯函数有 pytest 测试。
README 清楚说明安装、使用和限制。
不要过度设计
请先做 MVP,不要加入复杂功能。
暂时不要实现:
Web UI
数据库
Docker
多线程
定时任务
账号登录
代理
云端部署
大规模任务队列
历史推荐系统
优先交付一个简单、可运行、可读、可测试的本地项目。