尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python爬虫入门实战:从B站视频页抓取数据到应对基础反爬

Python爬虫入门实战:从B站视频页抓取数据到应对基础反爬 1. 从“看视频”到“抓数据”为什么B站爬虫是Python入门的绝佳实战如果你刚开始学Python或者刚接触爬虫大概率会听到一个建议“找个你感兴趣的网站练练手”。而B站几乎成了这个建议下的首选靶场。这背后其实有非常现实的逻辑B站的网页结构相对清晰数据丰富且贴近生活更重要的是你爬取的过程能直观地看到“代码”如何转化为“你想要的视频信息”这种正反馈对初学者至关重要。很多人卡在Day 3不是因为requests库多难而是没想明白我们写这几行代码到底在干什么以及B站这个“对手”有哪些简单的防御机制。今天我们就抛开那些笼统的“爬虫三步走”理论直接聚焦B站视频页。我会带你完整走一遍如何用Python的requests和BeautifulSoup从一个B站视频链接到最终拿到标题、UP主、播放量这些核心数据。过程中你会遇到第一个真正的“墙”——反爬机制我也会详细拆解最常见的User-Agent检测和简单参数验证并告诉你如何用最基础的方式绕过。这不是一个简单的代码复制粘贴而是理解一次完整的、带有轻微对抗性质的网络请求是如何构建和完成的。2. 环境准备别在第一步就踩进“包管理”的坑在动手写代码之前一个干净、可复现的Python环境是基石。很多新手会直接在自己的电脑全局Python环境里pip install这为后续的依赖冲突埋下了巨大隐患。我的建议是为每一个学习项目创建独立的虚拟环境。2.1 创建并激活虚拟环境打开你的终端Windows用CMD或PowerShellmacOS/Linux用Terminal执行以下命令。这里以项目目录名为bilibili_spider为例。# 创建项目目录并进入 mkdir bilibili_spider cd bilibili_spider # 使用Python内置的venv模块创建虚拟环境环境文件夹名为venv python -m venv venv创建完成后需要激活它Windows (PowerShell):.\venv\Scripts\Activate.ps1如果执行策略限制可能需要先运行Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser。Windows (CMD):.\venv\Scripts\activate.batmacOS/Linux:source venv/bin/activate激活成功后你的命令行提示符前会出现(venv)字样这表示你后续的所有Python和pip操作都只在这个“沙箱”里生效。2.2 安装核心依赖库在激活的虚拟环境下运行以下安装命令。我们将使用requests来发送HTTP请求用beautifulsoup4来解析HTML文档。(venv) pip install requests beautifulsoup4这里有一个关键的实操心得不要一次性安装所有你以为可能用到的库。比如lxml解析器虽然快但html.parser是Python内置的对于B站这种结构清晰的页面完全够用且避免了额外的安装依赖。我们遵循“按需安装”原则先只用beautifulsoup4它默认会使用html.parser。注意如果遇到网络超时或下载慢可以使用国内镜像源加速例如pip install requests beautifulsoup4 -i https://pypi.tuna.tsinghua.edu.cn/simple。安装完成后可以创建一个requirements.txt文件来记录依赖这是一个好习惯。(venv) pip freeze requirements.txt这样以后在任何机器上重建这个环境只需要pip install -r requirements.txt即可。3. 解剖一个B站视频页你的目标数据藏在哪里在写代码之前我们必须像侦探一样先手动检查一下“案发现场”。打开任意一个B站视频页面例如https://www.bilibili.com/video/BV1xx411c7mD。在页面上右键选择“检查”或“审查元素”Chrome/Firefox/Edge等浏览器均支持打开开发者工具。我们的目标是找到视频标题、UP主名称、播放量、弹幕数、点赞投币收藏数这些信息。它们不会凭空出现在Python变量里而是藏在服务器返回给浏览器的HTML源代码中或者通过后续的JavaScript请求加载。3.1 在HTML中直接寻找数据切换到开发者工具的“元素”Elements标签页。按下CtrlF或CmdF进行搜索。搜索标题尝试搜索视频标题中的关键词。你会发现标题通常被包裹在h1标签或某个带有classvideo-title、title属性的span标签里。例如你可能找到类似h1 title【Python爬虫】学习day-3 B站视频课的结构。搜索UP主搜索UP主的名字。通常会链接到UP主空间结构类似a classusername href//space.bilibili.com/xxxxx target_blank某UP主/a。搜索播放量等数据搜索“播放”或“观看”。你会发现播放量、弹幕数、发布时间等数据经常集中在一个区域比如一个classvideo-data的div里里面包含多个span标签。关键技巧不要只看一眼就觉得找到了。滚动一下找到的区域看看上下文的HTML结构。B站的前端结构可能会微调但核心数据的CSS类名class通常具有一定的语义和稳定性。记下包裹目标数据的标签名和其最独特的class属性这将是BeautifulSoup定位数据的“坐标”。3.2 识别潜在的动态加载数据有些数据比如详细的点赞、投币、收藏数可能不会在初始的HTML中完全呈现而是页面加载后由JavaScript发起另一个API请求获取并填充的。如何发现切换到开发者工具的“网络”Network标签页刷新页面。在纷繁复杂的请求列表中筛选XHR或Fetch类型的请求。这些通常是网页与服务器交换数据的API接口。寻找名称中包含info、stat、view等关键词的请求点击查看其“响应”Response内容很可能是JSON格式里面就包含了我们需要的结构化数据。例如你可能会找到一个类似https://api.bilibili.com/x/web-interface/view?aidxxxbvidxxx的请求其返回的JSON里就包含了视频的所有核心信息。对于初学者我强烈建议先从解析HTML开始因为这是理解网页基础结构的第一步。API请求通常涉及更复杂的参数如bvid、时间戳、签名等我们可以在掌握基础后再进阶。4. 编写第一版爬虫获取并解析HTML理解了目标结构现在开始写代码。在你的项目目录下创建一个Python文件比如spider_v1.py。4.1 构建一个“像浏览器”的请求直接使用requests.get(url)对B站发起请求很可能会得到一个错误页面或者状态码不是200。因为B站以及绝大多数现代网站有基础的反爬机制它会检查请求头Headers尤其是User-Agent。User-Agent是浏览器向网站表明自己身份的一个字符串。使用Pythonrequests的默认UA网站一眼就能认出你是爬虫。所以我们需要伪装。import requests from bs4 import BeautifulSoup # 目标视频URL url https://www.bilibili.com/video/BV1xx411c7mD # 请替换为实际你想爬的视频BV号 # 构建请求头模仿一个常见的Chrome浏览器 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: https://www.bilibili.com/, # 表示请求是从B站主页跳转过来的有时需要 } try: response requests.get(url, headersheaders, timeout10) response.raise_for_status() # 如果状态码不是200抛出HTTPError异常 response.encoding response.apparent_encoding # 让requests自动判断编码避免乱码 except requests.exceptions.RequestException as e: print(f请求失败: {e}) exit() print(f请求成功状态码: {response.status_code}) # 此时response.text 里就是页面的HTML源代码为什么这么做User-Agent是最基础的反爬校验。加上一个常见的浏览器UA是爬虫能获取到正常网页内容的“敲门砖”。Referer头则告诉服务器这个请求的来源页面对于一些有来源检查的接口或资源如视频流是必须的。4.2 使用BeautifulSoup提取目标数据现在我们有了HTML字符串需要用BeautifulSoup将其解析成一棵我们可以遍历和查询的树。# 使用BeautifulSoup解析HTML指定使用Python内置的html.parser soup BeautifulSoup(response.text, html.parser) # 接下来根据我们之前在开发者工具里观察到的结构来提取数据 # 注意以下选择器是示例B站前端代码可能变更需要你根据实际情况调整 # 1. 提取视频标题 - 通常在一个h1标签里或者有特定class的span里 # 假设我们观察到标题在 h1 class\video-title\ title\...\ 里 title_tag soup.find(h1, class_video-title) # 注意class_ 下划线因为class是Python关键字 if title_tag: video_title title_tag.get(title) or title_tag.text.strip() else: # 如果找不到尝试其他选择器或者打印一部分soup看看结构 video_title 未找到标题 print(f视频标题: {video_title}) # 2. 提取UP主名称 - 通常在指向个人空间的a标签里 # 假设结构是 a class\up-name\ href\...\UP主名/a up_tag soup.find(a, class_up-name) if up_tag: up_name up_tag.text.strip() else: up_name 未找到UP主 print(fUP主: {up_name}) # 3. 提取播放量等信息 - 这些数据经常在多个span里 # 假设它们在一个class为‘video-data’的div里 data_div soup.find(div, class_video-data) if data_div: # 找到这个div下所有的span spans data_div.find_all(span) # 通常播放量是第一个弹幕数是第二个等等。但这需要你根据实际页面观察顺序 # 这里只是演示遍历 for idx, span in enumerate(spans): print(f数据项{idx}: {span.text.strip()}) else: print(未找到视频数据区域)核心操作解析soup.find(tag, attrs): 找到第一个匹配指定标签和属性的元素。soup.find_all(tag, attrs): 找到所有匹配的元素返回一个列表。.get(‘attribute‘): 获取HTML元素的属性值如href、title、src。.text或.get_text(): 获取元素内包括所有子元素的文本内容.strip()用于去除首尾空白字符。第一个常见坑选择器失效。这是爬虫日常。B站前端工程师更新了页面样式你之前找到的classvideo-title可能变成了classvideo-title__main。怎么办重新打开开发者工具再次定位更新你的选择器。爬虫代码需要一定的维护成本。5. 应对反爬处理请求参数与简单验证如果你的第一版代码运行后打印出的response.text里包含“请求异常”、“安全验证”或者根本找不到你想要的数据那么你可能遇到了比User-Agent检查更进一步的验证。5.1 检查请求参数有些页面或API需要携带特定的查询参数Query Parameters才能返回正确数据。这些参数可能藏在初始页面的HTML中如一段JavaScript变量也可能是一个固定算法生成的。回到开发者工具的“网络”选项卡仔细查看你请求的那个视频页面对应的第一个文档Doc类型请求。点击它查看“标头”Headers下的“查询字符串参数”Query String Parameters。你可能看到除了BV号之外还有像p1分页、t123时间戳等参数。对于B站视频主页面通常只需要BV号。但对于视频列表、评论区、弹幕接口参数就复杂得多。例如获取弹幕的接口可能需要oid视频CID和date等参数。对于Day-3的目标我们暂时不涉及这些复杂API但要有这个意识查看网络请求复制完整参数。5.2 处理Cookie初步接触Cookie是服务器发给浏览器的一小段数据用于标识用户会话。有些内容需要登录后才能查看如某些UP主的专属视频或者简单的访问频率检查也会用到Cookie。如何获取最简单的方法是手动登录B站然后在开发者工具的“网络”选项卡中找一个请求查看其“请求头”找到Cookie那一长串字符串复制下来。在你的代码中可以将其添加到headers字典里headers { User-Agent: ..., Referer: ..., Cookie: 你的Cookie字符串很长很长... }重要警告不要泄露你的Cookie它等同于你的登录凭证。切勿将包含真实Cookie的代码上传到GitHub等公开平台。Cookie会过期B站的登录会话有过期时间过期后这个Cookie就失效了你的爬虫也就拿不到需要登录的数据了。伦理与法律仅将Cookie用于学习、测试你自己账户可访问的内容。未经授权爬取他人非公开数据是违法的。对于公开视频数据通常不需要Cookie。这里介绍是为了让你知道有这么一个机制存在。5.3 应对频率限制如果你短时间内发送大量请求IP地址可能会被暂时限制访问。这是网站保护服务器的最常见手段。给初学者的建议在请求间增加延时使用time.sleep()函数。例如每爬取一个页面后time.sleep(2)等待2秒。这能极大降低你被封锁的风险。切勿暴力请求学习阶段我们只爬取个别页面做分析不要编写循环去爬取海量页面。import time # ... 爬取一个视频的代码 ... time.sleep(3) # 暂停3秒模拟人类浏览间隔 # ... 再爬取下一个 ...6. 数据清洗与存储让结果可用爬取到的数据往往是粗糙的夹杂着HTML实体、多余的空格、换行符或者像“播放量”是“127.8万”这种带中文单位的字符串不便于后续分析。我们需要清洗。6.1 清洗文本数据def clean_text(text): 清洗文本去除首尾空白合并中间多个空格处理换行 if not text: return # 去除首尾空白字符 text text.strip() # 将任何空白字符空格、制表符、换行等序列替换为单个空格 import re text re.sub(r\s, , text) return text # 清洗播放量字符串例如将“127.8万”转换为数字1278000 def parse_play_count(play_str): play_str clean_text(play_str) if 万 in play_str: # 去除“万”字将数字部分转为浮点数再乘以10000 try: number float(play_str.replace(万, )) return int(number * 10000) except ValueError: return 0 else: # 如果没有“万”尝试直接转为整数 try: # 可能包含逗号如“1,234” return int(play_str.replace(,, )) except ValueError: return 0 # 示例使用 play_text “ 127.8万 ” # 假设从网页爬到的原始文本 clean_play clean_text(play_text) # 得到“127.8万” play_number parse_play_count(clean_play) # 得到 1278000 print(f原始文本: ‘{play_text}‘) print(f清洗后: ‘{clean_play}‘) print(f解析为数字: {play_number})6.2 将数据存储到文件最简单的存储方式是写入文本文件如.txt或结构化更好的CSV文件。存储到CSV文件 CSV逗号分隔值文件可以被Excel、Python的pandas库等轻松读取非常适合存储表格数据。import csv # 假设我们已经爬取并清洗好了数据 data_to_save [ [视频标题, UP主, 播放量, 弹幕数, 发布时间], [video_title, up_name, play_number, danmaku_count, pub_date], # ... 可以有多行数据 ] filename bilibili_video_data.csv with open(filename, w, newline, encodingutf-8-sig) as csvfile: # utf-8-sig支持Excel中文 writer csv.writer(csvfile) writer.writerows(data_to_save) print(f数据已保存到 {filename})存储到JSON文件 JSON格式更适合存储嵌套的、结构复杂的数据。import json video_info { title: video_title, up: up_name, play: play_number, danmaku: danmaku_count, pub_date: pub_date } filename video_info.json with open(filename, w, encodingutf-8) as f: # ensure_asciiFalse 确保中文正常显示indent2 让json格式化便于阅读 json.dump(video_info, f, ensure_asciiFalse, indent2) print(f数据已保存到 {filename})7. 完整代码示例与错误处理增强将以上所有步骤整合并加入更健壮的错误处理我们得到一个相对完整的、可用于学习的第一版B站视频信息爬虫。import requests from bs4 import BeautifulSoup import time import re import json def get_bilibili_video_info(bvid): 根据B站视频BV号获取基本信息 :param bvid: 视频的BV号例如 BV1xx411c7mD :return: 包含视频信息的字典如果失败返回None url fhttps://www.bilibili.com/video/{bvid} 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: https://www.bilibili.com/, } try: print(f正在请求: {url}) response requests.get(url, headersheaders, timeout15) response.raise_for_status() # 尝试多种编码方式确保中文不乱码 if response.encoding ISO-8859-1: response.encoding utf-8 else: response.encoding response.apparent_encoding except requests.exceptions.Timeout: print(错误请求超时) return None except requests.exceptions.HTTPError as e: print(fHTTP错误{e}) return None except requests.exceptions.RequestException as e: print(f请求异常{e}) return None soup BeautifulSoup(response.text, html.parser) # 初始化信息字典 info { bvid: bvid, title: None, up_name: None, play_count: None, danmaku_count: None, pub_date: None, } # 1. 提取标题 - 尝试多种可能的选择器 title_selectors [ (h1, {class: video-title}), (span, {class: video-title}), (div, {id: viewbox_report}).find(h1), # 另一种常见结构 ] for tag, attrs in title_selectors: if isinstance(attrs, dict): tag_obj soup.find(tag, attrsattrs) else: tag_obj attrs # 这里处理传入的是已找到的标签对象的情况 if tag_obj: info[title] tag_obj.get(title) or tag_obj.get_text(stripTrue) break # 2. 提取UP主 - 同样尝试多种选择器 up_selectors [ (a, {class: username}), (a, {class: up-name}), (div, {class: up-info}).find(a), ] for tag, attrs in up_selectors: up_tag soup.find(tag, attrsattrs) if up_tag: info[up_name] up_tag.get_text(stripTrue) break # 3. 提取播放、弹幕、发布时间区域 (这是一个难点因为B站前端结构多变) # 这里提供一个更通用的思路寻找包含“播放”、“弹幕”、“发布”等关键词的文本节点然后提取其父元素或相邻元素的数据 # 由于篇幅和复杂度这里简化为一个示例函数实际中可能需要更精细的解析 def extract_by_keyword(soup, keyword): 在页面中搜索包含关键词的文本并尝试提取其后的数字或日期 # 这是一个简化示例实际逻辑更复杂 elements soup.find_all(textre.compile(keyword)) for elem in elements: # 查看父元素或兄弟元素的文本 parent_text elem.parent.get_text(stripTrue) # 使用正则表达式从文本中提取数字 numbers re.findall(r[\d\.], parent_text) if numbers: return numbers[0] return None # 模拟提取实际效果可能不理想仅作演示 # info[play_count] extract_by_keyword(soup, 播放) # info[danmaku_count] extract_by_keyword(soup, 弹幕) # info[pub_date] extract_by_keyword(soup, 发布) # 更可靠的方法是定位到具体的结构化数据区域这里假设我们通过观察找到了一个数据容器 data_container soup.find(div, {class: video-data}) or soup.find(div, {id: viewbox_report}) if data_container: all_text data_container.get_text( , stripTrue) # 用空格连接所有文本 # 使用正则表达式匹配模式例如“播放数 127.8万 弹幕数 2.3万 发布时间 2023-10-27” play_match re.search(r播放[^0-9]*([\d\.万]), all_text) danmaku_match re.search(r弹幕[^0-9]*([\d\.万]), all_text) date_match re.search(r发布[^0-9]*([\d\-]), all_text) if play_match: info[play_count] parse_play_count(play_match.group(1)) if danmaku_match: info[danmaku_count] parse_play_count(danmaku_match.group(1)) if date_match: info[pub_date] date_match.group(1) return info def parse_play_count(count_str): 将‘127.8万’这样的字符串解析为整数 if not count_str: return None count_str count_str.strip() multiplier 1 if 万 in count_str: multiplier 10000 count_str count_str.replace(万, ) try: # 处理可能的小数点如‘127.8’ number float(count_str.replace(,, )) return int(number * multiplier) except ValueError: return None if __name__ __main__: # 替换成你想爬取的视频BV号 target_bvid BV1xx411c7mD video_info get_bilibili_video_info(target_bvid) if video_info: print(\n 视频信息获取成功 ) for key, value in video_info.items(): print(f{key}: {value}) # 保存为JSON文件 with open(f{target_bvid}_info.json, w, encodingutf-8) as f: json.dump(video_info, f, ensure_asciiFalse, indent2) print(f\n信息已保存至 {target_bvid}_info.json) else: print(视频信息获取失败。) # 礼貌性延时避免请求过快 time.sleep(2)这段代码的要点与潜在问题选择器备选提供了多个可能的选择器提高了代码的容错性。当B站前端微调时可能只需要更新这里的备选列表。数据提取的复杂性对于播放量等数据的提取代码展示了两种思路一是通过关键词定位再解析extract_by_keyword演示用实际不稳定二是通过正则表达式在数据容器的整体文本中匹配模式。后者在实际中更常用但正则表达式的模式需要你根据目标页面的具体文本格式来精心设计。错误处理对网络请求的各类异常超时、HTTP错误等进行了捕获避免程序因单次请求失败而崩溃。可扩展性将核心功能封装成函数get_bilibili_video_info便于复用和测试。运行这个脚本你应该能成功获取到指定B站视频的基本信息并保存为一个JSON文件。如果失败了大概率是选择器或正则表达式不匹配当前页面结构了。这时你需要回到第3步重新分析页面更新代码中的选择器或正则模式。这个过程就是爬虫工程师的日常调试。
返回列表