""" 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() # 执行主函数