Python 爬虫实战:Selenium 爬取 B 站排行榜数据(动态加载页面的显式等待策略)

引言

在当今数据驱动的时代,网络爬虫已成为获取互联网信息的重要手段。B站(哔哩哔哩)作为国内最大的视频弹幕网站,其排行榜数据蕴含着丰富的用户行为和内容趋势信息。然而,B站页面采用动态加载技术,传统requests库难以直接获取数据。本文将通过Selenium自动化测试工具,结合显式等待策略,手把手教你实现B站排行榜数据的完整爬取流程。

为什么选择Selenium?

  1. 动态页面处理:完美应对JavaScript渲染内容
  2. 交互模拟:支持点击、滚动等复杂操作
  3. 元素定位:提供CSS选择器/XPath等多种定位方式
  4. 等待机制:显式等待解决页面加载延迟问题

一、环境搭建与工具准备

1.1 开发环境配置

# 创建虚拟环境(可选)
python -m venv bilibili_spider
source bilibili_spider/bin/activate  # Linux/Mac
# bilibili_spider\Scripts\activate  # Windows

# 安装依赖库
pip install selenium webdriver-manager pandas

关键库说明

  • selenium:Web自动化测试核心库
  • webdriver-manager:浏览器驱动自动管理
  • pandas:数据存储与处理

1.2 浏览器驱动配置

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

# 自动下载并配置ChromeDriver
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

优势

  • 自动匹配浏览器版本
  • 免去手动下载驱动的繁琐
  • 跨平台兼容性(Windows/Mac/Linux)

二、页面结构深度分析

2.1 目标页面定位

打开B站排行榜页面:

https://www.bilibili.com/v/popular/rank/all

页面特征

  1. 动态加载:初始加载10条数据,滚动加载更多
  2. 反爬机制:需处理Cookie弹窗
  3. 数据分布:视频信息嵌套在li.rank-item元素中

2.2 开发者工具使用技巧

  1. 元素定位

    • 右键检查 → Copy → Copy selector
    • 示例:视频标题选择器 #rank-list li.rank-item div.info a.title
  2. 网络监控

    • Network标签页 → XHR过滤
    • 分析真实数据接口(备用方案)
  3. 响应式设计

    • 移动端适配:检查不同屏幕尺寸下的元素变化

三、显式等待策略详解

3.1 等待机制对比

等待类型 特点 适用场景
强制等待 time.sleep() 固定时间 简单调试
隐式等待 driver.implicitly_wait() 全局元素加载
显式等待 WebDriverWait + 条件判断 精准控制特定元素加载

3.2 显式等待核心实现

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def explicit_wait(driver, selector, timeout=10):
    """
    自定义显式等待函数
    :param driver: WebDriver实例
    :param selector: CSS选择器
    :param timeout: 最大等待时间(秒)
    :return: 找到的WebElement
    """
    try:
        element = WebDriverWait(driver, timeout).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, selector))
        )
        return element
    except Exception as e:
        print(f"元素等待超时:{selector},错误信息:{str(e)}")
        return None

关键参数说明

  • timeout:最大等待时间(建议5-15秒)
  • poll_frequency:检查间隔(默认0.5秒)
  • ignored_exceptions:忽略的异常类型

3.3 等待策略应用场景

  1. 页面加载:EC.presence_of_element_located
  2. 元素可见:EC.visibility_of_element_located
  3. 元素可点击:EC.element_to_be_clickable
  4. 文本变化:EC.text_to_be_present_in_element

四、数据解析与存储全流程

4.1 单条数据解析模板

def parse_video_item(item):
    """
    解析单个视频条目
    :param item: WebElement对象
    :return: 字典格式数据
    """
    try:
        return {
            "rank": item.find_element(By.CSS_SELECTOR, ".num").text,
            "title": item.find_element(By.CSS_SELECTOR, ".title").text,
            "link": item.find_element(By.CSS_SELECTOR, ".title").get_attribute("href"),
            "author": item.find_element(By.CSS_SELECTOR, ".up-name").text,
            "play_count": item.find_element(By.CSS_SELECTOR, ".play").text,
            "danmaku": item.find_element(By.CSS_SELECTOR, ".danmaku").text,
            "publish_time": item.find_element(By.CSS_SELECTOR, ".time").text.strip(),
        }
    except Exception as e:
        print(f"解析失败:{str(e)}")
        return None

字段说明

  • 排名:.num
  • 标题:.title
  • 链接:href属性
  • 作者:.up-name
  • 播放量:.play
  • 弹幕数:.danmaku
  • 发布时间:.time

4.2 滚动加载实现

def scroll_to_load(driver, scroll_times=5):
    """
    模拟滚动加载更多内容
    :param driver: WebDriver实例
    :param scroll_times: 滚动次数
    """
    for _ in range(scroll_times):
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(2)  # 等待数据加载

注意事项

  • 滚动间隔时间需根据网络状况调整
  • 需配合显式等待确保新元素加载完成

4.3 数据存储方案

import pandas as pd

def save_to_csv(data_list, filename="bilibili_rank.csv"):
    """
    保存数据到CSV文件
    :param data_list: 解析后的数据列表
    :param filename: 输出文件名
    """
    if not data_list:
        print("无数据可保存")
        return

    # 转换为DataFrame
    df = pd.DataFrame(data_list)
    
    # 数据清洗
    df['play_count'] = df['play_count'].str.replace('万次观看', '').astype(float) * 10000
    df['danmaku'] = df['danmaku'].str.replace('条弹幕', '').astype(int)
    
    # 保存文件
    df.to_csv(filename, index=False, encoding='utf-8-sig')
    print(f"数据已保存至:{filename}")

优化点

  • 数值转换:处理"万次观看"等中文单位
  • 编码处理:使用utf-8-sig避免Excel乱码
  • 类型转换:确保数据可进行后续分析

五、反爬机制应对方案

5.1 常见反爬策略

  1. Cookie验证:未登录状态限制访问
  2. 请求频率:高频访问触发验证码
  3. IP封禁:同一IP短时请求过多
  4. UA检测:识别非浏览器User-Agent

5.2 应对措施实现

from selenium.webdriver.chrome.options import Options

def init_driver_with_anti_anti():
    """
    初始化带反爬防护的浏览器驱动
    """
    chrome_options = Options()
    chrome_options.add_argument("--disable-blink-features=AutomationControlled")  # 禁用自动化特征
    chrome_options.add_argument(f"user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
    
    # 随机User-Agent(需安装fake_useragent库)
    # from fake_useragent import UserAgent
    # ua = UserAgent()
    # chrome_options.add_argument(f"user-agent={ua.random}")

    driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
    
    # 处理Cookie弹窗
    try:
        close_btn = explicit_wait(driver, ".banner-link.international-home")
        if close_btn:
            close_btn.click()
    except:
        pass
    
    return driver

进阶方案

  • 使用代理IP池:chrome_options.add_argument(f'--proxy-server={proxy}')
  • 控制请求频率:time.sleep(random.uniform(2,5))
  • 模拟人类操作:随机鼠标移动、滚动行为

六、完整代码实现与优化

6.1 完整爬虫代码

import time
import random
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
import pandas as pd

class BilibiliRankSpider:
    def __init__(self):
        self.driver = self.init_driver()
        self.base_url = "https://www.bilibili.com/v/popular/rank/all"
        self.data_list = []

    def init_driver(self):
        """初始化浏览器驱动"""
        chrome_options = webdriver.ChromeOptions()
        chrome_options.add_argument("--disable-blink-features=AutomationControlled")
        chrome_options.add_argument("--start-maximized")
        driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
        return driver

    def handle_login_popup(self):
        """处理登录弹窗"""
        try:
            close_btn = WebDriverWait(self.driver, 5).until(
                EC.element_to_be_clickable((By.CSS_SELECTOR, ".banner-link.international-home"))
            )
            close_btn.click()
        except:
            pass

    def scroll_load(self, times=3):
        """滚动加载更多内容"""
        for _ in range(times):
            self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            time.sleep(random.uniform(1, 3))  # 随机等待时间

    def parse_data(self):
        """解析页面数据"""
        items = self.driver.find_elements(By.CSS_SELECTOR, "#rank-list li.rank-item")
        for item in items:
            try:
                data = {
                    "rank": item.find_element(By.CSS_SELECTOR, ".num").text,
                    "title": item.find_element(By.CSS_SELECTOR, ".title").text,
                    "link": item.find_element(By.CSS_SELECTOR, ".title").get_attribute("href"),
                    "author": item.find_element(By.CSS_SELECTOR, ".up-name").text,
                    "play_count": item.find_element(By.CSS_SELECTOR, ".play").text,
                    "danmaku": item.find_element(By.CSS_SELECTOR, ".danmaku").text,
                    "publish_time": item.find_element(By.CSS_SELECTOR, ".time").text.strip(),
                }
                self.data_list.append(data)
            except Exception as e:
                print(f"解析失败:{str(e)}")

    def save_data(self, filename="bilibili_rank.csv"):
        """保存数据到CSV"""
        if not self.data_list:
            print("未获取到数据")
            return

        df = pd.DataFrame(self.data_list)
        df['play_count'] = df['play_count'].str.replace('万次观看', '').astype(float) * 10000
        df['danmaku'] = df['danmaku'].str.replace('条弹幕', '').astype(int)
        df.to_csv(filename, index=False, encoding='utf-8-sig')
        print(f"数据保存成功,共{len(self.data_list)}条记录")

    def run(self):
        """主执行流程"""
        try:
            self.driver.get(self.base_url)
            self.handle_login_popup()
            
            # 显式等待页面主体加载
            WebDriverWait(self.driver, 10).until(
                EC.presence_of_element_located((By.ID, "rank-list"))
            )
            
            # 初始解析
            self.parse_data()
            
            # 滚动加载更多
            self.scroll_load(times=2)
            self.parse_data()
            
            # 保存数据
            self.save_data()
            
        except Exception as e:
            print(f"爬虫运行出错:{str(e)}")
        finally:
            self.driver.quit()

if __name__ == "__main__":
    spider = BilibiliRankSpider()
    spider.run()

6.2 代码优化方向

  1. 异常重试机制
from tenacity import retry, stop_after_attempt, wait_random

@retry(stop=stop_after_attempt(3), wait=wait_random(min=1, max=3))
def robust_parse(self):
    # 解析逻辑
  1. 分布式爬取

    • 使用Scrapy-Redis实现分布式队列
    • 配合Celery进行任务调度
  2. 动态代理池

import requests

def get_proxy():
    # 从代理API获取可用代理
    resp = requests.get("http://proxy-api.com/get")
    return resp.json()['proxy']

chrome_options.add_argument(f'--proxy-server={get_proxy()}')

七、常见问题解决

7.1 元素定位失败

  1. 检查选择器是否正确(建议使用copy selector
  2. 确认元素是否在iframe中(需切换frame)
  3. 尝试XPath定位:
//div[contains(@class,'rank-item')]//a[@class='title']

7.2 页面加载超时

  1. 增加显式等待时间
  2. 分步加载策略:
# 先等待关键元素
WebDriverWait(driver, 20).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, ".rank-item:nth-child(20)"))
)

7.3 反爬验证处理

  1. 实现验证码自动识别(需OCR服务)
  2. 使用已登录的Cookie:
driver.add_cookie({'name': 'SESSIONDATA', 'value': 'your_cookie_value'})

八、总结与展望

本文通过B站排行榜爬取实战,系统讲解了:

  • Selenium环境搭建与高级配置
  • 显式等待策略的深度应用
  • 动态页面数据的完整解析流程
  • 反爬机制的应对方案
  • 完整爬虫系统的架构设计

进阶方向

  1. 定时爬取:结合APScheduler实现每日排行追踪
  2. 数据可视化:使用PyEcharts生成趋势图表
  3. 增量更新:通过数据库实现数据去重
  4. 分布式扩展:使用Scrapy+Redis构建集群

注意事项

  • 遵守robots.txt协议
  • 控制请求频率(建议QPS<1)
  • 尊重网站版权声明
  • 定期更新选择器(页面改版时)

Python 爬虫实战:Selenium 爬取 B 站排行榜数据(动态加载页面的显式等待策略)_第1张图片

你可能感兴趣的:(python爬虫实战,python,爬虫,selenium)