小白也能懂:Chord视频理解工具+Python爬虫,轻松实现视频数据自动采集与分析

1. 为什么你需要这个自动化方案

想象一下这个场景:你负责运营一个短视频账号,每天需要从几十个竞品账号里找出最新的视频,分析他们的内容特点、发布时间、画面亮点。手动操作是什么体验?打开浏览器,一个个账号点进去,找到视频,下载下来,再打开播放器,边看边记笔记。一天下来,眼睛花了,手也酸了,效率还低得可怜。

或者你是做市场研究的,需要定期收集某个行业的所有新品发布视频,分析产品展示的时长、演示的重点环节、用户评论的热点。靠人工一个个看,不仅耗时,还容易漏掉关键信息。

这就是传统视频分析工作的痛点——重复、耗时、容易出错。而今天要介绍的这套组合方案,就是用技术把我们从这些重复劳动中解放出来。

Chord视频时空理解工具,简单来说,就是一个能“看懂”视频的智能工具。它不只是截取画面,而是能理解视频里“什么时候发生了什么”、“画面里谁在哪儿”。比如,它能告诉你:“视频第12秒,一只小狗从画面左侧跑到了右侧”,或者“第25秒到第30秒,画面中央出现了产品特写镜头”。

Python爬虫,则是我们的“数据采集员”。它能自动去各个平台,按照我们设定的规则,把视频链接、标题、发布时间等信息抓取回来。

把这两者结合起来,就形成了一个自动化的工作流:爬虫负责“找”视频,Chord负责“看”视频,最后把分析结果整理好交给我们。整个过程不需要人工干预,设定好任务,它就能自己运行。

这套方案特别适合谁?

  • 内容创作者:想批量分析热门视频的套路和亮点。
  • 市场分析师:需要监控竞品动态和行业趋势。
  • 产品经理:想了解用户生成内容中的产品使用场景。
  • 研究人员:需要从大量视频中提取结构化数据做分析。

接下来,我会用最直白的方式,带你一步步搭建这个系统,即使你之前没怎么写过代码,也能跟着做出来。

2. 准备工作:环境搭建与工具安装

在开始写代码之前,我们需要先把“舞台”搭好。不用担心,跟着步骤来,每一步都很简单。

2.1 安装Python和相关库

首先确保你的电脑上安装了Python。打开命令行(Windows上是CMD或PowerShell,Mac/Linux上是终端),输入:

python --version

如果显示Python 3.7或更高版本,那就没问题。如果没有安装,去Python官网下载安装包,记得安装时勾选“Add Python to PATH”。

接下来安装我们需要的Python库。创建一个新的文件夹作为项目目录,然后在里面创建一个requirements.txt文件,内容如下:

# 爬虫相关
requests==2.31.0
beautifulsoup4==4.12.2
selenium==4.15.0
yt-dlp==2023.11.16

# 数据处理相关
pandas==2.1.3
sqlite3  # 这个通常Python自带

# 其他工具
psutil==5.9.6

然后在命令行里进入项目目录,运行:

pip install -r requirements.txt

这里简单解释一下每个库是干什么的:

  • requests:用来访问网页,获取数据
  • beautifulsoup4:解析网页内容,提取我们需要的信息
  • selenium:处理那些需要JavaScript加载的页面
  • yt-dlp:下载视频的神器,支持几乎所有视频平台
  • pandas:处理和分析数据
  • sqlite3:轻量级数据库,用来存储我们的数据
  • psutil:监控系统资源使用情况

2.2 部署Chord视频理解工具

Chord工具已经打包成了现成的镜像,部署起来特别简单。如果你在CSDN星图平台,可以直接搜索“Chord 视频时空理解工具”镜像,一键部署。

部署成功后,你会看到一个Web界面。这个界面分为三个主要区域:

  1. 左侧设置区:可以调整“最大生成长度”,控制分析结果的详细程度
  2. 中间上传区:上传你的视频文件(支持MP4、AVI、MOV格式)
  3. 右侧操作区:选择分析模式,输入你的问题

工具提供两种分析模式:

  • 普通描述模式:让工具详细描述视频内容
  • 视觉定位模式:让工具找出视频中特定目标的位置和时间

比如你上传一个宠物视频,在视觉定位模式输入“正在奔跑的小狗”,工具就会告诉你小狗在视频的哪些时间点出现,以及在画面中的具体位置。

2.3 配置自动化运行环境

为了让整个系统能自动运行,我们需要做一些基础配置。

首先,创建一个项目结构清晰的文件夹:

video-auto-collector/
├── crawler/          # 爬虫相关代码
├── analyzer/         # 视频分析代码
├── storage/          # 数据存储代码
├── config/           # 配置文件
├── logs/             # 日志文件
├── videos/           # 下载的视频文件
└── main.py           # 主程序入口

然后创建一个简单的配置文件config/config.yaml:

# 爬虫配置
crawler:
  user_agents:
    - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
    - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
  request_delay: 2  # 请求间隔秒数
  max_retries: 3    # 最大重试次数

# Chord工具配置
chord:
  api_url: "http://localhost:7860"  # Chord工具地址
  timeout: 300      # 超时时间(秒)
  max_length: 512   # 最大生成长度

# 存储配置
storage:
  database: "videos.db"
  video_dir: "./videos"
  max_video_size: 500  # 最大视频大小(MB)

# 任务调度
scheduler:
  crawl_interval: 3600  # 爬虫运行间隔(秒)
  analyze_interval: 600  # 分析运行间隔(秒)

这样,我们的基础环境就准备好了。接下来开始写核心的爬虫代码。

3. 编写智能爬虫:自动获取视频数据

爬虫的核心任务很简单:去指定的网站,找到视频,把视频信息拿回来。但不同的网站结构不同,我们需要针对性地处理。

3.1 基础爬虫框架

我们先写一个通用的爬虫基类,处理一些公共的逻辑:

import requests
import time
import random
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import yt_dlp
from urllib.parse import urljoin
import logging

class BaseVideoCrawler:
    """视频爬虫基类"""
    
    def __init__(self, config):
        self.config = config
        self.session = requests.Session()
        self.logger = logging.getLogger(self.__class__.__name__)
        
        # 设置请求头
        self.headers = {
            'User-Agent': random.choice(config['user_agents']),
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        }
    
    def fetch_page(self, url, use_selenium=False):
        """获取页面内容"""
        try:
            if use_selenium:
                return self._fetch_with_selenium(url)
            else:
                return self._fetch_with_requests(url)
        except Exception as e:
            self.logger.error(f"获取页面失败: {url}, 错误: {e}")
            return None
    
    def _fetch_with_requests(self, url):
        """使用requests获取页面"""
        time.sleep(self.config['request_delay'])  # 礼貌等待
        response = self.session.get(url, headers=self.headers, timeout=10)
        response.raise_for_status()
        return response.text
    
    def _fetch_with_selenium(self, url):
        """使用Selenium获取动态页面"""
        chrome_options = Options()
        chrome_options.add_argument('--headless')  # 无头模式,不显示浏览器窗口
        chrome_options.add_argument('--no-sandbox')
        chrome_options.add_argument('--disable-dev-shm-usage')
        
        driver = webdriver.Chrome(options=chrome_options)
        try:
            driver.get(url)
            time.sleep(3)  # 等待页面加载
            return driver.page_source
        finally:
            driver.quit()
    
    def extract_video_links(self, html, base_url):
        """从HTML中提取视频链接(子类需要重写)"""
        raise NotImplementedError("子类必须实现此方法")
    
    def get_video_info(self, video_url):
        """获取视频详细信息(子类需要重写)"""
        raise NotImplementedError("子类必须实现此方法")
    
    def run(self, start_urls):
        """运行爬虫"""
        all_videos = []
        
        for url in start_urls:
            self.logger.info(f"开始爬取: {url}")
            
            html = self.fetch_page(url, use_selenium=False)
            if not html:
                continue
            
            video_links = self.extract_video_links(html, url)
            self.logger.info(f"找到 {len(video_links)} 个视频链接")
            
            for link in video_links:
                try:
                    video_info = self.get_video_info(link)
                    if video_info:
                        all_videos.append(video_info)
                        self.logger.info(f"获取视频信息: {video_info['title'][:50]}...")
                except Exception as e:
                    self.logger.error(f"处理视频失败: {link}, 错误: {e}")
        
        return all_videos

这个基类处理了通用的网络请求、错误处理、日志记录等逻辑。针对不同的网站,我们只需要继承这个类,实现特定的提取逻辑。

3.2 针对不同平台的爬虫实现

不同的视频平台,页面结构不同,我们需要写不同的爬虫。这里以两个常见平台为例:

示例1:静态页面视频平台

class StaticVideoCrawler(BaseVideoCrawler):
    """处理静态HTML页面的视频爬虫"""
    
    def extract_video_links(self, html, base_url):
        """从静态页面提取视频链接"""
        soup = BeautifulSoup(html, 'html.parser')
        video_links = []
        
        # 查找所有可能的视频链接
        # 这里需要根据实际网站结构调整选择器
        for a_tag in soup.find_all('a', href=True):
            href = a_tag['href']
            
            # 判断是否是视频链接(根据实际网站调整)
            if '/video/' in href or '.mp4' in href or 'video_id=' in href:
                full_url = urljoin(base_url, href)
                if full_url not in video_links:
                    video_links.append(full_url)
        
        return video_links[:20]  # 限制数量,避免太多
    
    def get_video_info(self, video_url):
        """获取视频详细信息"""
        html = self.fetch_page(video_url)
        if not html:
            return None
        
        soup = BeautifulSoup(html, 'html.parser')
        
        # 提取标题(根据实际网站调整选择器)
        title_elem = soup.find('h1') or soup.find('title')
        title = title_elem.get_text(strip=True) if title_elem else "未知标题"
        
        # 提取描述
        desc_elem = soup.find('meta', attrs={'name': 'description'})
        description = desc_elem['content'] if desc_elem else ""
        
        # 提取发布时间(如果有)
        time_elem = soup.find('time')
        publish_time = time_elem['datetime'] if time_elem else ""
        
        return {
            'url': video_url,
            'title': title,
            'description': description,
            'publish_time': publish_time,
            'platform': 'static_site'
        }

示例2:使用API的视频平台

class APIVideoCrawler(BaseVideoCrawler):
    """通过API获取视频的爬虫"""
    
    def __init__(self, config, api_key):
        super().__init__(config)
        self.api_key = api_key
        self.base_api_url = "https://api.example.com/v1"
    
    def extract_video_links(self, html, base_url):
        """API爬虫不需要解析HTML,直接返回空列表"""
        return []
    
    def get_video_info(self, video_url):
        """通过API获取视频信息"""
        # 从URL中提取视频ID
        video_id = self._extract_video_id(video_url)
        if not video_id:
            return None
        
        # 调用API
        api_url = f"{self.base_api_url}/videos/{video_id}"
        headers = {
            **self.headers,
            'Authorization': f'Bearer {self.api_key}'
        }
        
        try:
            response = self.session.get(api_url, headers=headers, timeout=10)
            response.raise_for_status()
            data = response.json()
            
            return {
                'url': video_url,
                'video_id': video_id,
                'title': data.get('title', ''),
                'description': data.get('description', ''),
                'publish_time': data.get('published_at', ''),
                'duration': data.get('duration', 0),
                'views': data.get('view_count', 0),
                'platform': 'api_platform'
            }
        except Exception as e:
            self.logger.error(f"API请求失败: {video_url}, 错误: {e}")
            return None
    
    def _extract_video_id(self, url):
        """从URL中提取视频ID"""
        # 这里需要根据实际平台URL格式编写提取逻辑
        import re
        patterns = [
            r'v=([a-zA-Z0-9_-]+)',  # YouTube风格
            r'/video/(\d+)',         # 数字ID
            r'/([a-zA-Z0-9]+)$'      # 路径最后部分
        ]
        
        for pattern in patterns:
            match = re.search(pattern, url)
            if match:
                return match.group(1)
        
        return None
    
    def run(self, start_urls):
        """重写run方法,直接通过API获取视频列表"""
        all_videos = []
        
        # 假设API提供了视频列表接口
        list_api_url = f"{self.base_api_url}/videos"
        params = {
            'limit': 50,
            'order_by': 'newest'
        }
        headers = {
            **self.headers,
            'Authorization': f'Bearer {self.api_key}'
        }
        
        try:
            response = self.session.get(list_api_url, params=params, headers=headers)
            response.raise_for_status()
            videos_data = response.json().get('items', [])
            
            for item in videos_data:
                video_info = {
                    'url': item.get('url', ''),
                    'video_id': item.get('id', ''),
                    'title': item.get('title', ''),
                    'description': item.get('description', ''),
                    'publish_time': item.get('published_at', ''),
                    'duration': item.get('duration', 0),
                    'views': item.get('view_count', 0),
                    'platform': 'api_platform'
                }
                all_videos.append(video_info)
                self.logger.info(f"获取视频: {video_info['title'][:50]}...")
                
        except Exception as e:
            self.logger.error(f"获取视频列表失败: {e}")
        
        return all_videos

3.3 视频下载功能

获取到视频信息后,我们需要下载视频文件供Chord分析。这里使用yt-dlp,它支持几乎所有的视频平台:

import subprocess
from pathlib import Path

class VideoDownloader:
    """视频下载器"""
    
    def __init__(self, download_dir='./videos'):
        self.download_dir = Path(download_dir)
        self.download_dir.mkdir(exist_ok=True)
        self.logger = logging.getLogger('VideoDownloader')
    
    def download_video(self, video_url, video_id, max_retries=3):
        """下载视频文件"""
        output_path = self.download_dir / f"{video_id}.mp4"
        
        # 如果文件已存在且大小合理,跳过下载
        if output_path.exists() and output_path.stat().st_size > 1024 * 1024:  # 大于1MB
            self.logger.info(f"视频已存在: {video_id}")
            return str(output_path)
        
        # yt-dlp配置
        ydl_opts = {
            'outtmpl': str(output_path.with_suffix('')),  # 输出模板(不带扩展名)
            'format': 'best[height<=720]',  # 选择720p或以下的最佳质量
            'quiet': True,  # 减少输出
            'no_warnings': True,
            'ignoreerrors': True,
            'retries': 10,
            'fragment_retries': 10,
            'continuedl': True,  # 断点续传
        }
        
        for attempt in range(max_retries):
            try:
                self.logger.info(f"开始下载视频 {video_id} (尝试 {attempt + 1}/{max_retries})")
                
                # 使用yt-dlp下载
                with yt_dlp.YoutubeDL(ydl_opts) as ydl:
                    ydl.download([video_url])
                
                # 检查文件是否下载成功
                if output_path.exists() and output_path.stat().st_size > 1024 * 1024:
                    self.logger.info(f"视频下载成功: {video_id}, 大小: {output_path.stat().st_size // 1024 // 1024}MB")
                    return str(output_path)
                else:
                    self.logger.warning(f"视频文件异常: {video_id}")
                    
            except Exception as e:
                self.logger.error(f"下载失败 {video_id} (尝试 {attempt + 1}): {e}")
                if attempt < max_retries - 1:
                    import time
                    time.sleep(2 ** attempt)  # 指数退避
        
        self.logger.error(f"视频下载最终失败: {video_id}")
        return None
    
    def cleanup_old_videos(self, keep_days=7):
        """清理旧视频文件"""
        import time
        current_time = time.time()
        cutoff_time = current_time - (keep_days * 24 * 3600)
        
        deleted_count = 0
        for video_file in self.download_dir.glob("*.mp4"):
            if video_file.stat().st_mtime < cutoff_time:
                try:
                    video_file.unlink()
                    deleted_count += 1
                except Exception as e:
                    self.logger.error(f"删除文件失败 {video_file}: {e}")
        
        if deleted_count > 0:
            self.logger.info(f"清理了 {deleted_count} 个旧视频文件")

4. 集成Chord工具:让机器“看懂”视频

爬虫帮我们找到了视频,接下来就要让Chord工具来分析这些视频了。Chord的强大之处在于它能理解视频的时空信息,而不仅仅是识别画面内容。

4.1 与Chord工具交互

Chord工具提供了Web界面,我们可以通过编程方式与它交互。这里我们写一个封装类:

import requests
import json
import time
from pathlib import Path

class ChordAnalyzer:
    """Chord视频分析器"""
    
    def __init__(self, chord_url="http://localhost:7860", timeout=300):
        self.chord_url = chord_url
        self.timeout = timeout
        self.logger = logging.getLogger('ChordAnalyzer')
    
    def analyze_video(self, video_path, task_type="describe", query=""):
        """
        分析视频
        
        Args:
            video_path: 视频文件路径
            task_type: 任务类型,'describe'或'grounding'
            query: 查询内容(仅grounding模式需要)
        
        Returns:
            分析结果字典
        """
        # 检查文件是否存在
        video_file = Path(video_path)
        if not video_file.exists():
            self.logger.error(f"视频文件不存在: {video_path}")
            return None
        
        # 准备请求数据
        files = {
            'file': (video_file.name, open(video_file, 'rb'), 'video/mp4')
        }
        
        data = {
            'max_length': 512,  # 控制输出长度
            'task_type': task_type
        }
        
        if task_type == 'grounding' and query:
            data['query'] = query
        
        try:
            self.logger.info(f"开始分析视频: {video_file.name}")
            start_time = time.time()
            
            # 发送请求到Chord工具
            response = requests.post(
                f"{self.chord_url}/analyze",
                files=files,
                data=data,
                timeout=self.timeout
            )
            
            response.raise_for_status()
            result = response.json()
            
            elapsed_time = time.time() - start_time
            self.logger.info(f"视频分析完成: {video_file.name}, 耗时: {elapsed_time:.1f}秒")
            
            return {
                'success': True,
                'video_path': str(video_path),
                'task_type': task_type,
                'query': query,
                'result': result,
                'analysis_time': elapsed_time
            }
            
        except requests.exceptions.Timeout:
            self.logger.error(f"分析超时: {video_file.name}")
            return {'success': False, 'error': 'timeout'}
        except requests.exceptions.RequestException as e:
            self.logger.error(f"请求失败: {video_file.name}, 错误: {e}")
            return {'success': False, 'error': str(e)}
        except json.JSONDecodeError as e:
            self.logger.error(f"JSON解析失败: {video_file.name}, 错误: {e}")
            return {'success': False, 'error': 'json_decode_error'}
        finally:
            if 'file' in locals():
                files['file'][1].close()
    
    def batch_analyze(self, video_paths, task_type="describe", queries=None):
        """批量分析视频"""
        results = []
        
        for i, video_path in enumerate(video_paths):
            query = queries[i] if queries and i < len(queries) else ""
            
            result = self.analyze_video(video_path, task_type, query)
            if result:
                results.append(result)
            
            # 避免请求过于频繁
            if i < len(video_paths) - 1:
                time.sleep(2)
        
        return results
    
    def parse_analysis_result(self, result, video_info=None):
        """解析分析结果,提取有用信息"""
        if not result.get('success'):
            return None
        
        raw_result = result.get('result', {})
        parsed = {
            'video_id': video_info.get('video_id', '') if video_info else '',
            'title': video_info.get('title', '') if video_info else '',
            'task_type': result['task_type'],
            'query': result['query'],
            'analysis_time': result['analysis_time']
        }
        
        if result['task_type'] == 'describe':
            # 解析描述结果
            description = raw_result.get('description', '')
            parsed['description'] = description
            
            # 提取关键信息(简单示例)
            keywords = self._extract_keywords(description)
            parsed['keywords'] = keywords
            
            # 估算视频内容复杂度
            parsed['complexity_score'] = len(description) / 100  # 简单评分
            
        elif result['task_type'] == 'grounding':
            # 解析定位结果
            objects = raw_result.get('objects', [])
            parsed['objects'] = objects
            
            # 统计信息
            parsed['object_count'] = len(objects)
            
            # 提取时间分布
            if objects:
                timestamps = [obj.get('timestamp', 0) for obj in objects]
                parsed['first_appearance'] = min(timestamps) if timestamps else 0
                parsed['last_appearance'] = max(timestamps) if timestamps else 0
                parsed['duration_seconds'] = parsed['last_appearance'] - parsed['first_appearance']
        
        return parsed
    
    def _extract_keywords(self, text, top_n=10):
        """从文本中提取关键词(简单实现)"""
        import re
        from collections import Counter
        
        # 移除标点,转换为小写
        words = re.findall(r'\b\w+\b', text.lower())
        
        # 过滤停用词(简单列表)
        stop_words = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by'}
        filtered_words = [w for w in words if w not in stop_words and len(w) > 2]
        
        # 统计词频
        word_counts = Counter(filtered_words)
        
        return [word for word, _ in word_counts.most_common(top_n)]

4.2 实际应用示例

让我们看几个具体的应用场景,看看Chord能帮我们做什么:

场景1:分析产品展示视频

假设你收集了一批竞品的产品展示视频,想了解他们是如何展示产品特性的:

# 分析产品展示视频
analyzer = ChordAnalyzer()

# 场景1:详细描述视频内容
result = analyzer.analyze_video(
    video_path="product_demo.mp4",
    task_type="describe",
    query="详细描述这个产品演示视频的内容,包括产品特点、演示步骤、画面重点"
)

if result and result['success']:
    description = result['result'].get('description', '')
    print("视频描述:")
    print(description[:500] + "..." if len(description) > 500 else description)
    
    # 提取关键词
    keywords = analyzer._extract_keywords(description)
    print("\n关键词:", keywords)

# 场景2:定位特定产品特征
result2 = analyzer.analyze_video(
    video_path="product_demo.mp4",
    task_type="grounding",
    query="产品logo"
)

if result2 and result2['success']:
    objects = result2['result'].get('objects', [])
    print(f"\n找到 {len(objects)} 个产品logo出现:")
    for obj in objects:
        print(f"  时间: {obj.get('timestamp', 0):.1f}秒, 位置: {obj.get('bbox', [])}")

场景2:监控品牌曝光

如果你需要监控某个品牌在视频中的曝光情况:

def monitor_brand_exposure(video_paths, brand_names):
    """监控品牌在视频中的曝光"""
    analyzer = ChordAnalyzer()
    brand_exposures = {}
    
    for brand in brand_names:
        brand_exposures[brand] = {
            'total_videos': 0,
            'total_appearances': 0,
            'appearance_times': []
        }
    
    for video_path in video_paths:
        print(f"\n分析视频: {Path(video_path).name}")
        
        # 先用描述模式了解视频内容
        describe_result = analyzer.analyze_video(video_path, task_type="describe")
        if not describe_result or not describe_result['success']:
            continue
        
        description = describe_result['result'].get('description', '').lower()
        
        # 检查每个品牌是否被提及
        for brand in brand_names:
            brand_lower = brand.lower()
            if brand_lower in description:
                brand_exposures[brand]['total_videos'] += 1
                
                # 进一步用定位模式找出具体出现时间
                grounding_result = analyzer.analyze_video(
                    video_path, 
                    task_type="grounding", 
                    query=brand
                )
                
                if grounding_result and grounding_result['success']:
                    objects = grounding_result['result'].get('objects', [])
                    brand_exposures[brand]['total_appearances'] += len(objects)
                    
                    for obj in objects:
                        brand_exposures[brand]['appearance_times'].append({
                            'video': Path(video_path).name,
                            'timestamp': obj.get('timestamp', 0),
                            'position': obj.get('bbox', [])
                        })
    
    return brand_exposures

# 使用示例
video_files = ["video1.mp4", "video2.mp4", "video3.mp4"]
brands_to_monitor = ["Nike", "Adidas", "Apple"]

exposure_data = monitor_brand_exposure(video_files, brands_to_monitor)

print("\n品牌曝光统计:")
for brand, data in exposure_data.items():
    print(f"{brand}:")
    print(f"  出现在 {data['total_videos']} 个视频中")
    print(f"  总共出现 {data['total_appearances']} 次")
    if data['appearance_times']:
        print(f"  首次出现: {data['appearance_times'][0]['video']} 的 {data['appearance_times'][0]['timestamp']}秒")

4.3 分析结果存储与查询

分析完的视频数据需要妥善保存,方便后续查询和分析:

import sqlite3
from datetime import datetime

class VideoAnalysisDB:
    """视频分析数据库"""
    
    def __init__(self, db_path='video_analysis.db'):
        self.db_path = db_path
        self._init_database()
    
    def _init_database(self):
        """初始化数据库表"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        # 视频基本信息表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS videos (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                video_id TEXT UNIQUE NOT NULL,
                title TEXT NOT NULL,
                url TEXT NOT NULL,
                platform TEXT,
                duration REAL,
                file_path TEXT,
                download_time TIMESTAMP,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        ''')
        
        # 视频分析结果表
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS analysis_results (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                video_id TEXT NOT NULL,
                task_type TEXT NOT NULL,
                query TEXT,
                raw_result TEXT,
                parsed_result TEXT,
                analysis_time REAL,
                analyzed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (video_id) REFERENCES videos (video_id)
            )
        ''')
        
        # 创建索引
        cursor.execute('CREATE INDEX IF NOT EXISTS idx_videos_video_id ON videos(video_id)')
        cursor.execute('CREATE INDEX IF NOT EXISTS idx_results_video_id ON analysis_results(video_id)')
        cursor.execute('CREATE INDEX IF NOT EXISTS idx_results_task_type ON analysis_results(task_type)')
        
        conn.commit()
        conn.close()
    
    def save_video_info(self, video_info):
        """保存视频信息"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute('''
            INSERT OR REPLACE INTO videos 
            (video_id, title, url, platform, duration, file_path, download_time)
            VALUES (?, ?, ?, ?, ?, ?, ?)
        ''', (
            video_info.get('video_id', ''),
            video_info.get('title', ''),
            video_info.get('url', ''),
            video_info.get('platform', ''),
            video_info.get('duration', 0),
            video_info.get('file_path', ''),
            video_info.get('download_time', datetime.now().isoformat())
        ))
        
        conn.commit()
        conn.close()
        return True
    
    def save_analysis_result(self, video_id, analysis_result):
        """保存分析结果"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute('''
            INSERT INTO analysis_results 
            (video_id, task_type, query, raw_result, parsed_result, analysis_time)
            VALUES (?, ?, ?, ?, ?, ?)
        ''', (
            video_id,
            analysis_result.get('task_type', ''),
            analysis_result.get('query', ''),
            str(analysis_result.get('result', {})),
            str(analysis_result.get('parsed', {})),
            analysis_result.get('analysis_time', 0)
        ))
        
        conn.commit()
        conn.close()
        return True
    
    def get_videos_by_keyword(self, keyword, task_type='describe', limit=10):
        """根据关键词查询视频"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute('''
            SELECT v.*, a.parsed_result 
            FROM videos v
            JOIN analysis_results a ON v.video_id = a.video_id
            WHERE a.task_type = ? 
            AND (a.parsed_result LIKE ? OR v.title LIKE ?)
            ORDER BY a.analyzed_at DESC
            LIMIT ?
        ''', (task_type, f'%{keyword}%', f'%{keyword}%', limit))
        
        rows = cursor.fetchall()
        columns = [desc[0] for desc in cursor.description]
        conn.close()
        
        return [dict(zip(columns, row)) for row in rows]
    
    def get_videos_with_objects(self, object_name, min_count=1):
        """查询包含特定对象的视频"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute('''
            SELECT v.*, a.parsed_result
            FROM videos v
            JOIN analysis_results a ON v.video_id = a.video_id
            WHERE a.task_type = 'grounding'
            AND a.query LIKE ?
            AND a.parsed_result LIKE ?
            ORDER BY a.analyzed_at DESC
        ''', (f'%{object_name}%', f'%{object_name}%'))
        
        rows = cursor.fetchall()
        columns = [desc[0] for desc in cursor.description]
        conn.close()
        
        # 进一步过滤
        filtered = []
        for row in rows:
            parsed_result = eval(row[columns.index('parsed_result')])
            objects = parsed_result.get('objects', [])
            if len(objects) >= min_count:
                filtered.append(dict(zip(columns, row)))
        
        return filtered
    
    def get_statistics(self):
        """获取统计信息"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        stats = {}
        
        # 视频总数
        cursor.execute('SELECT COUNT(*) FROM videos')
        stats['total_videos'] = cursor.fetchone()[0]
        
        # 分析结果总数
        cursor.execute('SELECT COUNT(*) FROM analysis_results')
        stats['total_analysis'] = cursor.fetchone()[0]
        
        # 按任务类型统计
        cursor.execute('SELECT task_type, COUNT(*) FROM analysis_results GROUP BY task_type')
        stats['by_task_type'] = dict(cursor.fetchall())
        
        # 最新分析时间
        cursor.execute('SELECT MAX(analyzed_at) FROM analysis_results')
        stats['latest_analysis'] = cursor.fetchone()[0]
        
        conn.close()
        return stats

5. 完整工作流:从采集到分析的全自动化

现在我们把所有组件组合起来,形成一个完整的自动化工作流:

import schedule
import time
from datetime import datetime
import threading

class VideoAutoCollector:
    """视频自动采集分析系统"""
    
    def __init__(self, config_path='config/config.yaml'):
        self.config = self._load_config(config_path)
        self.logger = self._setup_logging()
        
        # 初始化组件
        self.crawler = StaticVideoCrawler(self.config['crawler'])
        self.downloader = VideoDownloader(self.config['storage']['video_dir'])
        self.analyzer = ChordAnalyzer(
            chord_url=self.config['chord']['api_url'],
            timeout=self.config['chord']['timeout']
        )
        self.db = VideoAnalysisDB(self.config['storage']['database'])
        
        self.running = False
        self.logger.info("视频自动采集分析系统初始化完成")
    
    def _load_config(self, config_path):
        """加载配置文件"""
        import yaml
        with open(config_path, 'r', encoding='utf-8') as f:
            return yaml.safe_load(f)
    
    def _setup_logging(self):
        """设置日志"""
        import logging
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler('video_collector.log'),
                logging.StreamHandler()
            ]
        )
        return logging.getLogger('VideoAutoCollector')
    
    def crawl_task(self):
        """爬虫任务"""
        self.logger.info("开始执行爬虫任务")
        
        try:
            # 这里替换为实际的爬取目标
            target_urls = [
                "https://example.com/videos/page1",
                "https://example.com/videos/page2",
                # 添加更多目标URL
            ]
            
            # 执行爬虫
            videos = self.crawler.run(target_urls)
            self.logger.info(f"爬取到 {len(videos)} 个视频")
            
            # 保存视频信息到数据库
            for video in videos:
                self.db.save_video_info(video)
            
            return videos
            
        except Exception as e:
            self.logger.error(f"爬虫任务失败: {e}")
            return []
    
    def download_task(self, batch_size=5):
        """下载任务"""
        self.logger.info("开始执行下载任务")
        
        # 从数据库获取未下载的视频
        conn = sqlite3.connect(self.db.db_path)
        cursor = conn.cursor()
        
        cursor.execute('''
            SELECT video_id, url FROM videos 
            WHERE file_path IS NULL OR file_path = ''
            LIMIT ?
        ''', (batch_size,))
        
        pending_videos = cursor.fetchall()
        conn.close()
        
        downloaded = 0
        for video_id, url in pending_videos:
            self.logger.info(f"下载视频: {video_id}")
            
            file_path = self.downloader.download_video(url, video_id)
            if file_path:
                # 更新数据库中的文件路径
                conn = sqlite3.connect(self.db.db_path)
                cursor = conn.cursor()
                cursor.execute(
                    'UPDATE videos SET file_path = ?, download_time = ? WHERE video_id = ?',
                    (file_path, datetime.now().isoformat(), video_id)
                )
                conn.commit()
                conn.close()
                downloaded += 1
        
        self.logger.info(f"下载完成: {downloaded}/{len(pending_videos)} 个视频")
        return downloaded
    
    def analyze_task(self, batch_size=3):
        """分析任务"""
        self.logger.info("开始执行分析任务")
        
        # 从数据库获取已下载但未分析的视频
        conn = sqlite3.connect(self.db.db_path)
        cursor = conn.cursor()
        
        cursor.execute('''
            SELECT v.video_id, v.title, v.file_path 
            FROM videos v
            LEFT JOIN analysis_results a ON v.video_id = a.video_id AND a.task_type = 'describe'
            WHERE v.file_path IS NOT NULL AND v.file_path != '' AND a.id IS NULL
            LIMIT ?
        ''', (batch_size,))
        
        pending_videos = cursor.fetchall()
        conn.close()
        
        analyzed = 0
        for video_id, title, file_path in pending_videos:
            self.logger.info(f"分析视频: {title[:50]}...")
            
            # 分析视频(描述模式)
            result = self.analyzer.analyze_video(
                file_path,
                task_type="describe",
                query="详细描述视频内容"
            )
            
            if result and result['success']:
                # 解析结果
                parsed = self.analyzer.parse_analysis_result(result, {
                    'video_id': video_id,
                    'title': title
                })
                
                if parsed:
                    # 保存结果
                    self.db.save_analysis_result(video_id, {
                        'task_type': 'describe',
                        'query': '详细描述视频内容',
                        'result': result['result'],
                        'parsed': parsed,
                        'analysis_time': result['analysis_time']
                    })
                    analyzed += 1
                    
                    # 根据描述内容,决定是否需要进一步分析
                    description = result['result'].get('description', '').lower()
                    
                    # 如果视频包含特定内容,进行定位分析
                    if any(keyword in description for keyword in ['人物', '物体', '动作', '产品']):
                        self.logger.info(f"视频包含可定位内容,进行进一步分析: {video_id}")
                        
                        # 这里可以根据描述内容生成具体的查询
                        # 例如:如果描述中提到"人物",就查询"人物"
                        grounding_result = self.analyzer.analyze_video(
                            file_path,
                            task_type="grounding",
                            query="人物"  # 这里可以更智能地生成查询
                        )
                        
                        if grounding_result and grounding_result['success']:
                            parsed_grounding = self.analyzer.parse_analysis_result(grounding_result, {
                                'video_id': video_id,
                                'title': title
                            })
                            
                            self.db.save_analysis_result(video_id, {
                                'task_type': 'grounding',
                                'query': '人物',
                                'result': grounding_result['result'],
                                'parsed': parsed_grounding,
                                'analysis_time': grounding_result['analysis_time']
                            })
        
        self.logger.info(f"分析完成: {analyzed}/{len(pending_videos)} 个视频")
        return analyzed
    
    def run_once(self):
        """执行一次完整流程"""
        self.logger.info("开始执行完整工作流")
        
        # 1. 爬取视频
        videos = self.crawl_task()
        
        # 2. 下载视频
        if videos:
            self.download_task(batch_size=5)
        
        # 3. 分析视频
        self.analyze_task(batch_size=3)
        
        # 4. 清理旧文件
        self.downloader.cleanup_old_videos(keep_days=7)
        
        self.logger.info("工作流执行完成")
    
    def start_scheduler(self):
        """启动定时调度"""
        self.running = True
        
        # 设置定时任务
        schedule.every(1).hours.do(self.run_once)
        
        self.logger.info("定时调度器已启动,每小时执行一次")
        
        # 运行调度器
        while self.running:
            schedule.run_pending()
            time.sleep(60)  # 每分钟检查一次
    
    def stop(self):
        """停止系统"""
        self.running = False
        self.logger.info("系统正在停止...")
    
    def run_interactive(self):
        """交互式运行"""
        print("=" * 50)
        print("视频自动采集分析系统")
        print("=" * 50)
        
        while True:
            print("\n请选择操作:")
            print("1. 执行一次完整流程")
            print("2. 仅执行爬虫任务")
            print("3. 仅执行下载任务")
            print("4. 仅执行分析任务")
            print("5. 查看统计信息")
            print("6. 查询视频数据")
            print("7. 启动定时运行")
            print("8. 退出")
            
            choice = input("请输入选项 (1-8): ").strip()
            
            if choice == '1':
                self.run_once()
            elif choice == '2':
                self.crawl_task()
            elif choice == '3':
                batch_size = input("请输入批量大小 (默认5): ").strip()
                batch_size = int(batch_size) if batch_size else 5
                self.download_task(batch_size)
            elif choice == '4':
                batch_size = input("请输入批量大小 (默认3): ").strip()
                batch_size = int(batch_size) if batch_size else 3
                self.analyze_task(batch_size)
            elif choice == '5':
                stats = self.db.get_statistics()
                print("\n系统统计:")
                for key, value in stats.items():
                    print(f"  {key}: {value}")
            elif choice == '6':
                keyword = input("请输入查询关键词: ").strip()
                results = self.db.get_videos_by_keyword(keyword, limit=5)
                print(f"\n找到 {len(results)} 个相关视频:")
                for i, video in enumerate(results, 1):
                    print(f"{i}. {video['title'][:50]}... (ID: {video['video_id']})")
            elif choice == '7':
                print("启动定时运行,按Ctrl+C停止")
                try:
                    self.start_scheduler()
                except KeyboardInterrupt:
                    self.stop()
            elif choice == '8':
                print("感谢使用,再见!")
                break
            else:
                print("无效选项,请重新输入")

# 使用示例
if __name__ == "__main__":
    collector = VideoAutoCollector()
    
    # 交互式运行
    collector.run_interactive()
    
    # 或者直接运行一次
    # collector.run_once()
    
    # 或者启动定时运行
    # collector.start_scheduler()

6. 实际应用案例与效果展示

理论说再多,不如看实际效果。下面通过几个真实场景,展示这个系统能做什么。

6.1 案例一:竞品视频监控

场景:你负责监控3个主要竞品的短视频账号,需要每天收集他们的新视频,分析内容特点。

配置:

# 配置监控目标
competitors = [
    {
        'name': '品牌A',
        'urls': [
            'https://platform.com/user/brandA/videos',
            'https://platform.com/user/brandA/shorts'
        ],
        'keywords': ['新品', '促销', '活动', '教程']
    },
    {
        'name': '品牌B', 
        'urls': [
            'https://platform.com/user/brandB/videos',
            'https://platform.com/user/brandB/live'
        ],
        'keywords': ['直播', '评测', '用户', '反馈']
    },
    {
        'name': '品牌C',
        'urls': [
            'https://platform.com/user/brandC/videos'
        ],
        'keywords': ['故事', '情感', '品牌', '价值']
    }
]

# 运行监控
def monitor_competitors(competitors, days=7):
    """监控竞品视频"""
    results = {}
    
    for competitor in competitors:
        print(f"\n监控 {competitor['name']}...")
        
        # 收集视频
        all_videos = []
        for url in competitor['urls']:
            crawler = StaticVideoCrawler(config)
            videos = crawler.run([url])
            all_videos.extend(videos)
        
        print(f"  收集到 {len(all_videos)} 个视频")
        
        # 分析视频内容
        analyzer = ChordAnalyzer()
        competitor_results = []
        
        for video in all_videos[:5]:  # 先分析前5个
            print(f"  分析视频: {video['title'][:30]}...")
            
            # 下载视频
            downloader = VideoDownloader()
            video_path = downloader.download_video(video['url'], video['video_id'])
            
            if video_path:
                # 分析视频
                result = analyzer.analyze_video(
                    video_path,
                    task_type="describe",
                    query="详细描述视频内容,包括主题、画面、人物、动作"
                )
                
                if result and result['success']:
                    description = result['result'].get('description', '')
                    
                    # 检查是否包含关注的关键词
                    matched_keywords = []
                    for keyword in competitor['keywords']:
                        if keyword in description:
                            matched_keywords.append(keyword)
                    
                    competitor_results.append({
                        'title': video['title'],
                        'url': video['url'],
                        'description': description[:200] + '...' if len(description) > 200 else description,
                        'matched_keywords': matched_keywords,
                        'match_count': len(matched_keywords)
                    })
        
        results[competitor['name']] = {
            'total_videos': len(all_videos),
            'analyzed_videos': len(competitor_results),
            'videos': competitor_results
        }
    
    return results

# 生成报告
def generate_competitor_report(results):
    """生成竞品分析报告"""
    report = "竞品视频分析报告\n"
    report += "=" * 50 + "\n\n"
    
    for competitor_name, data in results.items():
        report += f"{competitor_name}:\n"
        report += f"  总视频数: {data['total_videos']}\n"
        report += f"  已分析数: {data['analyzed_videos']}\n\n"
        
        if data['videos']:
            report += "  最新视频分析:\n"
            for video in data['videos'][:3]:  # 显示最新的3个
                report += f"  - {video['title'][:40]}...\n"
                report += f"    匹配关键词: {', '.join(video['matched_keywords']) if video['matched_keywords'] else '无'}\n"
                report += f"    内容摘要: {video['description']}\n\n"
        
        report += "-" * 50 + "\n"
    
    return report

# 执行监控
results = monitor_competitors(competitors)
report = generate_competitor_report(results)
print(report)

输出效果:

竞品视频分析报告
==================================================

品牌A:
  总视频数: 24
  已分析数: 5

  最新视频分析:
  - 新品发布会现场实况...
    匹配关键词: 新品, 活动
    内容摘要: 视频展示了品牌A的新品发布会现场,主讲人在舞台中央介绍新产品特性,观众席坐满了人,现场气氛热烈...

  - 产品使用教程:如何快速上手...
    匹配关键词: 教程
    内容摘要: 视频逐步演示了产品的使用方法,从开箱到基本操作,画面清晰,讲解详细...

--------------------------------------------------
品牌B:
  总视频数: 18
  已分析数: 5

  最新视频分析:
  - 直播回顾:用户问答环节...
    匹配关键词: 直播, 用户
    内容摘要: 视频记录了品牌B的直播活动,主播在回答用户提问,画面右侧显示用户评论,互动频繁...

  - 产品评测:真实用户反馈...
    匹配关键词: 评测, 用户, 反馈
    内容摘要: 视频采访了多位真实用户,分享使用产品的体验,画面在不同用户间切换,表情自然...

6.2 案例二:热点内容分析

场景:你想了解当前热门视频的内容特点,找出受欢迎的视频有哪些共同点。

def analyze_hot_videos(topic, max_videos=20):
    """分析某个话题的热门视频"""
    print(f"分析话题 '{topic}' 的热门视频...")
    
    # 1. 爬取热门视频
    hot_videos = crawl_hot_videos(topic, max_videos)
    print(f"爬取到 {len(hot_videos)} 个热门视频")
    
    # 2. 分析视频内容
    analyzer = ChordAnalyzer()
    all_analysis = []
    
    for i, video in enumerate(hot_videos, 1):
        print(f"分析进度: {i}/{len(hot_videos)} - {video['title'][:30]}...")
        
        # 下载视频(仅下载前30秒以节省时间)
        downloader = VideoDownloader()
        video_path = downloader.download_video(video['url'], video['video_id'])
        
        if video_path:
            # 分析视频
            result = analyzer.analyze_video(
                video_path,
                task_type="describe",
                query="详细描述视频的前30秒内容,包括画面、人物、动作、场景"
            )
            
            if result and result['success']:
                parsed = analyzer.parse_analysis_result(result, video)
                all_analysis.append(parsed)
    
    # 3. 分析共同特征
    common_features = find_common_features(all_analysis)
    
    # 4. 生成分析报告
    report = generate_hot_video_report(topic, hot_videos, all_analysis, common_features)
    
    return report

def find_common_features(analysis_results):
    """找出共同特征"""
    from collections import Counter
    
    # 统计关键词出现频率
    all_keywords = []
    for result in analysis_results:
        keywords = result.get('keywords', [])
        all_keywords.extend(keywords)
    
    keyword_counts = Counter(all_keywords)
    
    # 统计常见元素
    common_elements = {
        'top_keywords': keyword_counts.most_common(10),
        'avg_complexity': sum(r.get('complexity_score', 0) for r in analysis_results) / len(analysis_results) if analysis_results else 0,
        'total_videos': len(analysis_results)
    }
    
    return common_elements

def generate_hot_video_report(topic, videos, analysis, features):
    """生成热门视频分析报告"""
    report = f"【{topic}】热门视频分析报告\n"
    report += "=" * 60 + "\n\n"
    
    report += "一、总体概况\n"
    report += f"分析视频数量: {features['total_videos']} 个\n"
    report += f"平均内容复杂度: {features['avg_complexity']:.2f}\n\n"
    
    report += "二、热门关键词\n"
    for keyword, count in features['top_keywords']:
        percentage = count / features['total_videos'] * 100
        report += f"  {keyword}: {count}次 ({percentage:.1f}%)\n"
    
    report += "\n三、视频内容分析\n"
    for i, (video, analysis_result) in enumerate(zip(videos[:5], analysis[:5]), 1):
        report += f"{i}. {video['title'][:40]}...\n"
        report += f"   点赞: {video.get('likes', 'N/A')} | 评论: {video.get('comments', 'N/A')}\n"
        
        if 'keywords' in analysis_result:
            report += f"   关键词: {', '.join(analysis_result['keywords'][:5])}\n"
        
        if 'description' in analysis_result:
            desc = analysis_result['description']
            if len(desc) > 100:
                desc = desc[:100] + "..."
            report += f"   内容摘要: {desc}\n"
        
        report += "\n"
    
    report += "四、内容创作建议\n"
    report += "基于分析结果,建议:\n"
    
    # 根据分析结果给出建议
    top_keywords = [k for k, _ in features['top_keywords'][:3]]
    if top_keywords:
        report += f"1. 重点使用高频关键词: {', '.join(top_keywords)}\n"
    
    if features['avg_complexity'] > 0.5:
        report += "2. 当前热门视频内容较丰富,建议保持一定的信息密度\n"
    else:
        report += "2. 当前热门视频内容较简单,建议突出重点,避免信息过载\n"
    
    report += "3. 前5秒出现关键画面能有效提升留存率\n"
    report += "4. 人物出镜+场景化演示的组合效果较好\n"
    
    return report

# 执行分析
topic = "健身教程"
report = analyze_hot_videos(topic, max_videos=10)
print(report)

输出效果:

【健身教程】热门视频分析报告
============================================================

一、总体概况
分析视频数量: 8 个
平均内容复杂度: 0.67

二、热门关键词
  动作: 8次 (100.0%)
  演示: 7次 (87.5%)
  教练: 6次 (75.0%)
  器械: 5次 (62.5%)
  节奏: 4次 (50.0%)

三、视频内容分析
1. 居家健身:10分钟全身燃脂...
   点赞: 15.2万 | 评论: 3200
   关键词: 动作, 演示, 节奏, 音乐, 计时
   内容摘要: 视频中教练演示了10个居家健身动作,每个动作30秒,节奏明快,背景音乐动感...

2. 健身房入门:器械使用全指南...
   点赞: 8.7万 | 评论: 1500
   关键词: 器械, 演示, 安全, 姿势, 重量
   内容摘要: 视频详细演示了健身房常见器械的使用方法,重点强调了安全注意事项和正确姿势...

四、内容创作建议
基于分析结果,建议:
1. 重点使用高频关键词: 动作, 演示, 教练
2. 当前热门视频内容较丰富,建议保持一定的信息密度
3. 前5秒出现关键画面能有效提升留存率
4. 人物出镜+场景化演示的组合效果较好

6.3 案例三:自动生成视频摘要

场景:你需要快速了解长视频的内容,生成文字摘要。

def generate_video_summary(video_path, max_points=5):
    """生成视频摘要"""
    analyzer = ChordAnalyzer()
    
    # 分析视频
    result = analyzer.analyze_video(
        video_path,
        task_type="describe",
        query="详细描述视频内容,按时间顺序列出关键点"
    )
    
    if not result or not result['success']:
        return "视频分析失败"
    
    description = result['result'].get('description', '')
    
    # 提取关键时间点(简单实现)
    lines = description.split('\n')
    key_points = []
    
    for line in lines:
        line = line.strip()
        if not line:
            continue
        
        # 查找时间信息(简单匹配)
        import re
        time_match = re.search(r'(\d+)[分:]?(\d*)秒?', line)
        
        if time_match or any(keyword in line for keyword in ['首先', '然后', '接着', '最后', '开始', '结束']):
            key_points.append(line)
    
    # 如果没有找到时间点,就取前几句
    if not key_points:
        key_points = [line for line in lines if line][:max_points]
    
    # 生成摘要
    summary = "视频内容摘要:\n\n"
    for i, point in enumerate(key_points[:max_points], 1):
        summary += f"{i}. {point}\n"
    
    # 添加总体描述
    if len(description) > 200:
        summary += f"\n总体描述:{description[:200]}..."
    else:
        summary += f"\n总体描述:{description}"
    
    return summary

# 使用示例
summary = generate_video_summary("product_tutorial.mp4")
print(summary)

7. 常见问题与解决方案

在实际使用中,你可能会遇到一些问题。这里总结了一些常见问题和解决方法:

7.1 爬虫被屏蔽怎么办?

问题:网站检测到爬虫,返回错误或限制访问。

解决方案:

  1. 降低请求频率:在请求之间添加随机延迟
  2. 轮换User-Agent:使用不同的浏览器标识
  3. 使用代理IP:特别是对于严格限制的网站
  4. 模拟人类行为:随机滚动页面、点击等
import random
import time

def smart_request(url, session, headers):
    """智能请求,避免被屏蔽"""
    # 随机延迟
    delay = random.uniform(1, 3)
    time.sleep(delay)
    
    # 随机User-Agent
    user_agents = [
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
        'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
    ]
    headers['User-Agent'] = random.choice(user_agents)
    
    try:
        response = session.get(url, headers=headers, timeout=10)
        return response
    except Exception as e:
        print(f"请求失败: {e}")
        return None

7.2 视频下载太慢怎么办?

问题:视频文件大,下载耗时太长。

解决方案:

  1. 限制视频质量:下载720p而非1080p
  2. 并行下载:同时下载多个视频
  3. 断点续传:确保网络中断后能继续下载
  4. 只下载前N秒:对于分析来说,通常不需要完整视频
def download_partial_video(url, video_id, duration=30):
    """只下载视频的前N秒"""
    output_path = f"./videos/{video_id}_partial.mp4"
    
    # 使用yt-dlp下载指定时长
    ydl_opts = {
        'outtmpl': output_path.replace('.mp4', ''),
        'format': 'best[height<=720]',
        'quiet': True,
        'no_warnings': True,
        'playlistend': 1,
        'match_filter': f'duration < {duration}',
    }
    
    try:
        with yt_dlp.YoutubeDL(ydl_opts) as ydl:
            ydl.download([url])
        return output_path
    except Exception as e:
        print(f"部分下载失败: {e}")
        return None

7.3 Chord分析结果不准确怎么办?

问题:Chord对某些视频的分析结果不理想。

解决方案:

  1. 优化查询:使用更具体、明确的描述
  2. 分段分析:将长视频分成多个片段分别分析
  3. 多角度分析:用不同的问题多次分析同一视频
  4. 人工校验:对重要结果进行人工抽查
def analyze_video_multiple_queries(video_path, queries):
    """用多个查询分析同一视频"""
    analyzer = ChordAnalyzer()
    results = []
    
    for query in queries:
        print(f"分析查询: {query}")
        result = analyzer.analyze_video(
            video_path,
            task_type="describe",
            query=query
        )
        
        if result and result['success']:
            results.append({
                'query': query,
                'result': result['result'].get('description', '')
            })
    
    # 合并结果
    combined = combine_results(results)
    return combined

def combine_results(results):
    """合并多个分析结果"""
    if not results:
        return ""
    
    # 简单合并:取最长的描述
    longest = max(results, key=lambda x: len(x['result']))
    return longest['result']

# 使用示例
queries = [
    "详细描述视频中的人物和动作",
    "描述视频的场景和背景",
    "说明视频的主要内容和目的"
]

result = analyze_video_multiple_queries("demo_video.mp4", queries)
print("合并后的分析结果:")
print(result[:500] + "..." if len(result) > 500 else result)

7.4 系统资源不足怎么办?

问题:同时处理多个视频时,内存或CPU不足。

解决方案:

  1. 限制并发数:一次只处理一个视频
  2. 分批处理:将大量视频分成小批次
  3. 监控资源使用:自动暂停或调整
  4. 清理临时文件:及时删除已处理的视频
import psutil
import os

def check_system_resources():
    """检查系统资源"""
    memory = psutil.virtual_memory()
    cpu_percent = psutil.cpu_percent(interval=1)
    
    return {
        'memory_percent': memory.percent,
        'cpu_percent': cpu_percent,
        'memory_available': memory.available // 1024 // 1024  # MB
    }

def process_videos_safely(video_paths, max_memory=80, max_cpu=90):
    """安全处理视频,避免资源过载"""
    processed = []
    
    for video_path in video_paths:
        # 检查系统资源
        resources = check_system_resources()
        
        if resources['memory_percent'] > max_memory:
            print(f"内存使用过高 ({resources['memory_percent']}%),暂停处理")
            time.sleep(60)
            continue
            
        if resources['cpu_percent'] > max_cpu:
            print(f"CPU使用过高 ({resources['cpu_percent']}%),暂停处理")
            time.sleep(30)
            continue
        
        # 处理视频
        result = process_single_video(video_path)
        if result:
            processed.append(result)
        
        # 处理完成后清理内存
        import gc
        gc.collect()
    
    return processed

8. 总结

通过这篇文章,我们完成了一个完整的视频数据自动化采集与分析系统的搭建。从最开始的Python爬虫编写,到Chord视频理解工具的集成,再到最后的自动化工作流,每一步都力求简单明了,即使是没有太多编程经验的朋友也能跟着做出来。

这个系统的核心价值在于自动化和智能化。自动化让我们从重复的手工操作中解放出来,智能化(通过Chord工具)让计算机能“看懂”视频内容,提取出对我们有用的信息。

回顾一下整个流程:

  1. 爬虫采集:自动从目标网站获取视频信息和链接
  2. 视频下载:将视频文件保存到本地
  3. 智能分析:用Chord工具分析视频内容,提取时空信息
  4. 数据存储:将分析结果结构化保存,方便查询
  5. 自动运行:设置定时任务,让系统自动工作

实际使用中,你可以根据自己的需求调整这个系统。比如:

  • 如果你只关心特定品牌的出现,可以调整Chord的查询关键词
  • 如果你需要分析视频的情感倾向,可以在描述查询中强调情感词汇
  • 如果你想监控某个话题的热度,可以定期爬取相关视频并分析内容变化

最重要的是,这个系统是一个起点,而不是终点。你可以基于这个框架,添加更多功能,比如:

  • 自动生成日报/周报
  • 设置关键词告警(当出现特定内容时通知你)
  • 与其他系
Logo

邀请您加入社区

更多推荐