行之助项目爬虫
- 爬取各大视频网页,爬取标准引体向上视频,由于其他视频网站标准视频较少且画质模糊,所以选择爬取bilibili网页,但是b站视频没有直接的下载选项,且反爬机制严格,所以爬取下载视频要通过将视频播放页 URL(如https://www.bilibili.com/video/BVxxxx/) 转换为真实可下载的地址
- 将原 B 站爬取代码拆解为 初始化配置、核心提取函数、下载函数、列表页爬取函数、主逻辑分页爬取 五个核心模块
初始化配置
# ===================== 初始化配置 =====================
warnings.filterwarnings('ignore')
# B站基础配置(对标博物馆爬虫的 base_url 和数据存储结构)
BASE_SEARCH_API = "https://api.bilibili.com/x/web-interface/search/type" # B站搜索接口
SEARCH_KEYWORD = "标准引体向上" # 搜索关键词
SAVE_DIR = "bilibili_标准引体向上视频" # 视频保存目录
BILI_COOKIE = "SESSDATA=43e2e463%2C1780059750%2C9d5a2%2Ab1GjBMW19dYjZO5PuMoZkELTXWVWoGmPodcKF2ttkknZYkj9R_4268hCcaROQjlbKkQESvklyWnZyVUJTBT..; bili_jct=cab605029354fe16dba846178f2185c; DedeUserID=390496635;" # B站登录Cookie
data = [] # 存储爬取的视频信息
初始化配置模块是B站视频爬取的基础准备环节,首先通过warnings.filterwarnings('ignore')忽略SSL相关警告,避免无关提示干扰程序运行;随后定义一系列核心基础参数,对标博物馆爬虫的base_url和数据存储结构:其中BASE_SEARCH_API指定B站搜索接口地址,作为列表页爬取的核心请求地址;SEARCH_KEYWORD设定“标准引体向上”为搜索关键词,明确爬取内容方向;SAVE_DIR定义视频文件的保存目录,规范文件存储路径;BILI_COOKIE配置B站登录凭证,确保接口请求具备合法权限;同时创建空列表data,用于存储爬取过程中各视频的名称、URL、下载状态等信息,为后续数据汇总和导出CSV提供基础载体,整体为后续的列表页爬取、详情提取、视频下载等环节奠定了统一的参数和环境基础。
4.核心提取函数
# ===================== 核心提取函数=====================
def extract_video_play_info(video_url):
"""
提取B站视频详情的下载地址、请求头
:param video_url: 视频详情页URL
:return: (video_download_url, download_headers) 或 None
"""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0",
"Referer": "https://www.bilibili.com/",
"Cookie": BILI_COOKIE
}
# 提取 BV 号
bv_match = re.search(r'BV\w+', video_url)
if not bv_match:
return None
bv_id = bv_match.group()
try:
# 步骤1:获取视频 CID
play_api = f"https://api.bilibili.com/x/web-interface/view?bvid={bv_id}"
response = requests.get(play_api, headers=headers, timeout=30, verify=False)
response.encoding = "utf-8"
play_data = response.json()
if play_data.get("code") != 0:
return None
cid = play_data.get("data", {}).get("cid")
if not cid:
return None
# 步骤2:获取视频下载地址
download_api = f"https://api.bilibili.com/x/player/playurl?bvid={bv_id}&cid={cid}&qn=80"
download_response = requests.get(download_api, headers=headers, timeout=30, verify=False)
download_data = download_response.json()
if download_data.get("code") != 0:
return None
# 优先 dash 格式,失败则切换 flv 格式
dash_data = download_data.get("data", {}).get("dash", {})
video_list = dash_data.get("video", [])
audio_list = dash_data.get("audio", [])
if not video_list or not audio_list:
flv_list = download_data.get("data", {}).get("durl", [])
if not flv_list:
return None
video_download_url = flv_list[0].get("url")
else:
video_download_url = video_list[0].get("baseUrl")
# 构造下载请求头
download_headers = {
"User-Agent": headers["User-Agent"],
"Referer": "https://www.bilibili.com/",
"Cookie": BILI_COOKIE,
"Range": "bytes=0-",
"Origin": "https://www.bilibili.com"
}
return (video_download_url, download_headers)
except Exception as e:
print(f"提取视频下载信息失败:{str(e)}")
return None
extract_video_play_info函数是 B 站视频下载的核心信息提取模块,接收视频详情页 URL 作为入参,对标博物馆爬虫提取文物图片 URL 和参数的逻辑,负责获取视频的有效下载地址和配套请求头。函数首先构造合规的请求头,从视频 URL 中提取唯一标识 BV 号(无则返回 None);随后分两步请求 B 站接口:第一步调用视频信息接口获取关键的 CID(无则返回 None),第二步携带 BV 号和 CID 调用下载地址接口,优先提取 dash 格式的视频下载地址,若该格式无效则切换为 flv 格式(均无效则返回 None);最后构造包含 Range 等字段的下载专用请求头,将下载地址和请求头以元组形式返回,全程通过 try-except 捕获接口请求、数据解析等异常,打印错误信息并返回 None,确保单个视频提取失败不影响整体流程,为后续视频下载提供了核心的地址和请求凭证支撑。
5.下载函数
# ===================== 下载函数 =====================
def download_file(url, headers, save_path):
"""
分块下载视频
:param url: 视频下载地址
:param headers: 下载请求头
:param save_path: 视频保存路径
:return: 下载状态(True/False)
"""
try:
response = requests.get(url, headers=headers, stream=True, timeout=60, verify=False)
total_size = int(response.headers.get("content-length", 0))
downloaded_size = 0
with open(save_path, "wb") as f:
for chunk in response.iter_content(chunk_size=1024 * 1024):
if chunk:
f.write(chunk)
downloaded_size += len(chunk)
if total_size > 0:
progress = (downloaded_size / total_size) * 100
print(f"\r下载进度:{progress:.1f}%", end="")
print()
return True
except Exception as e:
print(f"下载失败:{str(e)}")
if os.path.exists(save_path):
os.remove(save_path)
return False
download_file函数是 B 站视频的核心分块下载模块,接收视频下载地址、请求头和保存路径作为入参,用于实现视频文件的流式下载并返回下载状态。函数内部先通过 requests 发送带流式参数的 GET 请求获取视频数据,解析响应头中的文件总大小后初始化已下载大小;随后以 1MB 为分块单位遍历响应内容,将有效数据块写入目标文件,同时实时计算并打印下载进度;全程通过 try-except 捕获网络超时、文件写入等异常,若发生异常则打印错误信息,删除已生成的无效文件并返回 False;若下载全程无异常则返回 True,该函数对标博物馆爬虫的图片下载逻辑,实现了大文件分块下载、进度可视化、异常容错及无效文件清理的核心能力。
6.详情处理函数
# ===================== 详情处理函数 =====================
def process_video_download(video_info):
"""
处理单条视频的下载流程
:param video_info: 包含url/title的视频信息字典
"""
# 创建保存目录
if not os.path.exists(SAVE_DIR):
os.makedirs(SAVE_DIR, exist_ok=True)
video_url = video_info["url"]
video_title = video_info["title"]
# 清理文件名
video_title = re.sub(r'[\\/:*?"<>|]', "_", video_title)[:50]
output_path = os.path.join(SAVE_DIR, f"{video_title}.mp4")
# 跳过已下载
if os.path.exists(output_path):
print(f"\n视频已存在,跳过:{video_title}")
# 保存已存在的状态到数据列表
data.append({
"视频名称": video_title,
"视频URL": video_url,
"下载状态": "已存在"
})
return
print(f"\n开始下载:{video_title}")
play_info = extract_video_play_info(video_url)
if not play_info:
print(f"下载失败:{video_title}(无法获取下载地址)")
# 保存失败状态到数据列表
data.append({
"视频名称": video_title,
"视频URL": video_url,
"下载状态": "失败(无下载地址)"
})
return
video_download_url, headers = play_info
# 下载视频
print("下载视频流...")
if download_file(video_download_url, headers, output_path):
print(f"下载成功:{video_title}(路径:{SAVE_DIR})")
# 保存成功状态到数据列表
data.append({
"视频名称": video_title,
"视频URL": video_url,
"下载状态": "成功"
})
else:
# 保存失败状态到数据列表
data.append({
"视频名称": video_title,
"视频URL": video_url,
"下载状态": "失败(下载异常)"
})
process_video_download函数是 B 站单条视频的核心下载处理模块,接收包含视频 URL 和标题的字典作为入参,首先检查并创建视频保存目录,对视频标题进行非法字符清洗和长度限制后拼接出视频保存路径;若目标路径已存在对应视频文件,则打印跳过提示并将 “已存在” 的状态信息存入数据列表后直接返回;若文件不存在,先调用extract_video_play_info函数获取视频下载地址和请求头,若获取失败则打印提示并记录 “失败(无下载地址)” 状态;若获取成功,调用download_file函数下载视频流,根据下载结果分别记录 “成功” 或 “失败(下载异常)” 的状态信息并存入数据列表,全程对标博物馆爬虫的文物详情处理逻辑,完成了文件存储校验、核心信息提取、下载执行及状态记录的全流程闭环。
7.列表页爬取函数
# ===================== 列表页爬取函数 =====================
def scrape_search_page(keyword, total_num=100):
"""
爬取B站搜索列表页(分页),提取视频信息
:param keyword: 搜索关键词
:param total_num: 目标爬取总数
:return: 视频信息列表
"""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0",
"Referer": "https://search.bilibili.com/video",
"Accept": "application/json, text/plain, */*",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Cookie": BILI_COOKIE
}
video_links = []
page = 1
page_size = 20 # B站搜索接口单页最大20条
max_page = (total_num + page_size - 1) // page_size # 计算需要的总页数
# 分页爬取列表页
while page <= max_page and len(video_links) < total_num:
params = {
"keyword": keyword,
"page": page,
"page_size": page_size,
"search_type": "video",
"order": "totalrank",
"platform": "web",
"highlight": "1"
}
try:
time.sleep(2) # 延迟避免风控
response = requests.get(
BASE_SEARCH_API,
headers=headers,
params=params,
timeout=30,
verify=False,
proxies={"http": None, "https": None}
)
print(f"\n第 {page} 页接口响应状态码:", response.status_code)
response.encoding = "utf-8"
api_data = response.json()
if api_data.get("code") != 0:
print(f"第 {page} 页接口请求失败:", api_data.get("message"))
page += 1
continue
results = api_data.get("data", {}).get("result", [])
if not results:
print(f"第 {page} 页未找到匹配视频,停止翻页")
break
# 提取当前页视频信息
for item in results:
if len(video_links) >= total_num:
break
bv_id = item.get("bvid")
if not bv_id:
continue
video_title = item.get("title", "无标题").strip()
video_title = re.sub(r"<[^>]+>", "", video_title) # 去除html标签
video_url = f"https://www.bilibili.com/video/{bv_id}/"
video_links.append({"url": video_url, "title": video_title})
print(f"[{len(video_links)}] 找到视频:{video_title} -> {video_url}")
page += 1 # 翻下一页
except Exception as e:
print(f"第 {page} 页获取视频列表失败:", str(e))
page += 1
continue
return video_links
scrape_search_page函数作为 B 站视频爬取的核心列表页处理模块,以搜索关键词和目标爬取总数(默认 100 条)为入参,先配置符合 B 站接口规范的请求头,初始化分页参数(初始页码 1、单页最大 20 条、计算总页数)并创建视频信息存储列表;随后通过 while 循环分页请求 B 站搜索接口,循环中先增加 2 秒防风控延迟,再构造包含关键词、页码等的请求参数发送 GET 请求,校验接口响应状态和返回数据有效性,若接口失败则跳过当前页、无视频结果则终止循环;有效响应下遍历视频结果,提取 BV 号(无则跳过)、清洗标题、拼接视频详情页 URL,将信息存入列表并实时打印进度,同时校验是否达目标总数以终止当前页遍历;过程中捕获各类异常并继续翻页,最终返回包含所有有效视频 URL 和标题的列表,为后续下载提供数据源,整体逻辑对标博物馆藏品列表页爬取,实现了分页控制、数据清洗、异常容错、数量限制等核心能力。
8.分页爬取+数据保存
# ===================== 主逻辑:分页爬取+数据保存 =====================
if __name__ == "__main__":
# 配置爬取参数
TOTAL_VIDEO_NUM = 100 # 目标爬取100条
print(f"开始搜索关键词:{SEARCH_KEYWORD}(目标爬取 {TOTAL_VIDEO_NUM} 条)")
# 爬取视频列表
video_list = scrape_search_page(SEARCH_KEYWORD, TOTAL_VIDEO_NUM)
if not video_list:
print("\n未找到视频!请检查网络或稍后重试")
exit()
print(f"\n共找到 {len(video_list)} 条视频,开始下载...")
# 遍历下载视频
for idx, video in enumerate(video_list, 1):
print(f"\n===== 处理第 {idx}/{len(video_list)} 条 =====")
process_video_download(video)
time.sleep(1) # 下载延迟,避免风控
# 保存爬取数据到CSV
df = pd.DataFrame(data)
df.to_csv("B站标准引体向上视频爬取结果.csv", index=False, encoding="utf-8-sig", na_rep="NULL")
# 输出结束信息
print("\n所有下载任务结束!")
print(f"视频文件保存在:{os.path.abspath(SAVE_DIR)}")
print(f"爬取结果保存在:{os.path.abspath('B站标准引体向上视频爬取结果.csv')}")
主逻辑设定了要爬取的视频总数量TOTAL_VIDEO_NUM(目标 100 条),并基于 B 站搜索接口单页最大返回 20 条的规则,计算出需要爬取的最大页数max_page,同时初始化当前页码page为 1。
通过循环,根据当前页码page构造搜索接口的请求参数(包含页码、每页条数、关键词等),然后调用scrape_search_page函数内的核心逻辑进行当前页视频列表数据的抓取。
每次爬取完当前页后,先检查已抓取的视频数量是否达到目标总数TOTAL_VIDEO_NUM,若已达到则直接终止循环;若未达到,则判断当前页码是否超过最大需爬取页数max_page,同时检查接口返回结果是否存在有效视频数据:
若接口返回正常结果且仍有未爬取的页数,更新当前页码page(page += 1)进入下一轮循环继续爬取;
若接口返回无视频结果,或当前页码已超过最大页数,则结束循环。
9.总结:
由于b站反爬机制严格 区别于普通爬虫代码 爬取b站的代码中需用到Cookie 字符串
BILI_COOKIE = "SESSDATA=43e2e463%2C1780059750%2C9d5a2%2Ab1GjBMW19dYjZO5PuMoZkELTXWVWoGmPodcKF2ttkknZYkj9R_4268hCcaROQjlbKkQESvklyWnZyVUJTBT..; bili_jct=cab605029354fe16dba846178f2185c; DedeUserID=390496635;"
核心作用是让代码模拟你本人的登录状态访问 Bilibili
一、Cookie 的核心作用
绕过登录验证,获取完整搜索结果Bilibili 的部分视频会限制未登录用户搜索 / 观看。Cookie 携带你的账号登录状态,让代码被识别为「已登录用户」,能搜到所有你账号有权限查看的视频。
解锁视频下载权限Bilibili 的视频真实下载地址是加密的,且会校验登录状态:
未登录用户:无法获取有效下载地址;
已登录用户:接口会返回真实可下载的视频流地址,代码才能顺利下载。
避免被反爬拦截未携带 Cookie 的请求会被 Bilibili 识别为「爬虫」,直接返回 412/403 错误。Cookie 是模拟真实用户行为的关键,能大幅降低被反爬的概率。
拼接 Cookie 字符串
字段名 SESSDATA:会话标识,记录你的登录会话信息,证明你当前是已登录状态
bili_jct:防 CSRF 令牌,用于验证请求的合法性
DedeUserID:每个用户的唯一id
爬取成功并下载视频后提供给后端测试代码,爬取大量视频,放入的有效 JSON 文件越多,样本量越大,最终得到的结果会越精准、越趋向于大众化科学标准,优化评定标准引体向上的标准。
浙公网安备 33010602011771号