当你需要长期跟踪某个DouYin博主的所有作品时,手动保存不仅效率低下,还容易遗漏内容。我曾经为了收集某个美食博主的300多个作品,连续一周每天花两小时手动下载,结果还是漏掉了十几个视频。这种重复劳动正是Python爬虫最擅长解决的问题。
一个完整的自动化系统应该包含三个核心功能:定时采集、智能归档和异常处理。与简单脚本不同,系统级方案需要考虑长期运行的稳定性。比如当遇到网络波动时,普通脚本可能直接崩溃,而健壮的系统应该能自动重试并记录失败点。
在Linux服务器上部署这类系统特别合适。我自己的采集系统已经稳定运行了8个月,累计自动抓取了超过2万个视频。关键是要处理好三个技术难点:动态页面渲染、反爬机制规避和资源管理。接下来我会分享具体实现方案。
在Linux服务器上,使用Firefox无头模式配合虚拟显示是最稳定的方案。这是我验证过的配置组合:
python复制from selenium import webdriver
from selenium.webdriver.firefox.options import Options
from pyvirtualdisplay import Display
display = Display(visible=0, size=(1980, 1440)) # 虚拟显示缓冲
display.start()
firefox_options = Options()
firefox_options.headless = True
firefox_options.binary_location = '/usr/bin/firefox' # 必须指定二进制路径
踩坑提醒:很多教程会漏掉binary_location配置,这会导致WebDriver报错。建议先用which firefox命令确认安装位置。另外内存小于2GB的服务器可能需要调整虚拟显示尺寸,否则容易崩溃。
BeautifulSoup虽然解析静态HTML很高效,但对动态加载的内容无能为力。我的方案是两者结合:
实测下来,这种混合模式比纯Selenium快40%,比纯BeautifulSoup成功率高出3倍。关键代码片段:
python复制driver.get(target_url)
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, 'e6wsjNLL'))
)
soup = BeautifulSoup(driver.page_source, 'lxml')
driver.quit() # 及时释放资源
DouYin的页面结构经常变动,但通过分析发现这两个class相对稳定:
e6wsjNLL - 作品列表容器niBfRBgX - 单个作品项滚动加载是必须处理的难点。我的方案是模拟用户滚动行为:
python复制driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
sleep(1.5) # 滚动后等待加载
更可靠的做法是循环滚动直到没有新内容加载,可以结合这个判断条件:
python复制last_height = driver.execute_script("return document.body.scrollHeight")
while True:
driver.execute_script("window.scrollTo(0, document.body.scrollHeight)")
sleep(2)
new_height = driver.execute_script("return document.body.scrollHeight")
if new_height == last_height:
break
last_height = new_height
视频和图文作品的DOM结构不同,需要分别处理。关键信息提取点:
D8UdT9V8(视频) / YWeXsAGK(图文)xg-video-container内的source标签KiGtXxLr下的多个img标签特别注意时间格式标准化,建议统一转成YYYY-MM-DD_HH-MM格式:
python复制from datetime import datetime
def format_time(raw_str):
# 处理"2023-05-01 15:30"这类格式
return datetime.strptime(raw_str, '%Y-%m-%d %H:%M').strftime('%Y-%m-%d_%H-%M')
我推荐的目录层级设计:
code复制/采集根目录/
├── /2023-05-01_15-30_video/ # 单个视频
│ └── 20230501_1530_001.mp4
├── /2023-05-02_09-15_pictures_3/ # 含3张图的图文
│ ├── 20230502_0915_001.webp
│ ├── 20230502_0915_002.webp
│ └── 20230502_0915_003.webp
└── archive_202305.zip # 按月打包的压缩包
实现这个结构的Python代码:
python复制import os
from datetime import datetime
def create_works_dir(base_path, publish_time, work_type, pic_count=0):
time_str = publish_time.strftime('%Y-%m-%d_%H-%M')
if work_type == 'video':
dir_name = f"{time_str}_video"
else:
dir_name = f"{time_str}_pictures_{pic_count}"
full_path = os.path.join(base_path, dir_name)
os.makedirs(full_path, exist_ok=True)
return full_path
长期运行会产生大量小文件,建议定期打包压缩。使用Python标准库zipfile的实现:
python复制import zipfile
def zip_monthly_data(source_dir):
month = os.path.basename(source_dir)[:7]
zip_path = f"{source_dir}/../archive_{month}.zip"
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(source_dir):
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, start=source_dir)
zipf.write(file_path, arcname)
# 压缩后删除原目录
shutil.rmtree(source_dir)
return zip_path
根据我的实战经验,DouYin主要采用这些防御机制:
有效应对方法包括:
python复制from random import uniform
sleep(uniform(0.5, 2.5)) # 随机等待时间
python复制options = Options()
options.set_preference('dom.webdriver.enabled', False)
options.set_preference('useAutomationExtension', False)
健壮的系统必须包含完善的错误处理。建议至少捕获这些异常:
我的异常处理模板:
python复制try:
# 主要操作代码
except NoSuchElementException as e:
log_error(f"元素未找到: {str(e)}")
driver.save_screenshot('error.png') # 保存现场截图
except TimeoutException:
log_error("页面加载超时")
if retry_count < 3:
retry_count += 1
continue
except Exception as e:
log_error(f"未知错误: {str(e)}")
finally:
driver.quit() # 确保资源释放
当需要采集大量博主时,可以考虑:
一个安全的多线程实现示例:
python复制from concurrent.futures import ThreadPoolExecutor
def safe_crawl(url):
try:
return get_works(url)
except Exception as e:
print(f"采集失败 {url}: {str(e)}")
return None
with ThreadPoolExecutor(max_workers=3) as executor: # 控制并发数
results = list(executor.map(safe_crawl, url_list))
使用系统级定时任务更可靠。在Linux上可以配置crontab:
bash复制0 3 * * * /usr/bin/python3 /path/to/crawler.py >> /var/log/douyin_crawl.log 2>&1
或者在Python中实现定时循环:
python复制import schedule
import time
def job():
print("开始执行采集任务...")
# 主要采集逻辑
schedule.every().day.at("03:00").do(job)
while True:
schedule.run_pending()
time.sleep(60)
在长期运行过程中,我总结了几个关键注意事项:
python复制def check_disk_space(min_gb=5):
stats = os.statvfs('/')
free_gb = (stats.f_bavail * stats.f_frsize) / (1024**3)
return free_gb > min_gb
code复制[2023-05-01 03:00:01] INFO 开始采集博主ID:12345
[2023-05-01 03:02:15] WARNING 视频45678下载失败,重试中...
[2023-05-01 03:02:18] SUCCESS 视频45678下载完成
python复制last_crawl_time = read_last_time()
if item_time > last_crawl_time:
download_item(item)
update_last_time(item_time)
这套系统经过多次迭代,目前已经能够稳定处理每天500+视频的采集任务。最关键的体会是:异常处理比主流程更重要。建议把60%的开发时间用在完善各种边界情况的处理上,这样才能保证长期稳定运行。