You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

173 lines
7.0 KiB

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