Python爬虫实战:打造视频下载与音频转换一体化工具

摘要

本文将详细介绍如何使用Python构建一个功能完整的视频下载与音频转换工具。我们将实现从指定网站下载视频,然后将视频批量转换为音频格式的功能。这个项目涵盖了网络爬虫、文件处理、外部程序调用等多个技术点,适合有一定Python基础的开发者学习。

项目背景

在日常开发中,我们经常需要处理多媒体内容,比如下载在线视频并将其转换为音频格式以便离线收听。这个需求在教育、娱乐、内容聚合等领域都很常见。本文将带你一步步实现一个完整的小工具,包含以下功能:

  1. 自动从指定网站下载视频文件

  2. 批量转换视频为音频格式

  3. 智能文件管理和错误处理

  4. 用户友好的交互界面

技术栈

  • Python 3.x

  • requests - HTTP请求库

  • BeautifulSoup - HTML解析库

  • subprocess - 系统命令调用

  • FFmpeg - 音视频处理工具

项目结构设计

 video-audio-converter/
 ├── downloader.py          # 视频下载主程序
 ├── converter.py           # 视频转音频工具
 ├── mp4_to_mp3_converter.py # MP4转MP3工具(Python实现)
 ├── utils.py               # 工具函数
 ├── config.py              # 配置文件
 ├── requirements.txt       # 依赖包列表
 ├── README.md             # 项目说明
 └── data/                 # 下载内容保存目录
     └── videos/           # 视频文件
     └── audios/           # 音频文件

1. 配置文件设计

首先,我们创建一个配置文件来管理项目参数:

 """项目配置文件"""
 ​
 # 视频下载配置
 BASE_URL = "https://www.tuxiaobei.com/play/"  # 固定播放页前缀
 SAVE_DIR = "data/videos"  # 视频保存目录
 SLEEP_TIME = 15  # 成功后休眠时间(秒)
 MIN_NUM = 1000   # 随机编号最小值
 MAX_NUM = 5000   # 随机编号最大值
 ​
 # 请求头配置
 HEADERS = {
     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
     "Referer": BASE_URL,
     "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
 }
 ​
 # 音频转换配置
 AUDIO_FORMAT = "mp3"  # 目标音频格式
 AUDIO_BITRATE = "192k"  # 音频比特率
 TARGET_DIR = "data/audios"  # 音频文件保存目录
 ​
 # 其他配置
 TIMEOUT = 30  # 请求超时时间(秒)
 CHUNK_SIZE = 1024 * 1024  # 下载块大小(1MB)

2. 工具函数实现

接下来,我们实现一些通用的工具函数:

 """工具函数模块"""
 ​
 import os
 import re
 import requests
 from urllib.parse import urljoin
 from bs4 import BeautifulSoup
 import config
 ​
 def create_save_dir(save_path=None):
     """创建保存目录,不存在则新建"""
     path = save_path or config.SAVE_DIR
     if not os.path.exists(path):
         os.makedirs(path)
         print(f"✅ 创建保存目录:{path}")
     else:
         print(f"✅ 保存目录已存在:{path}")
 ​
 def get_page_content(url):
     """获取页面HTML内容,捕获请求异常"""
     try:
         response = requests.get(url, headers=config.HEADERS, timeout=config.TIMEOUT)
         response.raise_for_status()  # 抛出HTTP错误(4xx/5xx)
         response.encoding = response.apparent_encoding  # 自动识别编码
         return response.text
     except Exception as e:
         print(f"❌ 获取页面失败:{e}")
         return None
 ​
 def extract_video_title(html, target_url):
     """提取视频标题,提取失败则用链接编号兜底,自动过滤非法字符"""
     soup = BeautifulSoup(html, "html.parser")
     title = ""
     # 优先从页面title标签提取(最稳定),并净化多余后缀
     if soup.title and soup.title.text:
         title = soup.title.text.strip()
         title = re.sub(r'[_-]+兔小贝.*$|_+儿歌.*$|_+动画.*$', '', title)
     # 兜底匹配页面内标题类标签
     if not title:
         title_tags = soup.find_all(['h1', 'h2', 'div'], class_=re.compile(r'title|name|video'))
         for tag in title_tags:
             if tag.text and len(tag.text.strip()) > 2:
                 title = tag.text.strip()
                 break
     # 过滤全平台非法文件名字符,终极兜底用视频编号
     if title:
         title = re.sub(r'[\\/:*?"<>|]', '_', title)  # 移除非法字符
         title = re.sub(r'\s+', ' ', title).strip()    # 合并多余空格
     else:
         title = target_url.split("/")[-1]  # 提取链接末尾数字作为标题
     return title
 ​
 def extract_video_links(html, target_url):
     """提取MP4视频链接,去重后返回"""
     video_links = []
     soup = BeautifulSoup(html, "html.parser")
     # 方式1:从script标签匹配mp4链接(动态加载场景)
     for script in soup.find_all("script"):
         if script.string:
             mp4_urls = re.findall(r'https?://[^\s"]+\.mp4', script.string)
             video_links.extend(mp4_urls)
     # 方式2:从video标签提取src(直接嵌入场景)
     for video in soup.find_all("video", src=True):
         src = video["src"]
         absolute_url = urljoin(target_url, src)
         if absolute_url.endswith(".mp4"):
             video_links.append(absolute_url)
     # 链接去重
     video_links = list(set(video_links))
     print(f"🔍 提取到 {len(video_links)} 个视频链接")
     return video_links
 ​
 def sanitize_filename(filename):
     """清理文件名,移除非法字符"""
     # 过滤全平台非法文件名字符
     sanitized = re.sub(r'[\\/:*?"<>|]', '_', filename)
     # 合并多余空格并清理首尾空白
     sanitized = re.sub(r'\s+', ' ', sanitized).strip()
     return sanitized
 ​
 def convert_to_audio(video_path, audio_path):
     """使用FFmpeg将视频转换为音频"""
     import subprocess
     try:
         # 使用FFmpeg转换视频为音频
         cmd = [
             "ffmpeg", "-i", video_path,  # 输入视频文件
             "-vn",  # 不包含视频
             "-acodec", "libmp3lame",  # 音频编码器
             "-q:a", "2",  # 音频质量(1为最高,9为最低)
             "-y",  # 覆盖已存在的文件
             "-loglevel", "error",  # 只显示错误信息
             audio_path  # 输出音频文件
         ]
         result = subprocess.run(cmd, capture_output=True, text=True)
         # 检查是否转换成功
         if result.returncode == 0 and os.path.exists(audio_path) and os.path.getsize(audio_path) > 1024:
             return True
         else:
             print(f"❌ 音频转换失败:{result.stderr if result.stderr else '文件未生成或大小异常'}")
             # 删除可能生成的空文件
             if os.path.exists(audio_path):
                 os.remove(audio_path)
             return False
     except Exception as e:
         print(f"❌ 音频转换出错:{e}")
         # 删除可能生成的空文件
         if os.path.exists(audio_path):
             os.remove(audio_path)
         return False

3. 视频下载功能实现

现在我们实现视频下载的核心功能:

 """视频下载主程序"""
 ​
 import os
 import random
 import time
 from utils import *
 import config
 ​
 def download_video(video_url, video_title):
     """流式下载视频,返回下载结果(成功True/失败False),跳过已存在文件"""
     # 确保目录存在
     create_save_dir()
     save_path = os.path.join(config.SAVE_DIR, f"{video_title}.mp4")
     # 跳过已存在的视频,避免重复下载
     if os.path.exists(save_path):
         print(f"⏭️  视频已存在,跳过:{video_title}.mp4")
         return True
     try:
         print(f"📥 开始下载:{video_url}")
         print(f"📛 命名为:{video_title}.mp4")
         # 流式下载(分块读取,避免大文件占用内存)
         response = requests.get(video_url, headers=config.HEADERS, stream=True, timeout=config.TIMEOUT)
         response.raise_for_status()
         with open(save_path, "wb") as f:
             for chunk in response.iter_content(chunk_size=config.CHUNK_SIZE):  # 1MB/块
                 if chunk:
                     f.write(chunk)
         print(f"✅ 下载完成:{video_title}.mp4")
         return True
     except Exception as e:
         print(f"❌ 下载失败:{e}")
         # 删除未下载完成的残缺文件
         if os.path.exists(save_path):
             os.remove(save_path)
         return False
 ​
 def get_download_count():
     """获取用户输入的下载数量,做合法性校验(仅接受正整数)"""
     while True:
         try:
             count = input("\n请输入需要下载的视频数量(正整数,如50):")
             count = int(count)
             if count > 0:
                 return count
             else:
                 print("❌ 输入错误!请输入大于0的正整数")
         except ValueError:
             print("❌ 输入错误!请输入有效的正整数(如50、100)")
 ​
 def main():
     """主函数:输入数量→循环下载→失败重试→成功计数→完成终止"""
     print("="*60)
     print("🐰 兔小贝视频下载脚本(指定数量版)")
     print("="*60)
     # 1. 初始化保存目录
     create_save_dir()
     # 2. 获取用户指定的下载数量(带合法性校验)
     target_count = get_download_count()
     # 3. 初始化成功下载计数器
     success_count = 0
     print(f"\n📌 下载目标:{target_count} 个视频")
     print(f"📌 随机编号范围:[{config.MIN_NUM}, {config.MAX_NUM}]")
     print(f"📌 成功后休眠:{config.SLEEP_TIME} 秒")
     print("="*60)
     print("开始循环下载(按Ctrl+C可手动终止)...\n")
 ​
     # 无限循环,直到达到目标下载数量
     while success_count < target_count:
         # 生成随机编号,拼接完整播放页URL
         random_num = random.randint(config.MIN_NUM, config.MAX_NUM)
         target_url = config.BASE_URL + str(random_num)
         print(f"===== 正在处理第 {success_count+1}/{target_count} 个目标 =====")
         print(f"🔗 目标播放页:{target_url}")
 ​
         # 步骤1:获取页面内容,失败则立即重试
         html = get_page_content(target_url)
         if not html:
             print("🔄 页面获取失败,立即重试下一个\n")
             continue
 ​
         # 步骤2:提取视频标题和链接,无链接则立即重试
         video_title = extract_video_title(html, target_url)
         video_links = extract_video_links(html, target_url)
         if not video_links:
             print("🔄 未提取到视频链接,立即重试下一个\n")
             continue
 ​
         # 步骤3:下载视频,失败则立即重试;成功则计数器+1并休眠
         download_result = download_video(video_links[0], video_title)
         if download_result:
             success_count += 1
             # 未达到目标数量则休眠,达到则直接终止
             if success_count < target_count:
                 print(f"⏳ 已成功下载 {success_count}/{target_count} 个,休眠{config.SLEEP_TIME}秒...\n")
                 time.sleep(config.SLEEP_TIME)
             else:
                 print(f"\n🎉 已完成目标!成功下载 {success_count} 个视频")
         else:
             print("🔄 视频下载失败,立即重试下一个\n")
             continue
 ​
 if __name__ == "__main__":
     try:
         main()
     except KeyboardInterrupt:
         print("\n\n🛑 用户手动终止,脚本执行完毕!")

4. 音频转换功能实现

我们还需要实现将视频转换为音频的功能:

 """视频转音频工具"""
 ​
 import os
 import glob
 from utils import convert_to_audio
 import config
 ​
 def convert_videos_to_audio():
     """将视频目录中的所有MP4文件转换为音频文件"""
     # 确保音频保存目录存在
     config.create_save_dir(config.TARGET_DIR)
     
     # 获取所有MP4文件
     video_pattern = os.path.join(config.SAVE_DIR, "*.mp4")
     video_files = glob.glob(video_pattern)
     
     if not video_files:
         print(f"❌ 未在 {config.SAVE_DIR} 中找到MP4文件")
         return
     
     print(f"🔍 找到 {len(video_files)} 个视频文件待转换")
     
     success_count = 0
     for video_path in video_files:
         # 生成音频文件路径
         video_name = os.path.basename(video_path)
         audio_name = os.path.splitext(video_name)[0] + f".{config.AUDIO_FORMAT}"
         audio_path = os.path.join(config.TARGET_DIR, audio_name)
         
         print(f"\n🎵 正在转换:{video_name} → {audio_name}")
         
         # 执行转换
         if convert_to_audio(video_path, audio_path):
             print(f"✅ 转换成功:{audio_name}")
             success_count += 1
             
             # 询问是否删除原视频文件
             delete_original = input("是否删除原视频文件?(y/N): ").lower().strip()
             if delete_original == 'y':
                 os.remove(video_path)
                 print(f"🗑️  已删除原视频:{video_name}")
         else:
             print(f"❌ 转换失败:{video_name}")
     
     print(f"\n🎉 转换完成!成功转换 {success_count}/{len(video_files)} 个文件")
 ​
 def batch_convert_with_deletion():
     """批量转换并自动删除原视频文件(对应PowerShell脚本功能)"""
     # 确保音频保存目录存在
     config.create_save_dir(config.TARGET_DIR)
     
     # 获取所有MP4文件
     video_pattern = os.path.join(config.SAVE_DIR, "*.mp4")
     video_files = glob.glob(video_pattern)
     
     if not video_files:
         print(f"❌ 未在 {config.SAVE_DIR} 中找到MP4文件")
         return
     
     print(f"🔍 找到 {len(video_files)} 个视频文件待转换")
     
     success_count = 0
     for video_path in video_files:
         # 生成音频文件路径
         video_name = os.path.basename(video_path)
         audio_name = os.path.splitext(video_name)[0] + f".{config.AUDIO_FORMAT}"
         audio_path = os.path.join(config.TARGET_DIR, audio_name)
         
         print(f"\n🎵 开始转换:{video_name} → {audio_name}")
         
         # 执行转换
         if convert_to_audio(video_path, audio_path):
             print(f"✅ 转换成功,正在删除原视频文件...")
             os.remove(video_path)
             print(f"🗑️  已删除原视频:{video_name}")
             success_count += 1
         else:
             print(f"❌ 转换失败,未删除原视频:{video_name}")
             # 清理转换失败生成的空音频文件(若有)
             if os.path.exists(audio_path):
                 os.remove(audio_path)
     
     print(f"\n🎉 批量转换完成!成功转换并删除 {success_count}/{len(video_files)} 个文件")
 ​
 def main():
     """主函数:提供用户选择转换模式"""
     print("="*60)
     print("🎵 视频转音频工具")
     print("="*60)
     print("请选择转换模式:")
     print("1. 手动模式 - 逐个确认是否删除原视频")
     print("2. 自动模式 - 转换成功后自动删除原视频(类似PowerShell脚本)")
     
     while True:
         choice = input("\n请输入选择 (1 或 2): ").strip()
         if choice == "1":
             convert_videos_to_audio()
             break
         elif choice == "2":
             batch_convert_with_deletion()
             break
         else:
             print("❌ 无效选择,请输入 1 或 2")
 ​
 if __name__ == "__main__":
     main()

5. PowerShell功能的Python实现

为了完全替代PowerShell脚本的功能,我们还创建了一个专门的转换脚本:

 """批量将MP4转换为MP3的Python实现"""
 ​
 import os
 import subprocess
 import glob
 from pathlib import Path
 ​
 def convert_mp4_to_mp3_batch(directory="."):
     """
     批量将指定目录中的所有MP4文件转换为同名MP3文件
     转换成功后自动删除原MP4文件
     """
     # 获取目录中所有MP4文件
     mp4_files = glob.glob(os.path.join(directory, "*.mp4"))
     
     if not mp4_files:
         print("当前目录中未找到MP4文件")
         return
     
     print(f"找到 {len(mp4_files)} 个MP4文件待转换")
     
     for mp4_file in mp4_files:
         # 获取文件路径信息
         mp4_path = Path(mp4_file)
         mp3_path = mp4_path.with_suffix('.mp3')
         
         print(f"\n开始转换:{mp4_path.name} → {mp3_path.name}")
         
         try:
             # 使用FFmpeg进行转换
             cmd = [
                 "ffmpeg", 
                 "-i", str(mp4_path),      # 输入MP4文件
                 "-vn",                     # 不包含视频轨道
                 "-acodec", "libmp3lame",  # 音频编码器使用MP3
                 "-q:a", "2",              # 音频质量 (1为最高,9为最低)
                 "-y",                     # 覆盖已存在的文件
                 "-loglevel", "error",     # 只显示错误信息
                 str(mp3_path)             # 输出MP3文件
             ]
             
             result = subprocess.run(cmd, capture_output=True, text=True)
             
             # 检查转换是否成功
             if result.returncode == 0 and mp3_path.exists() and mp3_path.stat().st_size > 1024:
                 print(f"转换成功,正在删除原MP4文件...")
                 mp4_path.unlink()  # 删除原MP4文件
                 print(f"删除完成:{mp4_path.name}")
             else:
                 print(f"【错误】{mp4_path.name} 转换失败,未删除原文件!")
                 # 删除可能生成的空MP3文件
                 if mp3_path.exists():
                     mp3_path.unlink()
         except Exception as e:
             print(f"【错误】处理 {mp4_path.name} 时出错:{e}")
 ​
 def main():
     """主函数"""
     print("🎵 批量MP4转MP3工具(Python实现)")
     print("="*50)
     print("功能:将当前目录下所有MP4文件转换为MP3格式")
     print("      转换成功后自动删除原MP4文件")
     print("="*50)
     
     directory = input("请输入要处理的目录路径(直接回车表示当前目录): ").strip()
     if not directory:
         directory = "."
     
     if not os.path.exists(directory):
         print(f"目录不存在:{directory}")
         return
     
     convert_mp4_to_mp3_batch(directory)
     print(f"\n所有MP4文件处理完成!")
 ​
 if __name__ == "__main__":
     main()

6. 依赖文件

创建requirements.txt文件:

 requests>=2.28.0
 beautifulsoup4>=4.11.0

使用说明

  1. 首先安装依赖:

 pip install -r requirements.txt
  1. 确保系统已安装FFmpeg:

# Windows
choco install ffmpeg
# macOS
brew install ffmpeg
# Linux
sudo apt install ffmpeg
  1. 运行视频下载:

python downloader.py
  1. 运行音频转换:

python converter.py

注意事项

  1. 遵守网站规则:使用爬虫时请遵守网站的robots.txt和使用条款

  2. 控制请求频率:适当设置休眠时间,避免对服务器造成过大压力

  3. 版权问题:下载内容仅供个人学习使用,请勿传播

  4. 错误处理:程序包含完善的错误处理机制,但网络环境复杂,仍需注意异常情况

总结

本文详细介绍了一个完整的视频下载与音频转换工具的开发过程。通过这个项目,我们学习了:

  1. 网络爬虫的基本原理和实现

  2. HTML解析和数据提取

  3. 文件下载和流式处理

  4. 外部程序调用(FFmpeg)

  5. 用户交互和错误处理

这个项目不仅实用,而且涵盖了Python开发中的多个重要概念,适合作为进阶练习项目。你可以根据实际需求进一步扩展功能,如支持更多视频格式、添加GUI界面、实现更复杂的爬虫策略等。


本文原创,转载请注明出处。如发现代码bug或有改进建议,欢迎在评论区交流。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐