一个支持多线程的douyin无水印视频下载器(Python 实现)
前言
本文是一篇 Python 爬虫技术学习笔记,通过分析抖音网页结构,学习以下技术点:
- 网页数据解析(正则表达式、JSON解析)
- HTTP请求与Session管理
- Python多线程编程(ThreadPoolExecutor)
- 命令行工具开发(argparse)
声明:本文仅供技术学习交流,请勿用于任何商业用途或侵犯他人权益的行为。
GitHub 项目地址:
https://github.com/yanghongm/douyin-downloader
技术栈
- Python 3.7+
- requests - HTTP请求库
- concurrent.futures - 多线程模块
- argparse - 命令行参数解析
- dataclass - 数据类
核心技术点
1. URL解析与正则表达式
抖音有多种URL格式,需要用正则表达式统一提取视频ID:
python
import re
URL_PATTERNS = [
r'douyin\.com/video/(\d+)', # PC端链接
r'iesdouyin\.com/share/video/(\d+)', # 分享链接
r'v\.douyin\.com/(\w+)', # 短链接
]
def extract_video_id(url: str) -> str:
for pattern in URL_PATTERNS:
match = re.search(pattern, url)
if match:
return match.group(1)
return None
知识点:
re.search()在字符串中搜索匹配(\d+)捕获组,匹配数字match.group(1)获取第一个捕获组
2. 短链接重定向处理
短链接需要跟随重定向获取真实URL:
python
import requests
def resolve_short_url(short_url: str) -> str:
session = requests.Session()
resp = session.get(short_url, allow_redirects=True)
return resp.url # 返回最终跳转的URL
知识点:
allow_redirects=True自动跟随重定向resp.url获取最终URL
3. 网页数据解析
从HTML中提取JSON数据的两种方式:
方式一:正则提取
python
# 提取页面中的JSON数据
pattern = r'"playApi"\s*:\s*"([^"]+)"'
match = re.search(pattern, html)
if match:
video_url = match.group(1)
# 处理Unicode转义
video_url = video_url.replace("\\u002F", "/")
方式二:JSON解析
python
import json
from urllib.parse import unquote
# 提取script标签中的JSON
pattern = r'<script id="RENDER_DATA"[^>]*>([^<]+)</script>'
match = re.search(pattern, html)
if match:
data = json.loads(unquote(match.group(1)))
# 递归查找需要的字段
4. 递归查找嵌套数据
当JSON结构复杂时,可以用递归函数查找:
python
def find_in_dict(obj, key: str, depth: int = 0):
"""递归查找字典中的值"""
if depth > 15: # 防止无限递归
return None
if isinstance(obj, dict):
if key in obj and obj[key]:
return obj[key]
for v in obj.values():
result = find_in_dict(v, key, depth + 1)
if result:
return result
elif isinstance(obj, list):
for item in obj:
result = find_in_dict(item, key, depth + 1)
if result:
return result
return None
5. 多线程并发下载
使用 ThreadPoolExecutor 实现线程池:
python
from concurrent.futures import ThreadPoolExecutor, as_completed
def process_batch(urls: list, workers: int = 5):
with ThreadPoolExecutor(max_workers=workers) as executor:
# 提交所有任务
futures = {
executor.submit(process_single, url): url
for url in urls
}
# 获取完成的结果
for future in as_completed(futures):
url = futures[future]
try:
result = future.result()
print(f"完成: {url}")
except Exception as e:
print(f"失败: {url}, 错误: {e}")
知识点:
ThreadPoolExecutor线程池管理executor.submit()提交任务as_completed()按完成顺序迭代
6. 线程安全的计数器
多线程环境下需要使用锁保护共享变量:
python
import threading
class ThreadSafeCounter:
def __init__(self):
self.count = 0
self.lock = threading.Lock()
def increment(self):
with self.lock:
self.count += 1
return self.count
7. 流式下载大文件
下载大文件时使用流式传输,避免内存溢出:
python
def download_file(url: str, filepath: str):
with requests.get(url, stream=True) as resp:
resp.raise_for_status()
total = int(resp.headers.get('content-length', 0))
downloaded = 0
with open(filepath, 'wb') as f:
for chunk in resp.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
downloaded += len(chunk)
# 可以在这里更新进度条
知识点:
stream=True启用流式传输iter_content()分块读取chunk_size每块大小
8. 命令行参数解析
使用 argparse 构建命令行工具:
python
import argparse
def main():
parser = argparse.ArgumentParser(description='示例工具')
parser.add_argument('url', nargs='?', help='目标URL')
parser.add_argument('-f', '--file', help='URL列表文件')
parser.add_argument('-o', '--output', default='.', help='输出目录')
parser.add_argument('-w', '--workers', type=int, default=5, help='线程数')
args = parser.parse_args()
if args.file:
# 批量处理模式
with open(args.file, 'r') as f:
urls = [line.strip() for line in f if line.strip()]
process_batch(urls, args.workers)
elif args.url:
# 单个处理模式
process_single(args.url)
9. 数据类简化代码
使用 dataclass 定义数据结构:
python
from dataclasses import dataclass
@dataclass
class VideoInfo:
video_id: str
title: str
author: str
url: str
cover_url: str = "" # 可选字段,有默认值
## 异常处理最佳实践
```python
import requests
from requests.exceptions import RequestException
def safe_request(url: str, retries: int = 3):
for attempt in range(retries):
try:
resp = requests.get(url, timeout=30)
resp.raise_for_status()
return resp
except RequestException as e:
if attempt == retries - 1:
raise
time.sleep(1) # 重试前等待
总结
本文介绍了以下Python技术:
- 正则表达式 - 文本模式匹配与提取
- HTTP请求 - requests库的高级用法
- JSON解析 - 复杂嵌套数据的处理
- 多线程 - ThreadPoolExecutor线程池
- 线程安全 - Lock锁的使用
- 流式IO - 大文件的高效处理
- CLI开发 - argparse参数解析
这些技术在爬虫开发、数据处理、自动化脚本等场景都非常实用。
免责声明
- 本文仅供技术学习和研究使用
- 请遵守相关网站的服务条款和robots协议
- 请勿将技术用于任何非法用途
- 任何因使用本文技术造成的问题,作者不承担责任
- 尊重内容创作者的版权,支持正版