"""
main.py - 从 Bilibili 获取视频弹幕并保存到 Excel 文件中
"""

import re
import time
import xml.etree.ElementTree as ET
import requests
import pandas as pd
from bs4 import BeautifulSoup

def search_videos(keyword, max_results=300):
    """
    根据关键词搜索视频，并返回视频 ID 列表。

    :param keyword: 搜索关键词
    :param max_results: 最大返回结果数量
    :return: 视频 ID 列表
    """
    video_list = []  # 存储视频 ID 的列表
    page = 1  # 当前页码
    while len(video_list) < max_results:
        # 构建搜索 URL
        search_url = f"http://search.bilibili.com/all?keyword={keyword}&page={page}"
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \
                (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
        }
        try:
            response = requests.get(search_url, headers=headers, timeout=10)  # 发送请求
            if response.status_code == 200:
                print(f"获取第 {page} 页搜索结果...")
                soup = BeautifulSoup(response.text, 'html.parser')  # 解析页面内容
                # 获取视频信息
                for item in soup.find_all('div', class_='bili-video-card'):
                    title_tag = item.find('a', href=True)  # 查找视频链接
                    if title_tag:
                        link = title_tag['href']  # 获取链接
                        if link.startswith('//'):
                            link = 'https:' + link  # 处理相对链接
                        video_id = link.split('/')[-2]  # 提取视频 ID
                        video_list.append(video_id)  # 添加视频 ID 到列表
                        print(f"视频 ID: {video_id}, 链接: {link}")
                    # 如果达到最大结果数，则停止获取
                    if len(video_list) >= max_results:
                        break
                page += 1  # 请求下一页
                time.sleep(1)  # 添加延迟，避免频繁请求
            else:
                print("搜索页面获取失败，状态码:", response.status_code)
                break
        except requests.exceptions.RequestException as req_error:
            print(f"搜索请求失败: {req_error}")
            break
    print(f"找到 {len(video_list)} 个视频.")  # 输出找到的视频数量
    return video_list

def get_cid(video_id):
    """
    根据视频 ID 获取弹幕 CID。

    :param video_id: 视频 ID
    :return: 弹幕 CID
    """
    video_url = f"http://www.bilibili.com/video/{video_id}"
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \
            (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
    }
    try:
        response = requests.get(video_url, headers=headers, timeout=10)  # 发送请求
        if response.status_code == 200:
            print("成功获取视频页面")
            match = re.search(r'"cid":(\d+)', response.text)  # 正则表达式匹配 cid
            if match:
                cid = match.group(1)  # 提取 cid
                print(f"找到 cid: {cid}")
                return cid
            print("未找到 cid")
            return None
        print("获取视频页面失败，状态码:", response.status_code)
        return None
    except requests.exceptions.RequestException as req_error:
        print(f"获取视频页面失败: {req_error}")
        return None

def get_danmaku(video_id, cid):
    """
    根据视频 ID 和 CID 获取弹幕数据。

    :param video_id: 视频 ID
    :param cid: 弹幕 CID
    :return: 弹幕数据的 XML 内容
    """
    danmaku_url = f"https://api.bilibili.com/x/v1/dm/list.so?oid={cid}"
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \
            (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
        "Referer": f"https://www.bilibili.com/video/{video_id}"
    }
    try:
        response = requests.get(danmaku_url, headers=headers, timeout=10)  # 发送请求
        if response.status_code == 200:
            print("成功获取弹幕数据")
            return response.content  # 返回弹幕数据
        print("获取弹幕数据失败，状态码:", response.status_code)
        return None
    except requests.exceptions.RequestException as req_error:
        print(f"获取弹幕数据失败: {req_error}")
        return None

def parse_danmaku(xml_content):
    """
    解析弹幕 XML 内容，提取弹幕文本。

    :param xml_content: 弹幕的 XML 内容
    :return: 弹幕文本列表
    """
    try:
        root = ET.fromstring(xml_content)  # 解析 XML
        danmaku_items = []  # 存储弹幕文本
        for item in root.findall('.//d'):
            content = item.text  # 提取弹幕文本
            if content:
                danmaku_items.append(content)  # 添加到列表
        return danmaku_items
    except ET.ParseError as parse_error:
        print(f"解析弹幕数据时出错: {parse_error}")
        return []

def save_danmakus_to_excel(all_danmakus, filename):
    """
    将弹幕数据保存到 Excel 文件中。

    :param all_danmakus: 所有弹幕内容
    :param filename: 保存的文件名
    """
    try:
        data_frame = pd.DataFrame(all_danmakus, columns=["弹幕内容"])  # 创建 DataFrame
        data_frame.to_excel(filename, index=False)  # 保存到 Excel 文件
        print(f"所有弹幕数据已保存到 {filename}")
    except FileNotFoundError:
        print(f"输出文件路径 {filename} 未找到，请检查路径是否正确。")
    except ValueError as value_error:
        print(f"数据转换错误: {value_error}. 请检查数据格式。")
    except PermissionError:
        print(f"没有权限写入文件 {filename}。请检查文件权限。")

def main():
    """
    主函数，执行视频搜索、获取弹幕并保存到 Excel 的主要逻辑。
    """
    keyword = "2024巴黎奥运会"  # 搜索关键词
    video_ids = search_videos(keyword)  # 搜索视频并获取视频 ID 列表
    all_danmakus = []  # 存储所有弹幕内容
    for video_id in video_ids:
        cid = get_cid(video_id)  # 获取视频的 cid
        print(f"正在获取视频 {video_id} 的弹幕...")
        if cid:
            xml_content = get_danmaku(video_id, cid)  # 获取弹幕数据
            if xml_content:
                danmaku_items = parse_danmaku(xml_content)  # 解析弹幕
                print(f"获取到 {len(danmaku_items)} 条弹幕")
                all_danmakus.extend(danmaku_items)  # 合并弹幕内容
            else:
                print(f"获取弹幕失败，视频 ID: {video_id}")
        time.sleep(2)  # 添加延迟，避免频繁请求
    # 将所有弹幕保存到 Excel 文件中
    save_danmakus_to_excel(all_danmakus, "all_danmakus.xlsx")

if __name__ == "__main__":
    main()  # 执行主函数
