video-pipeline
v1.3.3
Published
视频下载、转码、文本识别、AI 关键词分析一体化流程 CLI 工具
Maintainers
Readme
视频处理流水线 (Video Pipeline)
基于 process_videos.js (Node.js) 或 process_videos.py (Python),一键完成:yt-dlp 下载 → ffmpeg 转码 → whisper 识别 → AI 关键词归纳 → 写回 Excel。
五种使用方式,覆盖不同场景:
| 模式 | 输入 | 跳过步骤 | 适用场景 | |------|------|---------|----------| | Excel 批量 | Excel 行(多视频) | — | 批量处理全流程 | | --url 直链 | 单个视频 URL | — | 临时下载单个视频 | | --input 本地 | 本地视频/音频文件 | 下载 | 处理已有文件 | | --content 纯文本 | 文件路径或内联文本 | 下载+转码+识别 | 已有文本直接分析 | | --content-column | Excel 列的已有文本 | 下载+转码+识别 | 批量分析 Excel 中的文本 |
安装方式
Node.js 版本(推荐)
# 全局安装
npm install -g video-pipeline
# 使用后可直接调用
video-pipeline --helpPython 版本
# 克隆或下载脚本
git clone https://github.com/GuoSirius/yt-dlp_ffmpeg_whisper_memo-ai.git
cd yt-dlp_ffmpeg_whisper_memo-ai
# 安装 Python 依赖
pip install pandas openpyxl requests python-dotenv questionary环境依赖
必装工具
| 工具 | 版本要求 | 安装方式 | 用途 |
|------|-----------|----------|------|
| Python | 3.9+ | python.org | 脚本运行 |
| yt-dlp | 最新 | pip install yt-dlp 或 GitHub Release | 视频下载 |
| ffmpeg + ffprobe | 4.0+ | ffmpeg.org 或 winget install ffmpeg | 音频转码 + 时长检测 |
验证安装:在终端执行
yt-dlp --version、ffmpeg -version、ffprobe -version,确保均在 PATH 中。
必装 Node.js(YouTube n-sig 挑战)
YouTube 要求 JS 运行时解开 n-sig 挑战,否则无法提取视频格式。
| 方式 | 安装命令 |
|------|----------|
| Node.js(推荐) | nodejs.org 下载 LTS 版,安装后 node --version 验证 |
| Deno | winget install DenoLand.Deno 或 deno.com |
脚本默认使用
--js-runtimes node,如果你装的是 deno,修改.env中YOUTUBE_JS_RUNTIMES=deno。
Python 依赖
pip install pandas openpyxl requests python-dotenv questionary
questionary为可选依赖(交互式确认时使用),建议一并安装。
环境变量配置(.env)
从 v2 开始,所有路径、字段映射、平台参数均通过 .env 文件配置。 这意味着同一套脚本可以直接用于其他 Excel 文件,只需修改 .env 中的值即可。
# 首次使用:复制模板
cp .env.example .env
# 编辑 .env 适配你的 Excel 结构
# 详见 .env.example 中的注释核心配置项说明:
| 分类 | 变量 | 说明 |
|------|------|------|
| 输入 | EXCEL_FILE | Excel 文件路径 |
| 列映射 | COL_ID / COL_TITLE / COL_CONTENT / COL_KEYWORDS | 唯一标识列 / 标题列 / 识别文本输出列 / AI 关键词输出列 |
| 列映射 | COL_TENCENT / COL_BILIBILI / COL_YOUTUBE / COL_YOUKU | 各平台视频 ID 所在列 |
| Sheet | VIDEO_SHEETS | 逗号分隔需要处理的 sheet(留空则全部) |
| 平台 | PLATFORM_PRIORITY | 平台重试优先级 |
| 平台 | {平台}_URL_TPL | URL 模板(如 YOUTUBE_URL_TPL=https://youtu.be/{youtube}) |
| 平台 | {平台}_COOKIES_FROM_BROWSER | 从浏览器直读 cookie(推荐 Firefox,替代手动导出文件) |
| 平台 | {平台}_COOKIE_FILE | cookie 文件路径(备用方案,需定期更新) |
| 平台 | {平台}_PROXY | 代理地址(如 http://127.0.0.1:7897,Clash Verge) |
| 平台 | {平台}_FORMAT / {平台}_USER_AGENT | 下载格式 / UA |
| 平台 | {平台}_JS_RUNTIMES / {平台}_REMOTE_COMPONENTS | JS 运行时 / 远程组件(YouTube n-sig 求解) |
| 识别 | WHISPER_BACKEND | local(本地 openai-whisper)或 service(whisper.cpp server) |
| 识别 | WHISPER_* 系列 | 详见下方「Whisper 语音识别」章节——分共享(4) / 服务(2) / 本地(12) 三组,共 18 个变量 |
| 工具 | YTDLP / FFMPEG / FFPROBE | 外部工具路径 |
| AI 分析 | AI_ENABLED | true 启用 / false 跳过(默认 true) |
| AI 分析 | AI_API_KEY / AI_BASE_URL / AI_MODEL | OpenAI 兼容 API 配置 |
| AI 分析 | AI_PROMPT_TPL | 提示词模板,必须包含 {content} 占位符。支持文件路径(try-file-first),CLI 覆盖:--ai-prompt <text|path>(CLI > .env > 内置默认) |
| AI 分析 | AI_TEMPERATURE | AI 推理温度 (0.0~2.0) |
.env 配置项变更权限
.env.example 中每个配置项都带有变更权限标记,含义如下:
| 标记 | 含义 | 涵盖的配置项 | 示例 |
|------|------|-------------|------|
| 【自由】 | 值可随意改为任意合法内容 | 路径、开关、数字、字符串、URL、UA、格式参数等 | EXCEL_FILE, YOUTUBE_PROXY, WHISPER_MODEL |
| 【调序】 | 只能从固定集合中增减/排序,不能用集合外的值 | PLATFORM_PRIORITY | 只能包含 bilibili / youtube / tencent / youku |
| 【关联】 | 值需与脚本内约定的 Key 名一致 | URL 模板中的 {占位符} | {youtube} 必须跟 COL_YOUTUBE 的后缀一致 |
| 【固定】 | 除非 Excel 列名或脚本内部逻辑改变,否则不应修改 | 列名映射 | COL_ID=extra.id、COL_TITLE=title 等 |
最容易混淆的是【调序】:
PLATFORM_PRIORITY可以调整顺序、增减条目,但只能用脚本已定义的 4 个 key,新增tiktok、douyin等无效 key 会导致脚本无法识别。
Whisper 语音识别
支持两种后端,通过 WHISPER_BACKEND 切换。所有 Whisper 相关环境变量分三组管理:
🔷 共享变量(两种后端均生效)
| 变量 | 默认值 | 说明 |
|------|--------|------|
| WHISPER_BACKEND | local | 后端选择:local 或 service |
| WHISPER_TEMPERATURE | 0.0 | 采样温度(0.0=贪婪解码,推荐中文识别) |
| WHISPER_TEMPERATURE_INC | 0.2 | 温度递减步长(fallback 时温度递增步长) |
| WHISPER_OUTPUT_FORMAT | json | 输出格式:json / txt / srt / vtt / tsv |
🔶 服务模式独有(WHISPER_BACKEND=service)
需要本地或远程运行 whisper.cpp server,监听 http://127.0.0.1:9588。
| 变量 | 默认值 | 说明 |
|------|--------|------|
| WHISPER_SERVICE | http://127.0.0.1:9588 | whisper.cpp server 地址 |
| WHISPER_SERVICE_MODEL | (模型文件路径) | 模型文件路径,如 models/ggml-base.bin;留空=使用当前已加载模型 |
API 端点:
POST /inference← 上传 wav 文件,返回识别文本(参数: file / temperature / temperature_inc / response_format)POST /load← 切换模型(参数: model=模型文件路径),脚本首次识别时自动调用,同一模型只加载一次(缓存)
注意:本地模式和服务模式不能混用。
WHISPER_MODEL/WHISPER_LANGUAGE等本地变量在服务模式下不生效,反之亦然。
🔸 本地模式独有(WHISPER_BACKEND=local)
需安装 openai-whisper:pip install openai-whisper,脚本直接调用 whisper CLI。
| 变量 | 默认值 | 说明 |
|------|--------|------|
| WHISPER_TASK | transcribe | 任务类型: transcribe / translate |
| WHISPER_MODEL | medium | 模型大小:tiny / base / small / medium / large-v3 / turbo |
| WHISPER_LANGUAGE | zh | 语言代码(zh/en/ja 等),空=多语言自动检测 |
| WHISPER_DEVICE | cpu | 推理设备:cpu / cuda |
| WHISPER_MODEL_DIR | 空 | 模型缓存目录,空=~/.cache/whisper(或 $XDG_CACHE_HOME/whisper) |
| WHISPER_BEAM_SIZE | 5 | Beam search 宽度(越大越准但越慢,建议 5) |
| WHISPER_BEST_OF | 5 | 候选采样数(非零时启用温度采样) |
| WHISPER_INITIAL_PROMPT | 生物医学 90+ 术语 | 首段音频提示词,已预填细胞/免疫/分子/蛋白/实验技术等高频术语,空格分隔。支持文件路径(try-file-first):值指向存在的文件则读取内容。CLI 覆盖:--whisper-initial-prompt <text|path>(CLI > .env > 内置默认) |
| WHISPER_CONDITION_ON_PREV | False | 推荐 False:每段独立解码,避免长视频错误累积;True=前段文本传入当前段(仅适合短音频<30分钟) |
| WHISPER_FP16 | False | FP16 推理(需 CUDA/GPU,CPU 上无效) |
| WHISPER_THREADS | 0 | CPU 线程数(0=自动检测) |
| WHISPER_EXTRA_ARGS | 空 | 额外 whisper CLI 参数(shell 字符串,如 --beam_size 5 --verbose),追加到命令末尾。同名参数自动去重(extra 覆盖已有)。CLI 覆盖:--whisper-extra-args(CLI > .env) |
选择建议:默认
False(每段独立,避免长视频错误累积);短音频(<30min)单人连贯语音可设True提升连贯性。专有名词多的场景可配合INITIAL_PROMPT提升准确率,详细示例见.env.example。需要微调 whisper 行为时可通过WHISPER_EXTRA_ARGS传入额外 CLI 参数(如--beam_size 10 --verbose),同名参数自动去重。
目录结构
├── process_videos.js # Node.js 主流程脚本(推荐)
├── process_videos.py # Python 主流程脚本(备选)
├── package.json # Node.js 项目配置(npm 包)
├── .env.example # 环境变量模板(可提交 Git)
├── .env # 实际环境变量(已 gitignore,按需修改)
├── data/ # 数据源目录
│ └── export_2026-06-10_split.xlsx # Excel 数据源
├── cookies/ # 站点 cookie 文件
│ ├── bilibili.txt # B站 cookie(Netscape 格式)
│ └── youtube.txt # YouTube cookie 备用(Firefox 直读方案不需要)
├── output/ # 输出根目录(可通过环境变量覆盖)
│ ├── downloads/ # yt-dlp 下载输出(mp4)
│ │ ├── youtube/ # 按平台分目录
│ │ └── bilibili/
│ ├── transcoded/ # ffmpeg 转码输出(wav 16kHz mono)
│ │ ├── youtube/
│ │ └── bilibili/
│ └── reports/ # 执行报告(按 sheet/平台分目录)
│ ├── YouTube视频/
│ │ ├── report_YYYYMMDD_HHMMSS.json # JSON 报告(机器可读,用于重跑)
│ │ └── tasks/ # 人类可读文本摘要
│ │ ├── 2143.txt
│ │ └── ...
│ ├── 普诺赛中文站/
│ │ ├── report_YYYYMMDD_HHMMSS.json
│ │ └── tasks/
│ │ └── ...
│ ├── youtube/ # --url 模式按平台名分目录
│ │ ├── report_YYYYMMDD_HHMMSS.json
│ │ └── tasks/
│ ├── local/ # --input 模式默认目录
│ │ ├── report_YYYYMMDD_HHMMSS.json
│ │ └── tasks/
│ └── content/ # --content 模式固定目录
│ ├── report_YYYYMMDD_HHMMSS.json
│ └── tasks/
├── scripts/ # 辅助脚本
│ ├── release.js # 版本发布脚本
│ └── regenerate-changelog.js # CHANGELOG 重建脚本
├── .github/ # GitHub Actions 工作流
├── .husky/ # Git hooks(commit 消息检查)
├── node_modules/ # Node.js 依赖(已 gitignore)
├── CHANGELOG.md # 版本变更记录
├── README.md # 使用文档
└── LICENSE # MIT 许可证Cookie 设置(首次使用必须)
YouTube(推荐:Firefox 浏览器直读)
yt-dlp 可直接从 Firefox 浏览器读取 cookie,无需手动导出:
- 用 Firefox 浏览器登录 youtube.com
- 在
.env中设置YOUTUBE_COOKIES_FROM_BROWSER=firefox - 脚本自动通过
--cookies-from-browser firefox读取
Firefox 在 Windows 上的 cookie 加密格式 yt-dlp 可稳定解密,只要浏览器保持登录态即可。 Chrome/Edge 在 Windows 上 DPAPI 解密已知失败,不推荐使用。
YouTube(备用:手动导出文件)
如果无法使用 Firefox,可手动导出 cookie 文件:
- Chrome 安装扩展 Get cookies.txt LOCALLY
- 访问 youtube.com 并登录
- 点击扩展图标 → Export → 保存为
cookies/youtube.txt - 在
.env中注释掉YOUTUBE_COOKIES_FROM_BROWSER,启用YOUTUBE_COOKIE_FILE=cookies/youtube.txt
⚠️ YouTube cookie 有效期约 48 小时,过期后需重新导出。下载时如果报
cookies does no longer seem to be valid,说明 cookie 已失效。优先用 Firefox 方案,免维护。
B站(bilibili)
方案 A(推荐):直接从 Firefox 浏览器读 cookie
- 用 Firefox 浏览器登录 bilibili.com
- 在
.env中设置BILIBILI_COOKIES_FROM_BROWSER=firefox - 脚本自动通过
--cookies-from-browser firefox读取
Firefox cookie 直读方案同样适用于 B站,无需手动导出。
方案 B(备用):从文件读取 cookie
- Chrome 安装扩展 Get cookies.txt LOCALLY
- 访问 bilibili.com 并登录
- 点击扩展图标 → Export → 保存为
cookies/bilibili.txt - 在
.env中注释掉BILIBILI_COOKIES_FROM_BROWSER,启用BILIBILI_COOKIE_FILE=cookies/bilibili.txt
使用方法
单条测试
# 下载 + 转码 + 识别 + AI分析,指定 sheet + extra.id
node process_videos.js --sheet "YouTube视频" --id 2143
# 或 Python 版本
python process_videos.py --sheet "YouTube视频" --id 2143
# 只跑下载
node process_videos.js --sheet "普诺赛中文站" --id 16 --step download
# 或 Python 版本
python process_videos.py --sheet "普诺赛中文站" --id 16 --step download
# 只跑转码(需要已有下载文件)
node process_videos.js --sheet "普诺赛中文站" --id 16 --step transcode
# 只跑识别(需要已有转码文件)
node process_videos.js --sheet "普诺赛中文站" --id 16 --step transcribe
# 只跑 AI 分析(需要已有识别文本)
node process_videos.js --sheet "普诺赛中文站" --id 16 --step analyze
# 强制重新下载(忽略已有文件)
node process_videos.js --sheet "YouTube视频" --id 2143 --force批量全量
# 全量执行(2 个并发,失败重试 3 次)
node process_videos.js --concurrency 2 --retry 3
# 只跑某一 sheet
node process_videos.js --sheet "YouTube视频" --concurrency 2 --retry 3
# 先干跑预览
node process_videos.js --dry-run
# Excel 数据量大时,偏移+限量调试
node process_videos.js --offset 10 --limit 5 --dry-run # 跳过前10条,预览5条
node process_videos.js --limit 3 --concurrency 1 # 只处理前3条重跑失败
# 第一次跑完后生成 reports/{sheet名称}/report_xxx.json
# 查看失败项:
node process_videos.js --retry-failed reports/YouTube视频/report_20260610_143000.json --dry-run
# 重跑:
node process_videos.js --retry-failed reports/YouTube视频/report_20260610_143000.json --concurrency 2 --retry 3超时控制(防止任务卡死)
每个步骤都有独立超时,超时后自动 kill 子进程、标记失败并继续执行后续任务:
# 自定义超时(单位秒,设为 0 表示不限制)
node process_videos.js \
--download-timeout 1800 \ # 下载 30 分钟
--transcode-timeout 1200 \ # 转码 20 分钟
--transcribe-timeout 0 \ # 识别 不限制
--analyze-timeout 300 # AI 分析 5 分钟
# 默认值:下载 1800s / 转码 1200s / 识别 0(不限制) / AI 分析 300s- 超时属于可重试错误,会触发指数退避重试(
--retry控制次数) - 无论超时多少次,不会阻塞其他并发任务,失败项会记录到报告
- 超时失败的任务可用
--retry-failed单独重跑
直接指定 URL 下载
# 直接指定视频链接,自动识别平台(支持标准链接、短链接、内嵌链接)
node process_videos.js --url "https://www.youtube.com/watch?v=zzJmKPX8a3c"
python process_videos.py --url "https://www.bilibili.com/video/BV1xx411c7mD"
# 指定输出文件名(不含扩展名)
node process_videos.js --url "https://youtu.be/zzJmKPX8a3c" --name "产品介绍"
# 只执行部分步骤
node process_videos.js --url "https://www.youtube.com/watch?v=zzJmKPX8a3c" --step transcode支持的 URL 格式:
- YouTube: 标准页、短链接、Shorts、内嵌页、直播
- B站: 标准页(BV/av号)、短链接、内嵌页、移动端
- 腾讯视频: 标准页、内嵌页、移动端
- 优酷: 标准页
文件命名规则:
- 默认:
{平台}_{视频ID}(如youtube_zzJmKPX8a3c) - 自定义:通过
--name指定(如--name "产品介绍") - 冲突处理:自动提示选择(覆盖 / 跳过 / 自定义名称)
处理本地文件
# 指定本地视频文件,跳过下载,直接转码→识别→分析
node process_videos.js --input "downloads/产品介绍.mp4"
python process_videos.py --input "downloads/产品介绍.mp4"
# 指定输出文件名
node process_videos.js --input "downloads/产品介绍.mp4" --name "产品介绍_分析"
# 只执行部分步骤
node process_videos.js --input "downloads/产品介绍.mp4" --step analyze文件校验:
- 检查文件是否存在
- 检查文件格式是否支持(视频/音频)
- 检查是否可以正常读取
- 校验失败会提示错误并退出
处理纯文本内容(跳过视频步骤)
如果你已经有了一段文本内容(比如爬虫爬取的、之前识别好的、或者从其他途径获取的),可以直接做 AI 分析,跳过下载、转码、识别三个步骤:
# ═══════════ --content 模式:纯文本 AI 分析 ═══════════
# 从文件读取内容,自动用文件名作为输出名
node process_videos.js --content "data/article.txt"
python process_videos.py --content "data/article.txt"
# 直接提供内联文本,自动取前 32 字符作为输出名
node process_videos.js --content "这是一段需要分析的内容..."
python process_videos.py --content "这是一段需要分析的内容..."
# 指定输出文件名(--name)
node process_videos.js --content "data/article.txt" --name "文章分析"
python process_videos.py --content "data/article.txt" --name "文章分析"
# 配合 --dry-run 预览
node process_videos.js --content "data/article.txt" --dry-run输出文件命名规则:
- 指定了
--name→ 使用--name的值 - 内容是文件路径 → 使用文件名(不含扩展名)
- 内容是内联文本 → 使用前 32 个字符
输出位置: output/reports/content/tasks/{name}.txt + output/reports/content/report_xxx.json
Excel 列文本批量 AI 分析
当 Excel 某列已经存好了文本内容(比如之前爬虫爬取的),可以批量对这些文本做 AI 关键词分析:
# ═══════════ --content-column 模式:批量 AI 分析 ═══════════
# 对 Excel 中 "content" 列的文本逐行做 AI 关键词分析,结果写回 "keywords" 列
node process_videos.js --content-column "content"
# 指定其他列名
node process_videos.js --content-column "爬取文本"
# 指定特定 sheet
node process_videos.js --sheet "普诺赛中文站" --content-column "content"
# 配合 --dry-run 预览
node process_videos.js --content-column "content" --dry-run
# 配合 --offset / --limit 调试
node process_videos.js --content-column "content" --offset 0 --limit 3
node process_videos.js --content-column "content" --concurrency 2 --retry 2注意:
--content-column模式自动设置--step analyze(仅 AI 分析),不会触发下载/转码/识别。 文本为空的行会自动跳过。 分析结果写入 Excel 的keywords列(由COL_KEYWORDS环境变量指定)。
工具预检(执行前自动检测)
每次执行任务前,脚本会自动检测本次涉及步骤所需的工具/服务是否可用:
| 步骤 | 检测项 | 不可用时行为 |
|------|--------|-------------|
| download | yt-dlp 可调用 | 提示用户,输入 yes 继续 / 其他取消 |
| transcode | ffmpeg + ffprobe 可调用 | 同上 |
| transcribe | whisper 服务/本地 CLI 可连通 | 同上 |
| analyze | AI_ENABLED=true 且 API 配置完整 | 同上 |
- dry-run 模式下同样展示检测结果(但不中断执行)
- 所有模式(正常执行、重跑失败、单步运行)均执行预检
- 即使工具不可用,用户仍可选择强制继续(但相应步骤大概率失败)
参数说明
| 参数 | 类型 | 默认值 | 说明 |
|------|------|---------|------|
| --sheet <name> | str | 全部 | 指定 sheet 名称 |
| --id <id> | str | — | 指定 extra.id 或 title(单条测试) |
| --offset <n> | int | 0 | 跳过前 N 条任务(从 0 开始),适合调试大量数据 |
| --limit <n> | int | 0 | 最多处理 N 条任务,0 表示无限制 |
| --step <step> | str | 全跑 | 只执行某步:download / transcode / transcribe / analyze |
| --force | flag | off | 强制重做下载+转码,忽略已有文件 |
| --concurrency <n> | int | 1 | 并发数,建议 2~3 |
| --retry <n> | int | 0 | 每步失败最大重试次数 |
| --retry-delay <n> | float | 5 | 重试间隔基数(秒),指数退避 5→10→20 |
| --download-timeout <n> | int | 1800 | 单个下载任务最长执行时间(秒),0=不限制 |
| --transcode-timeout <n> | int | 1200 | 单个转码任务最长执行时间(秒),0=不限制 |
| --transcribe-timeout <n> | int | 0 | 单个识别任务最长执行时间(秒),0=不限制 |
| --analyze-timeout <n> | int | 300 | 单个 AI 分析任务最长执行时间(秒),0=不限制 |
| --dry-run | flag | off | 干跑模式,只列任务不执行 |
| --retry-failed <path> | path | — | 从报告 JSON 重跑失败项(如 reports/YouTube视频/report_xxx.json) |
| --init | flag | off | 复制 .env.example 到当前目录并重命名为 .env |
| --file <path> | path | — | 指定 Excel 文件路径(优先级高于 EXCEL_FILE 环境变量) |
| --input <path> | path | — | 指定本地视频文件路径(跳过下载,直接转码→识别→分析) |
| --url <url> | str | — | 直接指定视频下载链接(跳过 Excel),支持标准链接和内嵌链接 |
| --content <text 或 path> | str | — | 直接提供文本内容(文件路径或内联文本),跳过下载/转码/识别,仅做 AI 分析 |
| --content-column <col> | str | — | Excel 模式:指定包含已有文本的列名,批量做 AI 分析(自动设 --step analyze) |
| --name <name> | str | — | 指定输出文件名,不含扩展名(与 --url / --input / --content 配合使用) |
| --env-file <path> | path | .env | 指定要加载的 .env 文件路径 |
| --whisper-initial-prompt <text\|path> | str | .env | Whisper 初始提示词(文本或文件路径,CLI 优先级最高) |
| --ai-prompt <text\|path> | str | .env | AI 分析提示词模板(文本或文件路径,CLI 优先级最高) |
| --whisper-extra-args <args> | str | .env | Whisper 额外参数(shell 字符串,如 "--beam_size 5",最高优先级且自动去重) |
重试规则
| 可重试 | 不重试 | |----------|----------| | 网络超时、连接拒绝 | HTTP 404 / 403 / 401 | | yt-dlp 下载中断 | 视频已删除 / 私有 | | whisper 服务超时 | 无效 URL、文件不存在 | | 步骤级超时(任务卡死) | 参数错误(ValueError/TypeError) |
智能跳过与自动重转码
脚本默认不会重复处理已有文件,但会在以下情况自动触发重做:
| 步骤 | 跳过条件 | 自动重做条件 |
|------|-----------|----------------|
| 下载 | 同名文件已存在(非 --force) | --force 或文件不存在 |
| 转码 | WAV 已存在 且 MP4 时间戳 ≤ WAV 时间戳 | --force 或 MP4 比 WAV 新(重新下载过) |
| 识别 | —(每次必跑,覆盖写入 Excel) | — |
关键设计:即使不加
--force,只要视频重新下载过(MP4 的修改时间晚于 WAV),转码也会自动重新执行,确保下载和转码内容始终保持一致。
临时文件自动清理
yt-dlp 下载过程中会生成 .part(未完成分片)和 .ytdl(元数据)临时文件。脚本在以下时机自动清理这些残留:
| 时机 | 说明 |
|------|------|
| 下载开始前 | 清除上次中断留下的 .part / .ytdl,确保干净环境 |
| 跳过已有文件时 | 检查并清除该视频的历史残留 |
| 下载失败后 | 立即清理,避免无效文件占磁盘 |
例如:
2152.mp4.part+2152.mp4.ytdl会在下次下载该视频时自动删除,无需手动清理。
AI 关键词归纳
在识别完成后,脚本可自动调用 OpenAI 兼容 API 对识别文本做关键词归纳,结果写入 keywords 列。
配置
在 .env 中配置以下变量(见 .env.example):
# 启用/禁用 AI 分析环节
AI_ENABLED=true
# OpenAI 兼容 API(支持任何兼容接口)
AI_API_KEY=sk-xxx
AI_BASE_URL=https://apihub.agnes-ai.com/v1
AI_MODEL=agnes-2.0-flash
# 提示词模板({content} 会被识别文本替换)
# 采用两步法:先语义修正 Whisper 同音/形近/术语错误,再提取关键词
# 支持文件路径:值指向存在的文件则读取内容(try-file-first 策略)
# CLI 覆盖:--ai-prompt <text|path> 优先级最高(CLI > .env > 内置默认)
AI_PROMPT_TPL=你是多语言内容分析专家...这是内容:{content}
# 请求超时(秒,通过 --analyze-timeout 参数设置)工作原理
- whisper 识别完成 → 得到文本(存入
content列) - AI 先对识别文本做语义修正(修正 Whisper 常见的同音错字、专业术语误判、形近字混淆)
- 再对修正后的文本提取搜索关键词 → 写入
keywords列
提示词模板可自由定制:只需保留
{content}占位符,提示词内容可改为翻译、摘要、分类等任意任务。完整模板见.env.example。值支持文件路径(指向存在的文件则自动读取内容),也可通过--ai-promptCLI 参数临时覆盖(优先级:CLI > .env > 内置默认)。
单独运行
# 已有识别文本,只跑 AI 分析
node process_videos.js --sheet "普诺赛中文站" --id 427 --step analyze
# 或 Python 版本
python process_videos.py --sheet "普诺赛中文站" --id 427 --step analyze
# 单独跑 analyze 超过 16 条不会写入 Excel
# 要想写入 Excel 跑完整流程 --step analyze
node process_videos.js --sheet "YouTube视频" --step analyze --concurrency 2禁用 AI 分析
设置 AI_ENABLED=false,识别完成后跳过 AI 分析步骤。
提示词优先级
WHISPER_INITIAL_PROMPT 和 AI_PROMPT_TPL 均支持三种输入方式:
| 方式 | 示例 | 说明 |
|------|------|------|
| 内联文本 | WHISPER_INITIAL_PROMPT=细胞 冻存 | 直接写入值 |
| 文件路径 | AI_PROMPT_TPL=./prompts/my-prompt.txt | 值指向存在的文件时自动读取内容 |
| CLI 覆盖 | --ai-prompt ./prompts/custom.txt | 优先级最高,临试覆盖不修改 .env |
优先级:CLI 参数 > .env 环境变量 > 内置默认值
# 用自定义 prompt 文件跑全量
video-pipeline --ai-prompt ./prompts/keyword-extract.txt --sheet "普诺赛中文站"
# 临时覆盖 whisper 初始提示词 + 额外参数
video-pipeline --whisper-initial-prompt "细胞冻存,复苏" --whisper-extra-args "--beam_size 10 --verbose" --id 427文件名去重
脚本默认使用 COL_ID(即 extra.id)作为文件名 stem。当同一个 sheet 内出现重复 id 时,自动应用以下去重策略:
| 优先级 | 格式 | 示例 |
|--------|------|------|
| 1 | {id} | 2143 |
| 2 | {id}_{title} | 2143_产品介绍 |
| 3 | {id}_{title}_{platform} | 2143_产品介绍_bilibili |
去重仅在同 sheet 内生效,不同 sheet 之间允许同名文件(存放在不同子目录)。
进度显示
执行时会同时展示总体进度和单视频进度:
[1/91] [2143] 开始处理 (sheet=YouTube视频, platform=youtube, title=xxx)
[2143] 开始下载 (平台=youtube)
[2143] https://youtu.be/zzJmKPX8a3c
[2143] 解析页面...
[2143] 15.2% 2.50MiB/s ETA 00:17 ← 下载实时进度
[2143] 45.8% 3.12MiB/s ETA 00:08
[2143] 下载完成 -> 2143.mp4
[2143] 开始转码 -> 2143.wav
[2143] 25.3% (38s/150s) ← 转码进度 + 时长比
[2143] 50.1% (75s/150s)
[2143] 转码完成
[2143] 开始识别 (文件 45.2MB)...
[2143] 识别中... 5s ← 识别每 5s 报时
[2143] 识别完成 (22s, 1234 字符)
[2143] AI 分析中... 5s ← AI 每 5s 报时
[2143] AI 分析中... 10s
[2143] AI 分析完成 (567 字符)
[总进度 1/91 (1.1%)] ✅1 ❌0 ⚠️0 ⏭️0 ← 每完成一个刷新| 层级 | 显示内容 |
|------|----------|
| 总体进度 | 完成/总任务数、百分比、✅成功 ❌失败 ⚠️部分 ⏭️无视频 四维计数 |
| 下载 | yt-dlp 实时百分比 + 速度 + ETA |
| 转码 | 先 ffprobe 取时长,再实时解析 time= 算百分比(如 25.3% (38s/150s)) |
| 识别 | 每 5s 打印已用时间,完成时显示总耗时和文本长度 |
| AI 分析 | 每 5s 打印已用时间,完成时显示结果长度或失败原因 |
多线程并发时使用打印锁保证输出不交错。
输出结构速查表
五种输入来源在不同处理环节的输出路径汇总如下。所有路径均以 output/ 为根(可通过 DOWNLOADS_DIR / TRANSCODED_DIR / REPORTS_DIR 环境变量覆盖)。
{sheet}= Excel 工作表名(如YouTube视频、普诺赛中文站){platform}= 视频平台标识(如youtube、bilibili、tencent、youku){stem}= 去重后的安全文件名(不含扩展名)
① Excel 批量模式(默认)
| 环节 | 输出路径 | 产物格式 | 说明 |
|------|---------|---------|------|
| 下载 | output/downloads/{sheet}/{stem}.mp4 | 视频 | yt-dlp 下载原始视频 |
| 转码 | output/transcoded/{sheet}/{stem}.wav | 音频 | ffmpeg 转 16kHz mono WAV |
| JSON 报告 | output/reports/{sheet}/report_YYYYMMDD_HHMMSS.json | JSON | 机器可读,含 summary + failed_items,可供 --retry-failed 重跑 |
| 文本报告 | output/reports/{sheet}/tasks/{stem}.txt | 文本 | 人类可读,含语音识别原文 + AI 关键词分析 |
多 sheet 同时执行时,每个 sheet 独立一个子目录,互不干扰。
② --url 直链模式
| 环节 | 输出路径 | 产物格式 | 说明 |
|------|---------|---------|------|
| 下载 | output/downloads/{platform}/{name}.mp4 | 视频 | yt-dlp 下载单个视频 |
| 转码 | output/transcoded/{platform}/{name}.wav | 音频 | ffmpeg 转 16kHz mono WAV |
| JSON 报告 | output/reports/{platform}/report_YYYYMMDD_HHMMSS.json | JSON | 格式与 Excel 模式一致 |
| 文本报告 | output/reports/{platform}/tasks/{name}.txt | 文本 | 含识别原文 + AI 分析 |
{platform}由脚本自动从 URL 解析,如https://www.youtube.com/watch?v=xxx→youtube。
③ --input 本地文件模式
| 环节 | 输出路径 | 产物格式 | 说明 |
|------|---------|---------|------|
| 下载 | —(跳过) | — | 本地文件无需下载 |
| 转码 | output/transcoded/local/{stem}.wav | 音频 | ffmpeg 转 16kHz mono WAV |
| JSON 报告 | output/reports/local/report_YYYYMMDD_HHMMSS.json | JSON | 格式与 Excel 模式一致 |
| 文本报告 | output/reports/local/tasks/{stem}.txt | 文本 | 含识别原文 + AI 分析 |
local是--input模式的固定目录名(与 Excel 模式的 sheet 名无关),所有本地文件处理结果统一归入此目录。
④ --content 纯文本模式
| 环节 | 输出路径 | 产物格式 | 说明 |
|------|---------|---------|------|
| 下载 | —(跳过) | — | 无需下载 |
| 转码 | —(跳过) | — | 无需转码 |
| 识别 | —(跳过) | — | 无需语音识别 |
| JSON 报告 | output/reports/content/report_YYYYMMDD_HHMMSS.json | JSON | 格式与 Excel 模式一致 |
| 文本报告 | output/reports/content/tasks/{stem}.txt | 文本 | 含源内容 + AI 关键词分析 |
content是固定目录名。{stem} =--name值 > 文件名 stem > 内联文本前 32 字符。
⑤ --content-column Excel列文本批量模式
| 环节 | 输出路径 | 产物格式 | 说明 |
|------|---------|---------|------|
| 下载 | —(跳过) | — | 无需下载 |
| 转码 | —(跳过) | — | 无需转码 |
| 识别 | —(跳过) | — | 无需语音识别 |
| JSON 报告 | output/reports/{sheet}/report_YYYYMMDD_HHMMSS.json | JSON | 按 Excel sheet 分目录,格式与 Excel 模式一致 |
| 文本报告 | output/reports/{sheet}/tasks/{stem}.txt | 文本 | 含列文本 + AI 关键词分析 |
| Excel 写回 | {EXCEL_FILE} 的 keywords 列 | Excel | AI 关键词写入 Excel |
此模式自动设置
--step analyze,下载/转码/识别全跳过。AI 结果同时写入 Excel 和报告文件。
五种来源对比一览
| 维度 | Excel 批量 | --url 直链 | --input 本地文件 | --content 纯文本 | --content-column 列文本 |
|------|-----------|-----------|-----------------|-----------------|------------------------|
| 输入 | Excel 行(多视频批量) | 单个视频 URL | 本地视频/音频文件 | 文件路径或内联文本 | Excel 列的已有文本 |
| 下载 | ✅ yt-dlp | ✅ yt-dlp | ❌ 跳过 | ❌ 跳过 | ❌ 跳过 |
| 转码 | ✅ ffmpeg | ✅ ffmpeg | ✅ ffmpeg | ❌ 跳过 | ❌ 跳过 |
| 识别 | ✅ whisper | ✅ whisper | ✅ whisper | ❌ 跳过 | ❌ 跳过 |
| AI 分析 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 下载目录 | downloads/{sheet}/ | downloads/{platform}/ | 无 | 无 | 无 |
| 转码目录 | transcoded/{sheet}/ | transcoded/{platform}/ | transcoded/local/ | 无 | 无 |
| 报告目录 | reports/{sheet}/ | reports/{platform}/ | reports/local/ | reports/content/ | reports/{sheet}/ |
| 分组依据 | Excel sheet 名 | URL 解析的平台名 | 固定 local | 固定 content | Excel sheet 名 |
| 并发支持 | ✅ 多线程 | ❌ 单任务 | ❌ 单任务 | ❌ 单任务 | ✅ 多线程 |
| 写入 Excel | ✅ | ❌ | ❌ | ❌ | ✅ |
| 支持 --retry-failed | ✅ | ❌ | ❌ | ❌ | ❌ |
| 适用场景 | 批量处理全流程 | 临时下载单个视频 | 处理已有视频文件 | 已有文本直接分析 | 批量分析Excel中的文本 |
JSON 报告结构
{
"timestamp": "2026-06-10T14:30:00",
"config": { "concurrency": 3, "retry": 3 },
"summary": {
"total": 91,
"success": 85,
"partial": 3,
"failed": 2,
"no_video": 1
},
"failed_items": [
{
"sheet": "普诺赛中文站",
"id": "427",
"title": "xxx视频",
"download_error": "HTTP Error 403",
"transcode_error": null,
"transcribe_error": null
}
]
}状态含义
- success:下载 + 转码 + 识别全部成功(AI 分析失败不影响此状态)
- partial:下载 + 转码成功,识别或 AI 分析失败
- failed:下载或转码失败
- no_video:该行无可用视频 ID
典型工作流
场景一:Excel 批量处理视频
# 1. 干跑预览
node process_videos.js --dry-run
# 或 Python 版本
python process_videos.py --dry-run
# 2. 单条验证
node process_videos.js --sheet "YouTube视频" --id 2143 --retry 2
# 3. 全量执行
node process_videos.js --concurrency 3 --retry 3
# 4. 查看报告,重跑失败项
node process_videos.js --retry-failed reports/YouTube视频/report_xxx.json --concurrency 2 --retry 3场景二:临时下载单个视频
# 从 URL 下载 → 转码 → 识别 → AI 分析,一条龙
node process_videos.js --url "https://www.youtube.com/watch?v=zzJmKPX8a3c"
# 指定输出文件名
node process_videos.js --url "https://www.bilibili.com/video/BV1xx411c7mD" --name "产品介绍视频"场景三:处理本地视频文件
# 已有视频文件,直接转码分析
node process_videos.js --input "downloads/产品介绍.mp4"
# 只做 AI 分析(已有转码+识别结果)
node process_videos.js --input "downloads/产品介绍.mp4" --step analyze场景四:纯文本 AI 分析
# 已有文本内容,跳过所有视频步骤,直接做关键词提取
node process_videos.js --content "data/article.txt"
# 内联文本直接分析
node process_videos.js --content "今天我们要讨论的是普诺赛产品..." --name "产品讨论"场景五:批量分析 Excel 中的已有文本
# Excel 某列已有文本(如爬虫爬取的),批量做 AI 关键词分析
node process_videos.js --content-column "content" --dry-run # 先预览
node process_videos.js --content-column "content" --concurrency 2 # 执行平台适配说明
脚本支持四个视频平台的下载,各有不同的反爬配置:
| 平台 | 字段 | 反爬措施 |
|------|-------|----------|
| B站 (bilibili) | extra.bilibili | Chrome UA + Referer 头 + 有效 cookie + 并发分片 |
| YouTube | extra.youtube | Chrome UA + Firefox cookie 直读 + 代理 + Node.js 解 n-sig |
| 腾讯视频 | extra.tencent | 无需特殊配置 |
| 优酷 | extra.youku | 无需特殊配置(部分视频需会员) |
YouTube 反爬最强:需要 代理 + 登录态 cookie + JS runtime 解 n-sig 三者配合。 脚本会自动给 yt-dlp 及其 node/ejs 子进程注入
HTTPS_PROXY环境变量,确保所有流量走代理。
各平台 URL 格式与视频 ID 提取
脚本通过 {平台}_URL_TPL 生成下载链接,支持 yt-dlp 能识别的所有 URL 格式。
下表列出各平台「标准页面 / 内嵌链接 / 短链接」格式及视频 ID 提取正则,方便从完整 URL 中解析视频 ID。
YouTube
| 格式类型 | URL 示例 | 视频 ID 提取正则 |
|----------|-----------|-------------------|
| 标准观看页 | https://www.youtube.com/watch?v=VIDEO_ID | youtube\.com/watch\?v=([a-zA-Z0-9_-]{11}) |
| 短链接 | https://youtu.be/VIDEO_ID | youtu\.be/([a-zA-Z0-9_-]{11}) |
| Shorts | https://www.youtube.com/shorts/VIDEO_ID | youtube\.com/shorts/([a-zA-Z0-9_-]{11}) |
| 内嵌页 | https://www.youtube.com/embed/VIDEO_ID | youtube\.com/embed/([a-zA-Z0-9_-]{11}) |
| 直播 | https://www.youtube.com/live/VIDEO_ID | youtube\.com/live/([a-zA-Z0-9_-]{11}) |
- 视频 ID 格式:11 位字符(大小写字母 + 数字 +
-+_) - 统一提取正则(覆盖所有格式):
(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/|live\/)|youtu\.be\/)([a-zA-Z0-9_-]{11}) - 格式互转:
- 标准 → 短链接:提取
VIDEO_ID→https://youtu.be/VIDEO_ID - 标准 → 内嵌:提取
VIDEO_ID→https://www.youtube.com/embed/VIDEO_ID - Shorts → 标准:提取
VIDEO_ID→https://www.youtube.com/watch?v=VIDEO_ID
- 标准 → 短链接:提取
B站(bilibili)
| 格式类型 | URL 示例 | 视频 ID 提取正则 |
|----------|-----------|-------------------|
| 标准页(BV 号) | https://www.bilibili.com/video/BV1xx411c7mD | bilibili\.com\/video\/(BV[a-zA-Z0-9]{10}) |
| 标准页(av 号) | https://www.bilibili.com/video/av170001 | bilibili\.com\/video\/av(\d+) |
| 短链接 | https://b23.tv/BV1xx411c7mD | b23\.tv\/(BV[a-zA-Z0-9]{10}) |
| 内嵌页 | https://player.bilibili.com/player.html?bvid=BV1xx411c7mD&cid=CID | bvid=(BV[a-zA-Z0-9]{10}) |
| 移动端 | https://m.bilibili.com/video/BV1xx411c7mD | m\.bilibili\.com\/video\/(BV[a-zA-Z0-9]{10}) |
- BV 号格式:
BV+ 10 位字符(大小写敏感) - 统一提取正则:
bilibili\.com\/video\/(BV[a-zA-Z0-9]{10})|bvid=(BV[a-zA-Z0-9]{10}) - 格式互转:
- 标准 → 内嵌:提取
BV_ID后需通过 B站 API 获取cid→https://api.bilibili.com/x/player/pagelist?bvid=BV_ID获取 cid → 内嵌 URL:https://player.bilibili.com/player.html?bvid=BV_ID&cid=CID&page=1 - BV 号 → av 号:需调用 API(
https://api.bilibili.com/x/web-interface/view?bvid=BV_ID返回aid)
- 标准 → 内嵌:提取
腾讯视频
| 格式类型 | URL 示例 | 视频 ID 提取正则 |
|----------|-----------|-------------------|
| 标准页(x/page) | https://v.qq.com/x/page/VIDEO_ID.html | v\.qq\.com\/x\/page\/([a-zA-Z0-9]+)\.html |
| 标准页(x/cover) | https://v.qq.com/x/cover/COVER/VIDEO_ID.html | v\.qq\.com\/x\/cover\/[^\/]+\/([a-zA-Z0-9]+)\.html |
| 内嵌页 | https://v.qq.com/txp/iframe/player.html?vid=VIDEO_ID | [?&]vid=([a-zA-Z0-9]+) |
| 移动端 | https://m.v.qq.com/x/mv.xhtml?vid=VIDEO_ID | [?&]vid=([a-zA-Z0-9]+) |
- 视频 ID 格式:字母 + 数字组合(如
o0325y3hqh,长度不固定) - 统一提取正则:
v\.qq\.com\/(?:x\/page\/|x\/cover\/[^\/]+\/)([a-zA-Z0-9]+)\.html|[?&]vid=([a-zA-Z0-9]+) - 格式互转:
- 标准 → 内嵌:提取
VIDEO_ID→https://v.qq.com/txp/iframe/player.html?vid=VIDEO_ID
- 标准 → 内嵌:提取
优酷(Youku)
| 格式类型 | URL 示例 | 视频 ID 提取正则 |
|----------|-----------|-------------------|
| 标准页(v_show) | https://v.youku.com/v_show/id_VIDEO_ID.html | v\.youku\.com\/v_show\/id_([a-zA-Z0-9=]+)\.html |
| 标准页(video) | https://v.youku.com/video/VIDEO_ID | v\.youku\.com\/video\/([a-zA-Z0-9=]+) |
| 标准页(www) | https://www.youku.com/v_show/id_VIDEO_ID.html | www\.youku\.com\/v_show\/id_([a-zA-Z0-9=]+)\.html |
- 视频 ID 格式:旧格式
X+ Base64 字符串(可能含=填充);新格式长度不固定 - 统一提取正则:
v\.youku\.com\/v_show\/id_([a-zA-Z0-9=]+)\.html|v\.youku\.com\/video\/([a-zA-Z0-9=]+) - 格式互转:
- 优酷内嵌格式较复杂,建议直接使用标准页链接(
{YOUKU_URL_TPL})
- 优酷内嵌格式较复杂,建议直接使用标准页链接(
脚本使用提示:Excel 中只需填入视频 ID(如
zzJmKPX8a3c、BV1pg411b7Ug、o0325y3hqh、XMzgxNzExNTY4MA==),脚本自动替换 URL 模板中的{youtube}、{bilibili}等占位符生成下载链接。
常见下载错误
| 错误 | 平台 | 原因 | 解决方案 |
|------|------|------|----------|
| Sign in to confirm you're not a bot | YouTube | cookie 过期或无效 | 检查 Firefox 登录态,或重新导出 cookie 文件 |
| cookies does no longer seem to be valid | YouTube | cookie 文件超过 48h | 用 Firefox cookies-from-browser 方案(免维护) |
| Unable to download webpage: HTTP Error 403 | YouTube | IP 被识别为非 YouTube 地区 | 确保代理运行(端口 7897),检查 YOUTUBE_PROXY |
| n challenge solving failed | YouTube | 无 JS 运行时 | 安装 Node.js,确保 YOUTUBE_JS_RUNTIMES=node |
| Requested format is not available | YouTube | n-sig 未解开,格式不可用 | 同上,安装 JS 运行时 |
| HTTP Error 412 | B站 | 缺少 Chrome UA 或 cookie 过期 | 重新导出 cookies/bilibili.txt 或使用 Firefox 直读 |
| HTTP Error 403 | B站 | 地区限制或视频已删除 | 检查视频是否可访问 |
| dpapi decryption failed | YouTube | Windows Chrome cookie 加密 | 改用 Firefox(.env 中设 YOUTUBE_COOKIES_FROM_BROWSER=firefox) |
换电脑使用
Node.js 版本
- 安装 Node.js (18+):nodejs.org
- 安装视频处理工具:
npm install -g video-pipeline - 克隆或下载项目文件(
.env.example、.env、cookies/等) - 安装必装工具:
yt-dlp、ffmpeg、ffprobe,确保均在 PATH - 用 Firefox 登录 YouTube,设置
YOUTUBE_COOKIES_FROM_BROWSER=firefox - B站 cookie 仍需手动导出
cookies/bilibili.txt(或设置BILIBILI_COOKIES_FROM_BROWSER=firefox) - 启动代理(Clash Verge 等),确认端口匹配
YOUTUBE_PROXY video-pipeline --dry-run验证
Python 版本
- 安装 Python 3.9+:python.org
- 安装必装工具:
yt-dlp、ffmpeg、ffprobe,确保均在 PATH - 安装 Python 依赖:
pip install pandas openpyxl requests python-dotenv questionary cp .env.example .env,根据实际情况修改.env中的路径、代理端口和字段映射- 用 Firefox 登录 YouTube,设置
YOUTUBE_COOKIES_FROM_BROWSER=firefox - B站 cookie 仍需手动导出
cookies/bilibili.txt(或设置BILIBILI_COOKIES_FROM_BROWSER=firefox) - 启动代理(Clash Verge 等),确认端口匹配
YOUTUBE_PROXY python process_videos.py --dry-run验证
适配其他 Excel
如果需要用这套脚本处理其他项目的 Excel(列名不同、平台不同):
方法一:修改 .env 文件
- 复制
.env.example为新.env(或修改现有.env) - 修改
EXCEL_FILE指向新 Excel - 修改列映射(
COL_ID、COL_TITLE、COL_CONTENT及各平台列名) - 修改
VIDEO_SHEETS为新的 sheet 名称 - 如需新平台,在
PLATFORM_PRIORITY中添加 key,并配置对应的{KEY}_URL_TPL node process_videos.js --dry-run验证配置- 跑全量
方法二:使用 --file 选项(推荐)
# 直接指定 Excel 文件,无需修改 .env
node process_videos.js --file "data/其他项目.xlsx" --dry-run
# 配合 --env-file 使用自定义环境变量
node process_videos.js --file "data/其他项目.xlsx" --env-file ".env.其他项目" --dry-run优点:
- 无需修改
.env文件 - 可以为不同项目创建不同的
.env配置文件 - 命令行优先级高于环境变量
