Python爬虫实战:打造视频下载与音频转换一体化工具
Python爬虫实战:打造视频下载与音频转换一体化工具
摘要
本文将详细介绍如何使用Python构建一个功能完整的视频下载与音频转换工具。我们将实现从指定网站下载视频,然后将视频批量转换为音频格式的功能。这个项目涵盖了网络爬虫、文件处理、外部程序调用等多个技术点,适合有一定Python基础的开发者学习。
项目背景
在日常开发中,我们经常需要处理多媒体内容,比如下载在线视频并将其转换为音频格式以便离线收听。这个需求在教育、娱乐、内容聚合等领域都很常见。本文将带你一步步实现一个完整的小工具,包含以下功能:
-
自动从指定网站下载视频文件
-
批量转换视频为音频格式
-
智能文件管理和错误处理
-
用户友好的交互界面
技术栈
-
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
使用说明
-
首先安装依赖:
pip install -r requirements.txt
-
确保系统已安装FFmpeg:
# Windows choco install ffmpeg # macOS brew install ffmpeg # Linux sudo apt install ffmpeg
-
运行视频下载:
python downloader.py
-
运行音频转换:
python converter.py
注意事项
-
遵守网站规则:使用爬虫时请遵守网站的robots.txt和使用条款
-
控制请求频率:适当设置休眠时间,避免对服务器造成过大压力
-
版权问题:下载内容仅供个人学习使用,请勿传播
-
错误处理:程序包含完善的错误处理机制,但网络环境复杂,仍需注意异常情况
总结
本文详细介绍了一个完整的视频下载与音频转换工具的开发过程。通过这个项目,我们学习了:
-
网络爬虫的基本原理和实现
-
HTML解析和数据提取
-
文件下载和流式处理
-
外部程序调用(FFmpeg)
-
用户交互和错误处理
这个项目不仅实用,而且涵盖了Python开发中的多个重要概念,适合作为进阶练习项目。你可以根据实际需求进一步扩展功能,如支持更多视频格式、添加GUI界面、实现更复杂的爬虫策略等。
本文原创,转载请注明出处。如发现代码bug或有改进建议,欢迎在评论区交流。
更多推荐
所有评论(0)