本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:批量爬取B站小视频是一项综合性Python实战任务,涵盖网络爬虫、API调用、HTML解析、模拟登录、数据存储等关键技术。本项目通过requests发送HTTP请求,使用BeautifulSoup或lxml解析页面内容,结合cookies管理实现身份验证,采用分页策略获取多页视频数据,并通过异常处理与重试机制提升爬虫稳定性。同时强调遵守网站规则与法律规范,确保爬取行为合法合规。项目适合提升Python编程能力与爬虫实战经验。
python批量爬取b站小视频

1. Python爬虫基础与实战概述

Python爬虫是一种通过程序自动化抓取网页数据的技术,广泛应用于数据采集、内容监控、市场分析等领域。随着短视频平台的兴起,爬取B站小视频数据成为许多数据爱好者和研究者的实践目标。本项目旨在通过实战方式,使用Python构建一个能够稳定抓取B站小视频信息的爬虫系统。我们将从基础的HTTP请求发送开始,逐步深入HTML解析、API分析与反爬策略应对,最终完成一个结构清晰、功能完整的爬虫项目。整个流程将结合requests、BeautifulSoup、lxml等主流库,帮助开发者掌握真实场景下的数据抓取技巧。

2. requests库发送HTTP请求

在构建Python爬虫的过程中,发送HTTP请求是获取目标网页内容的第一步,也是至关重要的一步。本章将围绕Python中广泛使用的 requests 库展开讲解,从基础的安装与使用,到请求头、参数设置,再到高级请求处理技巧,逐步深入,帮助读者掌握发送HTTP请求的核心技能,并为后续B站小视频爬取项目打下坚实基础。

2.1 requests库的安装与基本使用

requests 是一个简洁而强大的用于发送HTTP请求的第三方Python库,它隐藏了底层socket通信的复杂性,使开发者可以专注于业务逻辑的实现。

2.1.1 安装requests模块

在使用 requests 之前,需要先进行安装。可以通过以下命令安装:

pip install requests

安装完成后,可以在Python脚本中导入模块:

import requests

参数说明:

  • pip install requests :使用pip包管理器安装requests模块;
  • import requests :导入requests库,以便后续调用其功能。

逻辑分析
安装是使用任何第三方库的第一步。requests库由于其简洁性和功能全面性,已经成为Python中处理HTTP请求的标准工具之一。

2.1.2 发送GET与POST请求的基本方法

HTTP协议中常见的请求方法有 GET 和 POST。GET用于获取资源,POST用于提交数据。

示例1:发送GET请求
response = requests.get('https://www.example.com')
print(response.status_code)  # 输出状态码
print(response.text)         # 输出响应内容
示例2:发送POST请求
data = {'key1': 'value1', 'key2': 'value2'}
response = requests.post('https://httpbin.org/post', data=data)
print(response.json())  # 输出JSON格式的响应

参数说明:

  • requests.get(url) :向指定URL发送GET请求;
  • requests.post(url, data=data) :向指定URL发送POST请求,携带表单数据;
  • response.status_code :HTTP响应状态码,如200表示成功;
  • response.text :服务器返回的文本内容;
  • response.json() :将响应内容解析为JSON格式。

逻辑分析
上述代码分别展示了GET和POST请求的发送方式。GET请求通常用于获取数据,而POST请求则用于提交数据。通过响应对象可以获取服务器返回的状态码和内容,便于后续处理。

2.2 请求头与参数设置

为了模拟真实用户的访问行为,常常需要在请求中设置请求头(Headers)和参数(Params),以避免被目标服务器识别为爬虫。

2.2.1 设置User-Agent与Referer伪装浏览器

服务器通常会通过User-Agent判断访问来源。我们可以通过设置Headers模拟浏览器访问。

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Referer': 'https://www.google.com/'
}
response = requests.get('https://www.example.com', headers=headers)
print(response.request.headers)

参数说明:

  • User-Agent :标识客户端浏览器类型和操作系统;
  • Referer :表示请求来源页面;
  • headers=headers :指定请求头信息。

逻辑分析
通过设置User-Agent和Referer字段,可以伪装成真实浏览器访问目标网站,从而降低被反爬机制拦截的风险。

2.2.2 URL参数传递与POST数据提交

GET请求传参
params = {
    'search': 'python',
    'page': 1
}
response = requests.get('https://www.example.com/search', params=params)
print(response.url)  # 输出最终请求的URL
POST请求提交表单数据
data = {
    'username': 'test',
    'password': '123456'
}
response = requests.post('https://www.example.com/login', data=data)
print(response.text)
POST请求提交JSON数据
json_data = {
    'title': 'Hello',
    'content': 'World'
}
response = requests.post('https://api.example.com/data', json=json_data)
print(response.json())

参数说明:

  • params=params :用于GET请求的URL参数;
  • data=data :用于POST请求的表单数据;
  • json=json_data :用于POST请求的JSON格式数据。

逻辑分析
GET请求通过URL参数传递信息,而POST请求则通过表单或JSON格式提交数据。根据目标接口的接受格式选择合适的方式提交数据是成功爬取的关键。

2.3 获取B站视频页面的响应数据

在本节中,我们将结合前面所学知识,尝试获取B站小视频页面的响应内容,并分析其结构。

2.3.1 分析B站小视频页面的URL结构

B站的视频页面通常具有如下格式:

https://www.bilibili.com/video/BV1sT4y1Z7K9

其中 BV1sT4y1Z7K9 是视频的唯一标识符。小视频(如动态视频)的链接结构可能略有不同,例如:

https://www.bilibili.com/video/BV1pQ4y1Q7J9

可以通过浏览器开发者工具(F12)查看页面的HTML结构,找到视频数据的位置。

2.3.2 使用requests获取HTML响应内容

import requests

url = 'https://www.bilibili.com/video/BV1pQ4y1Q7J9'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}

response = requests.get(url, headers=headers)
if response.status_code == 200:
    with open('bilibili_video_page.html', 'w', encoding='utf-8') as f:
        f.write(response.text)
    print("HTML内容已保存")
else:
    print(f"请求失败,状态码:{response.status_code}")

参数说明:

  • url :目标视频页面的URL;
  • headers=headers :伪装浏览器访问;
  • response.text :获取响应的HTML内容;
  • with open(...) as f: :以UTF-8编码将HTML内容写入本地文件。

逻辑分析
该段代码模拟浏览器访问B站视频页面,并将HTML内容保存到本地文件。后续章节中,我们将使用解析库(如BeautifulSoup或lxml)从中提取所需数据。

2.4 高级请求处理

在实际爬虫开发中,我们往往需要处理更复杂的网络请求场景,如保持会话状态、控制超时、捕获异常等。本节将介绍 requests 中的一些高级用法。

2.4.1 使用Session对象管理请求会话

Session对象可以跨请求保持某些参数,如Cookies、Headers等,常用于模拟登录场景。

import requests

session = requests.Session()
session.headers.update({
    'User-Agent': 'Mozilla/5.0'
})

# 发送多个请求,保持会话
response1 = session.get('https://httpbin.org/headers')
response2 = session.get('https://httpbin.org/cookies/set/sessioncookie/123456789')
response3 = session.get('https://httpbin.org/cookies')

print(response3.text)

参数说明:

  • requests.Session() :创建一个会话对象;
  • session.headers.update({...}) :为该会话统一设置请求头;
  • 多个请求之间共享Cookies和Headers。

逻辑分析
使用Session对象可以模拟浏览器会话行为,适用于需要登录或跨页面保持状态的爬取任务。

2.4.2 超时控制与异常捕获

网络请求可能会因为服务器无响应、连接超时等原因失败,因此我们需要设置超时时间并捕获异常。

import requests
from requests.exceptions import Timeout, ConnectionError, HTTPError

try:
    response = requests.get('https://www.bilibili.com', timeout=5)
    response.raise_for_status()  # 如果状态码不是2xx,抛出HTTPError
except Timeout:
    print("请求超时,请重试")
except ConnectionError:
    print("连接失败,请检查网络")
except HTTPError as e:
    print(f"HTTP错误:{e}")
except Exception as e:
    print(f"发生未知错误:{e}")

参数说明:

  • timeout=5 :设置请求超时时间为5秒;
  • raise_for_status() :如果响应状态码不是2xx,抛出HTTPError;
  • 捕获不同类型的异常以进行相应处理。

逻辑分析
通过设置超时时间和捕获异常,可以提高爬虫的健壮性,避免因网络问题导致程序崩溃。

小结

在本章中,我们系统地学习了 requests 库的基本使用方法,包括发送GET和POST请求、设置请求头与参数、获取B站视频页面的HTML内容,以及使用Session管理会话和处理异常。这些技能是构建Python爬虫的基础,为后续章节中的HTML解析与数据提取做好了准备。

在下一章中,我们将深入探讨如何使用 BeautifulSoup lxml 等解析库,从HTML文档中提取出我们感兴趣的数据,特别是B站小视频的相关信息。

3. HTML解析与数据提取技术

在爬虫开发过程中,获取网页内容只是第一步,真正的挑战在于如何从 HTML 文档中精准提取所需信息。HTML 页面结构复杂,数据嵌套层级多样,不同网站采用的前端渲染方式也各不相同。因此,掌握 HTML 解析技术是构建高效、稳定爬虫系统的关键。

在本章中,我们将深入探讨 Python 中主流的 HTML 解析库,重点介绍 BeautifulSoup lxml 的使用方法,并通过 B站小视频页面的实战解析,展示如何提取视频标题、链接、作者、播放量等关键字段信息。同时,我们还将对两种解析方式在性能、易用性等方面进行对比分析,帮助开发者根据实际项目需求选择合适的解析策略。

3.1 BeautifulSoup库的使用

BeautifulSoup 是 Python 中最常用的 HTML 解析库之一,以其语法简洁、易于上手著称。它非常适合处理不规范或格式混乱的 HTML 文档,是初学者和中小型爬虫项目的首选工具。

3.1.1 BeautifulSoup的基本解析方式

BeautifulSoup 的核心是将 HTML 文档解析为一棵树形结构(即 DOM 树),然后通过方法或属性来访问其中的节点。我们首先需要安装该库:

pip install beautifulsoup4

接下来,我们使用 requests 获取 B站小视频页面的 HTML 内容,并使用 BeautifulSoup 解析:

import requests
from bs4 import BeautifulSoup

url = "https://www.bilibili.com/video/BV1sT4y1Z7K9"  # 示例视频页面
headers = {
    "User-Agent": "Mozilla/5.0"
}

response = requests.get(url, headers=headers)
html_content = response.text

# 使用BeautifulSoup解析HTML
soup = BeautifulSoup(html_content, "html.parser")
print(soup.title.string)  # 输出网页标题

代码逻辑分析:

  • 第 1~2 行:导入 requests BeautifulSoup 模块。
  • 第 4 行:定义目标页面 URL。
  • 第 5~7 行:设置请求头,模拟浏览器访问,防止被反爬。
  • 第 9 行:使用 requests.get() 发送 GET 请求。
  • 第 10 行:提取响应内容中的 HTML 字符串。
  • 第 13 行:创建 BeautifulSoup 对象,指定解析器为 html.parser
  • 第 14 行:打印页面标题,展示解析后的结果。

3.1.2 使用find和find_all定位视频元素

在实际项目中,我们往往需要从页面中提取多个相同类型的数据,例如多个视频标题、链接等。此时, find_all() 方法就派上了用场。

以下代码演示如何提取 B站小视频页面中所有推荐视频的标题和链接:

# 查找所有推荐视频的标题和链接
recommendations = soup.find_all("div", class_="video-item matrix")

for item in recommendations:
    title = item.find("a", class_="title").text
    link = item.find("a", class_="title")["href"]
    print(f"标题: {title}, 链接: {link}")

参数说明与逻辑分析:

  • find_all() 方法用于查找所有匹配的标签。
  • 第一行查找 div 标签,且 class "video-item matrix" ,这通常表示推荐视频区域。
  • 在循环中,我们使用 find() 方法进一步定位标题和链接。
  • text 属性用于获取标签内的文本内容, ["href"] 用于获取超链接地址。

这种方式适用于结构较为清晰、静态加载的 HTML 页面。对于动态加载的内容,需要结合 API 接口进行数据获取,这将在后续章节中详细介绍。

3.2 lxml库的XPath解析方法

虽然 BeautifulSoup 易于使用,但在处理大规模 HTML 页面时,其性能相对较弱。 lxml 是另一个强大的解析库,支持 XPath 语法,能够高效地定位节点,适用于需要高性能解析的场景。

3.2.1 XPath语法基础与节点定位

XPath 是一种在 XML 和 HTML 文档中查找信息的语言,它通过路径表达式来定位节点。

安装 lxml

pip install lxml

下面是一个使用 lxml 解析 B站视频页面标题的示例:

from lxml import etree

# 使用requests获取HTML内容
url = "https://www.bilibili.com/video/BV1sT4y1Z7K9"
headers = {
    "User-Agent": "Mozilla/5.0"
}
response = requests.get(url, headers=headers)
html_content = response.content

# 构建XPath解析器
tree = etree.HTML(html_content)
title = tree.xpath('//h1[@class="video-title"]/text()')
print("视频标题:", title[0].strip())

逐行解析说明:

  • 第 1 行:导入 etree 模块。
  • 第 4~7 行:设置请求头并发送请求。
  • 第 8 行:获取响应的二进制内容。
  • 第 11 行:使用 etree.HTML() 将 HTML 内容转换为可解析的文档树。
  • 第 12 行:使用 XPath 表达式 //h1[@class="video-title"] 定位标题节点,并提取文本内容。
  • 第 13 行:输出标题并去除前后空格。

3.2.2 提取B站视频的标题与链接信息

下面我们将使用 XPath 提取推荐视频的标题和链接:

# 提取推荐视频的标题和链接
titles = tree.xpath('//div[@class="video-item matrix"]//a[@class="title"]/text()')
links = tree.xpath('//div[@class="video-item matrix"]//a[@class="title"]/@href')

for i in range(len(titles)):
    print(f"标题: {titles[i]}, 链接: {links[i]}")

参数说明与逻辑分析:

  • xpath() 方法返回一个列表,包含所有匹配的节点内容。
  • text() 提取文本内容, @href 提取属性值。
  • 使用 for 循环将标题与链接一一对应输出。

这种方式在处理结构清晰、嵌套层级复杂的 HTML 页面时效率更高,特别适合处理大量数据的爬取任务。

3.2.3 性能对比与可视化分析

我们可以通过 timeit 模块简单对比 BeautifulSoup lxml 的解析效率:

import timeit

def bs4_parse():
    soup = BeautifulSoup(html_content, "html.parser")
    soup.find_all("div", class_="video-item matrix")

def lxml_parse():
    tree = etree.HTML(html_content)
    tree.xpath('//div[@class="video-item matrix"]')

bs4_time = timeit.timeit(bs4_parse, number=100)
lxml_time = timeit.timeit(lxml_parse, number=100)

print(f"BeautifulSoup耗时:{bs4_time:.4f}s")
print(f"lxml耗时:{lxml_time:.4f}s")

执行结果(示例):

解析方式 耗时(秒)
BeautifulSoup 1.2345
lxml 0.3456

通过表格可以直观看出 lxml 在性能上的优势,尤其在处理大规模页面时更为明显。

3.3 多解析方式的对比与选择

在实际项目中,我们需要根据页面结构、数据量、开发效率等因素,选择合适的解析方式。

3.3.1 BeautifulSoup与lxml的性能对比

特性 BeautifulSoup lxml
易用性
性能 中等
对不规范HTML容忍度
支持XPath
学习成本

从表格可以看出, BeautifulSoup 更适合开发初期快速原型设计,而 lxml 更适合数据量大、性能要求高的生产环境。

3.3.2 根据网页结构选择合适的解析策略

  • 静态页面 :HTML 内容一次性加载完成,适合使用 BeautifulSoup lxml
  • 动态页面 :部分内容通过 JavaScript 异步加载,建议使用 Selenium 或分析 API 接口获取数据。
  • 结构复杂页面 :多层嵌套结构,建议使用 lxml + XPath,定位更精准。
  • 快速开发需求 :优先使用 BeautifulSoup ,代码简洁,易于调试。

3.4 实战:定位B站小视频的字段信息

在本节中,我们将结合前面介绍的技术,完整提取 B站小视频页面的关键字段信息,如视频标题、作者、播放量、点赞数等,并以结构化形式输出。

3.4.1 视频标题、作者、播放量等字段的提取

以下是完整的字段提取代码:

from bs4 import BeautifulSoup
import requests

url = "https://www.bilibili.com/video/BV1sT4y1Z7K9"
headers = {
    "User-Agent": "Mozilla/5.0"
}
response = requests.get(url, headers=headers)
html = response.text

soup = BeautifulSoup(html, "html.parser")

# 提取视频标题
title = soup.find("h1", class_="video-title").get_text(strip=True)

# 提取作者
author = soup.find("span", class_="up-name").get_text(strip=True)

# 提取播放量
play_count = soup.find("span", class_="view").get_text(strip=True)

# 提取点赞数
like_count = soup.find("span", class_="like").get_text(strip=True)

print(f"标题: {title}")
print(f"作者: {author}")
print(f"播放量: {play_count}")
print(f"点赞数: {like_count}")

逻辑分析:

  • 使用 BeautifulSoup 定位页面中各字段的 HTML 标签及类名。
  • get_text(strip=True) 去除多余空格,提升可读性。
  • 输出结果为结构化文本,便于后续处理。

3.4.2 结构化输出解析后的数据

为了便于后续处理,我们可以将提取的数据以字典形式组织,并保存为 JSON 或 CSV 文件:

import json

video_info = {
    "title": title,
    "author": author,
    "play_count": play_count,
    "like_count": like_count
}

# 保存为JSON文件
with open("video_info.json", "w", encoding="utf-8") as f:
    json.dump(video_info, f, ensure_ascii=False, indent=4)

print("数据已保存至video_info.json")

mermaid流程图展示:

graph TD
    A[发送GET请求] --> B[获取HTML响应]
    B --> C[使用BeautifulSoup解析HTML]
    C --> D[定位视频字段元素]
    D --> E[提取字段内容]
    E --> F[组织为字典结构]
    F --> G[保存为JSON文件]

该流程图清晰展示了整个数据提取与保存的过程,有助于开发者理解爬虫执行逻辑。

通过本章的学习,我们掌握了两种主流 HTML 解析方式的使用方法,并通过实战项目提取了 B站小视频页面的关键信息。在下一章中,我们将深入探讨 B站 API 的调用方式,进一步提升爬虫的稳定性和数据获取能力。

4. B站API分析与爬虫进阶技术

在掌握了基础的网页爬取与数据解析技术后,我们将进入更加复杂的爬虫进阶领域。在这一章节中,我们将重点探讨如何通过分析 B站的 API 接口,获取结构化的视频数据,并实现模拟登录、分页处理、异常控制与数据存储等高级功能。同时,我们也将讨论如何应对常见的反爬机制,以及在实际开发中应遵守的法律和道德规范。

4.1 模拟登录与Cookie管理

4.1.1 登录流程分析与Cookie获取

B站的登录机制通常依赖 Cookie 和 Session 来维持用户状态。要模拟登录,首先需要分析其登录请求的 URL、请求方式(POST)、所需参数(如用户名、密码、验证码等)以及请求头信息(如 User-Agent、Referer)。

使用浏览器的开发者工具(F12),在“Network”选项卡中观察登录请求:

  • URL: https://api.bilibili.com/x/web-interface/login/v2
  • 请求方式:POST
  • 参数示例:
  • username :用户账号
  • password :经过加密的密码(通常为 MD5 或 RSA 加密)
  • keep_state :是否保持登录状态(1/0)
import requests

login_url = 'https://api.bilibili.com/x/web-interface/login/v2'
headers = {
    'User-Agent': 'Mozilla/5.0',
    'Referer': 'https://www.bilibili.com/'
}

data = {
    'username': 'your_username',
    'password': 'your_encrypted_password',
    'keep_state': 1
}

session = requests.Session()
response = session.post(login_url, headers=headers, data=data)

# 获取 Cookie
cookies = session.cookies.get_dict()
print("登录后的 Cookie:", cookies)

代码解释:
- 使用 requests.Session() 创建一个会话对象,可以自动维护 Cookie。
- post() 方法发送登录请求。
- 登录成功后,使用 cookies.get_dict() 获取当前会话的 Cookie。

注意: B站的密码加密方式较为复杂,实际开发中建议使用官方 API 或模拟浏览器登录,避免直接破解加密逻辑。

4.1.2 使用requests.Session维护登录状态

一旦成功登录,后续请求可以通过该 Session 对象自动携带 Cookie,实现状态保持。

# 使用 Session 发送需要登录的请求
user_info_url = 'https://api.bilibili.com/x/web-interface/nav'
response = session.get(user_info_url, headers=headers)
print(response.json())

逻辑分析:
- 此请求用于获取当前登录用户的信息。
- 如果 Cookie 有效,将返回用户昵称、等级、头像等信息。

4.2 B站API接口的抓取与调用

4.2.1 使用浏览器开发者工具分析API请求

在浏览器中打开 B站首页,进入“小视频”页面,按下 F12 打开开发者工具,切换到“Network”选项卡,刷新页面,观察加载数据的请求。

通常,B站小视频数据由以下接口提供:

  • 接口地址: https://api.bilibili.com/x/web-interface/wbi/index/top/rcmd
  • 请求方式:GET
  • 参数:
  • ps :每页数量(默认为 20)
  • fresh_type :刷新类型(推荐为 3)
  • w_rid wts :WBI 验签参数(需通过特定算法生成)

提示: B站对部分 API 接口增加了 WBI 验签机制,需要计算 w_rid wts 参数,否则返回 403 Forbidden

4.2.2 构造带参数的API请求获取视频数据

为了获取小视频数据,我们需要构造一个包含必要参数的 GET 请求。

import requests
from datetime import datetime
import hashlib

def get_wbi_sign(params: dict):
    # 示例 WBI 签名算法(实际需根据最新规则生成)
    salt = 'your_salt_key'  # 模拟盐值
    param_str = '&'.join(f"{k}={v}" for k, v in sorted(params.items()))
    sign = hashlib.md5((param_str + salt).encode()).hexdigest()
    return sign

# 构造请求参数
params = {
    'ps': 20,
    'fresh_type': 3,
    'wts': int(datetime.now().timestamp()),
    'w_rid': get_wbi_sign({'ps': 20, 'fresh_type': 3})
}

headers = {
    'User-Agent': 'Mozilla/5.0',
    'Referer': 'https://www.bilibili.com/'
}

api_url = 'https://api.bilibili.com/x/web-interface/wbi/index/top/rcmd'
response = requests.get(api_url, headers=headers, params=params)
data = response.json()

# 输出前5个视频标题
for item in data['data']['item']:
    print(item['desc'])

逻辑分析:
- get_wbi_sign() 是一个简化的 WBI 签名生成函数,实际开发中需根据官方规则实现。
- wts 为当前时间戳, w_rid 为签名值。
- 使用 requests.get() 构造 GET 请求, params 自动拼接到 URL 中。
- 响应数据为 JSON 格式,包含视频描述、作者、播放量等信息。

参数说明:
- ps :每页显示的视频数。
- fresh_type :刷新类型,3 表示推荐视频。
- wts :时间戳,用于防重放攻击。
- w_rid :签名值,防止请求伪造。

表格:B站小视频 API 返回字段说明

字段名 类型 说明
desc string 视频描述文本
author string 视频作者名称
play int 播放量
like int 点赞数
pub_time string 视频发布时间
cover string 视频封面图片地址

4.3 分页策略与URL构造

4.3.1 动态加载页面与分页机制分析

B站小视频页面采用“无限滚动”方式加载内容,每次滚动到底部会触发一次新的 API 请求,携带不同的 ps offset 参数。通过观察多个请求可以发现, offset 是每次返回的 next_offset ,用于标识下一页的起始位置。

分页逻辑流程图(mermaid):

graph TD
    A[开始爬取第一页] --> B[发送GET请求]
    B --> C{是否成功?}
    C -->|是| D[解析当前页数据]
    D --> E[获取next_offset]
    E --> F[构造下一页URL]
    F --> G[继续爬取下一页]
    G --> C
    C -->|否| H[记录失败页码]

4.3.2 构造多页请求的URL序列

我们可以在循环中构造多个请求,实现多页爬取:

base_url = 'https://api.bilibili.com/x/web-interface/wbi/index/top/rcmd'
offset = 0

for page in range(1, 6):  # 爬取前5页
    params = {
        'ps': 20,
        'fresh_type': 3,
        'wts': int(datetime.now().timestamp()),
        'offset': offset,
        'w_rid': get_wbi_sign({'ps': 20, 'fresh_type': 3, 'offset': offset})
    }
    response = requests.get(base_url, headers=headers, params=params)
    data = response.json()

    for item in data['data']['item']:
        print(f"Page {page} - {item['desc']}")

    offset = data['data']['next_offset']  # 更新 offset

代码说明:
- 每次循环更新 offset 值。
- 使用 get_wbi_sign() 重新生成签名,确保请求合法性。
- 输出每页的视频描述信息。

4.4 异常处理与数据存储

4.4.1 网络异常与重试机制设计

在实际爬取过程中,网络不稳定、API 限流等问题会导致请求失败。我们需要设计重试机制和异常处理逻辑。

import time

def fetch_data(url, params, headers, retries=3):
    for i in range(retries):
        try:
            response = requests.get(url, params=params, headers=headers, timeout=10)
            if response.status_code == 200:
                return response.json()
            else:
                print(f"尝试第 {i+1} 次失败,状态码:{response.status_code}")
                time.sleep(2)
        except requests.exceptions.RequestException as e:
            print(f"请求失败:{e}")
            time.sleep(2)
    return None

逻辑分析:
- fetch_data() 函数封装了 GET 请求,并支持重试机制。
- timeout=10 设置请求超时时间为 10 秒。
- 若连续失败 3 次,返回 None

4.4.2 将爬取结果保存为CSV与JSON格式

我们使用 pandas 库将数据保存为 CSV 和 JSON 格式。

import pandas as pd

video_list = []  # 存储所有视频数据

for item in data['data']['item']:
    video_info = {
        'desc': item['desc'],
        'author': item['author'],
        'play': item['play'],
        'like': item['like'],
        'pub_time': item['pub_time'],
        'cover': item['cover']
    }
    video_list.append(video_info)

df = pd.DataFrame(video_list)

# 保存为 CSV
df.to_csv('bilibili_videos.csv', index=False)

# 保存为 JSON
df.to_json('bilibili_videos.json', orient='records', lines=True)

输出格式说明:
- CSV 文件适合 Excel 打开查看。
- JSON 文件适合用于 API 接口或数据导入数据库。

4.5 反爬应对与合法性遵循

4.5.1 设置请求间隔与IP代理池

B站对频繁请求有严格的限制,因此我们需要控制请求频率,使用 IP 代理池避免被封禁。

import time
import random

# 模拟代理池
proxies_pool = [
    {'http': 'http://192.168.1.10:8080'},
    {'http': 'http://192.168.1.11:8080'},
    {'http': 'http://192.168.1.12:8080'}
]

for url in url_list:
    proxy = random.choice(proxies_pool)  # 随机选择代理
    try:
        response = requests.get(url, headers=headers, proxies=proxy, timeout=10)
        # 处理响应
    except:
        print("请求失败,尝试更换代理")
    time.sleep(random.uniform(1, 3))  # 随机等待1~3秒

建议:
- 使用付费代理服务提高稳定性。
- 每次请求之间间隔 1~3 秒,避免触发频率限制。

4.5.2 遵守robots协议与法律规范

B站的 robots.txt 文件位于 https://www.bilibili.com/robots.txt ,其中规定了哪些页面允许爬取,哪些禁止访问。

示例:

User-agent: *
Disallow: /search/
Disallow: /account/

法律建议:
- 不爬取用户隐私数据。
- 不频繁请求,避免影响服务器性能。
- 若用于商业用途,请遵守《中华人民共和国网络安全法》和《个人信息保护法》。

下一章节将继续讲解如何构建完整的爬虫项目,并实现模块化封装、数据清洗与部署优化等内容。

5. Python爬虫项目完整流程实践

5.1 项目结构设计与模块划分

在构建一个完整的爬虫项目时,良好的项目结构设计是确保代码可维护性和可扩展性的关键。一个典型的B站小视频爬虫项目可以按照功能模块划分为以下几个目录和文件:

bilibili_video_crawler/
│
├── config/                   # 配置文件目录
│   └── settings.py           # 存放配置参数,如请求头、URL模板等
│
├── crawler/                  # 爬虫主模块
│   ├── __init__.py
│   ├── request_handler.py    # 请求发送模块
│   ├── parser.py             # 数据解析模块
│   └── storage.py            # 数据存储模块
│
├── data/                     # 存储爬取的数据文件
│   └── output.csv
│
├── logs/                     # 日志文件目录
│   └── crawler.log
│
├── utils/                    # 工具类模块
│   └── helper.py             # 辅助函数,如去重、日志配置等
│
└── main.py                   # 主程序入口

通过这样的结构,我们可以将请求、解析、存储等功能模块独立封装,提高代码的复用性和可读性。例如, request_handler.py 负责发送网络请求, parser.py 负责解析HTML内容,而 storage.py 则负责将数据写入文件。

5.2 爬取B站小视频的完整流程实现

5.2.1 编写主程序控制爬虫执行流程

主程序 main.py 是整个爬虫流程的控制中心。它的主要职责是协调请求、解析与存储模块,并控制爬取的执行流程。以下是一个简化的主程序实现示例:

import time
from crawler.request_handler import send_request
from crawler.parser import parse_video_list
from crawler.storage import save_to_csv

BASE_URL = "https://www.bilibili.com/video/video_list?page={}"

def main():
    all_videos = []
    for page in range(1, 6):  # 爬取前5页
        url = BASE_URL.format(page)
        print(f"正在爬取第 {page} 页:{url}")
        html = send_request(url)
        videos = parse_video_list(html)
        all_videos.extend(videos)
        time.sleep(2)  # 设置请求间隔,避免频繁请求被封IP
    save_to_csv(all_videos)
    print("爬取完成,数据已保存。")

if __name__ == "__main__":
    main()

上述代码中,我们使用 send_request 发送请求获取HTML内容,再通过 parse_video_list 解析出视频列表,最后调用 save_to_csv 将数据保存。每页请求间隔2秒,以减少对服务器的压力。

5.2.2 实现多页数据自动爬取与去重

为了避免重复爬取相同数据,我们需要在主程序中加入去重机制。可以使用集合 seen 来记录已经爬取的视频ID:

seen = set()

def parse_video_list(html):
    # 假设解析出的视频信息为字典列表,每个字典包含 "title", "url", "id"
    video_list = []
    for item in parsed_items:
        if item['id'] not in seen:
            seen.add(item['id'])
            video_list.append(item)
    return video_list

这样,每次解析时都会判断视频ID是否已经存在,避免重复存储。

5.3 数据清洗与pandas处理

5.3.1 使用pandas加载和清洗爬取数据

爬取完成后,通常需要对数据进行清洗处理。我们可以使用 pandas 对数据进行加载、去重、格式转换等操作。以下是一个示例代码:

import pandas as pd

# 加载CSV数据
df = pd.read_csv("data/output.csv")

# 去除重复行
df.drop_duplicates(subset=["video_id"], keep="first", inplace=True)

# 清洗播放量字段,转为整数
df["views"] = df["views"].str.replace("万", "").astype(float) * 10000

# 保存清洗后的数据
df.to_csv("data/cleaned_output.csv", index=False)

通过 pandas ,我们可以非常方便地进行数据处理和分析。

5.3.2 数据统计与可视化展示(可选)

如果你希望对爬取到的数据进行可视化分析,可以使用 matplotlib seaborn 库进行图表绘制。例如,绘制视频播放量分布直方图:

import matplotlib.pyplot as plt

df = pd.read_csv("data/cleaned_output.csv")
plt.hist(df["views"], bins=30, color="skyblue", edgecolor="black")
plt.title("B站小视频播放量分布")
plt.xlabel("播放量")
plt.ylabel("数量")
plt.show()

这将帮助你更直观地理解数据特征。

5.4 项目优化与部署建议

5.4.1 性能优化与并发爬取(多线程/异步)

为了提高爬虫效率,可以引入并发机制。以下是一个使用 concurrent.futures 实现多线程爬取的示例:

from concurrent.futures import ThreadPoolExecutor

def fetch_page(page):
    url = BASE_URL.format(page)
    print(f"正在爬取第 {page} 页:{url}")
    html = send_request(url)
    return parse_video_list(html)

def main():
    all_videos = []
    with ThreadPoolExecutor(max_workers=3) as executor:  # 同时运行3个线程
        results = executor.map(fetch_page, range(1, 6))
        for result in results:
            all_videos.extend(result)
    save_to_csv(all_videos)
    print("多线程爬取完成。")

这种方式可以显著加快爬取速度,但要注意控制并发数量,避免触发反爬机制。

5.4.2 日志记录与定时任务部署方案

为了方便调试和维护,建议为项目添加日志记录功能。可以通过 logging 模块实现:

import logging

logging.basicConfig(
    filename="logs/crawler.log",
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)

logging.info("开始爬取第1页")

此外,可以使用 cron 或 Python 的 schedule 库实现定时爬取任务:

import schedule
import time

def job():
    print("执行每日爬取任务...")
    main()

schedule.every().day.at("10:00").do(job)

while True:
    schedule.run_pending()
    time.sleep(1)

这将确保你的爬虫每天在指定时间自动运行。

(本章完)

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:批量爬取B站小视频是一项综合性Python实战任务,涵盖网络爬虫、API调用、HTML解析、模拟登录、数据存储等关键技术。本项目通过requests发送HTTP请求,使用BeautifulSoup或lxml解析页面内容,结合cookies管理实现身份验证,采用分页策略获取多页视频数据,并通过异常处理与重试机制提升爬虫稳定性。同时强调遵守网站规则与法律规范,确保爬取行为合法合规。项目适合提升Python编程能力与爬虫实战经验。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐