Selenium+Python 爬虫:动态加载头条问答爬取

简介: Selenium+Python 爬虫:动态加载头条问答爬取

在互联网数据采集领域,静态网页爬取早已是基础操作,但随着前端技术的迭代,大量网站采用 JavaScript 动态渲染页面,传统基于 requests + 正则、BeautifulSoup 的静态爬虫已无法满足需求。今日头条旗下的头条问答(现整合入头条搜索 / 头条内容生态)便是典型的动态加载页面 —— 其问答列表、详情内容、评论数据均通过 AJAX 异步加载,直接请求网页源码无法获取完整数据。

Selenium 作为主流的 Web 自动化测试工具,能够模拟真实浏览器操作,完美解决动态页面渲染难题。本文将以Python+Selenium为技术核心,从零到一实现头条问答的定向爬取,涵盖环境配置、浏览器驱动、动态页面解析、数据存储、反爬规避等全流程,不仅能获取问答标题、作者、回答内容,还能实现分页自动加载与数据持久化存储,为数据分析、内容聚合提供技术支撑。

一、技术背景与核心原理

1.1 动态页面爬取痛点

传统爬虫通过 HTTP 请求获取网页 HTML 源码,而动态页面的核心数据由 JavaScript 在浏览器端渲染生成。以头条问答为例:

  • 页面初始加载仅返回骨架 HTML,无问答正文、列表数据;
  • 滚动页面、点击分页时,前端通过 AJAX 请求后台接口,动态插入数据;
  • 部分接口携带加密参数、Token 校验,直接抓包接口难度较高。

1.2 Selenium 核心优势

Selenium 是一款跨平台、跨浏览器的自动化工具,支持 Chrome、Firefox、Edge 等主流浏览器,直接驱动浏览器渲染页面,完全模拟人类操作(点击、滚动、输入、等待),能获取浏览器渲染后的完整 DOM 结构,彻底解决动态数据加载问题。

结合 Python 的简洁语法,Selenium+Python 成为动态爬虫的最优解之一,无需分析复杂接口加密,降低爬取门槛,适配 90% 以上的动态网站。

1.3 爬取目标明确

本文爬取目标:头条问答指定关键词下的问答列表 + 详情数据,包含字段:

  1. 问答标题
  2. 问题发布者
  3. 问题发布时间
  4. 优质回答内容
  5. 回答者昵称
  6. 回答点赞数、评论数
  7. 问答链接

二、环境配置:Python+Selenium 基础搭建

2.1 开发环境准备

  • Python 版本:3.8 及以上(推荐 3.9/3.10)
  • 操作系统:Windows/Mac/Linux 均可
  • 核心依赖库:
  • selenium:Web 自动化核心库
  • lxml:XPath 解析页面元素
  • pandas:数据格式化存储
  • time/random:反爬延时控制


2.2 浏览器驱动配置(关键步骤)

Selenium 需要浏览器驱动来控制浏览器,最常用 Chrome 浏览器,需下载对应版本的 ChromeDriver:

  1. 查看 Chrome 版本:打开 Chrome → 右上角三个点 → 设置 → 关于 Chrome,记录版本号(如 124.0.6367.60);
  2. 下载 ChromeDriver:访问ChromeDriver 官方镜像,下载对应版本驱动;
  3. 驱动配置:
  • Windows:将chromedriver.exe放到 Python 安装根目录(与 python.exe 同级);
  • Mac/Linux:将驱动放到/usr/local/bin/目录,或代码中指定路径。

进阶:Selenium 4.6 + 版本支持自动驱动管理,无需手动下载,代码中会自动匹配驱动,新手推荐使用该特性。

三、核心思路:头条问答爬取流程设计

本次爬取采用模拟浏览器操作 + 动态等待 + XPath 解析的方案,完整流程:

  1. 初始化 Selenium 浏览器对象,配置反爬参数;
  2. 访问头条问答搜索页面,输入目标关键词;
  3. 模拟页面滚动,加载动态问答列表;
  4. 遍历问答列表,获取详情链接;
  5. 进入详情页,等待动态内容加载完成;
  6. 解析页面 DOM,提取目标数据;
  7. 自动分页爬取,循环获取多页数据;
  8. 数据清洗、去重,保存为 Excel/CSV 文件。

四、代码实现:从零编写动态爬虫

4.1 配置浏览器启动参数(反爬核心)

头条问答具备基础反爬机制,直接使用裸浏览器会被识别为爬虫,需配置无头模式、禁用自动化提示、模拟 UA,代理IP等参数:

python

运行

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
import time
def init_browser():
    # 代理信息(你提供的)
    proxyHost = "www.16yun.cn"
    proxyPort = "5445"
    proxyUser = "16QMSOML"
    proxyPass = "280651"
    # 创建Chrome配置对象
    chrome_options = Options()
    
    # 基础配置:禁用自动化控制提示(关键反爬)
    chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
    chrome_options.add_experimental_option('useAutomationExtension', False)
    
    # 模拟真实浏览器UA
    chrome_options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36')
    
    # 禁用图片加载(提升爬取速度,可选)
    # chrome_options.add_argument('blink-settings=imagesEnabled=false')
    
    # 无头模式(后台运行,不显示浏览器,正式爬取开启)
    # chrome_options.add_argument('--headless=new')
    # ===================== 代理配置开始 =====================
    # 方案:Chrome插件注入账号密码代理(最稳定)
    manifest_json = """
    {
        "version": "1.0.0",
        "manifest_version": 2,
        "name": "Chrome Proxy",
        "permissions": [
            "proxy",
            "tabs",
            "unlimitedStorage",
            "storage",
            "<all_urls>",
            "webRequest",
            "webRequestBlocking"
        ],
        "background": {
            "scripts": ["background.js"]
        },
        "minimum_chrome_version":"22.0.0"
    }
    """
    background_js = """
    var config = {
            mode: "fixed_servers",
            rules: {
              singleProxy: {
                scheme: "http",
                host: "%s",
                port: %s
              },
              bypassList: ["localhost"]
            }
          };
    chrome.proxy.settings.set({value: config, scope: "regular"}, function() {});
    function callbackFn(details) {
        return {
            authCredentials: {
                username: "%s",
                password: "%s"
            }
        };
    }
    chrome.webRequest.onAuthRequired.addListener(
                callbackFn,
                {urls: ["<all_urls>"]},
                ['blocking']
    );
    """ % (proxyHost, proxyPort, proxyUser, proxyPass)
    # 载入代理插件
    import zipfile
    import os
    plugin_file = "proxy_auth_plugin.zip"
    with zipfile.ZipFile(plugin_file, 'w') as zp:
        zp.writestr("manifest.json", manifest_json)
        zp.writestr("background.js", background_js)
    chrome_options.add_extension(plugin_file)
    # ===================== 代理配置结束 =====================
    # 初始化浏览器对象
    driver = webdriver.Chrome(options=chrome_options)
    
    # 修改浏览器特征,绕过Selenium检测
    driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
        'source': '''
            Object.defineProperty(navigator, 'webdriver', {
                get: () => undefined
            })
        '''
    })
    
    # 设置浏览器窗口大小
    driver.maximize_window()
    # 设置全局等待时间
    driver.implicitly_wait(10)
    # 启动后删除临时插件文件
    try:
        os.remove(plugin_file)
    except:
        pass
    return driver

核心作用:通过修改浏览器webdriver属性,隐藏自动化特征,绕过网站的爬虫检测。

4.3 搜索关键词 + 加载动态列表

头条问答的搜索入口为https://m.toutiao.com/search(移动端页面,结构更简洁,适合爬取),代码实现关键词搜索与动态滚动加载:

python

运行

def search_question(driver, keyword):
    # 访问头条搜索页面
    driver.get("https://m.toutiao.com/search")
    # 随机延时,模拟人类操作
    time.sleep(random.uniform(1, 2))
    
    try:
        # 等待搜索框加载完成
        search_box = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.XPATH, '//input[@placeholder="搜索内容"]'))
        )
        # 输入关键词
        search_box.clear()
        search_box.send_keys(keyword)
        time.sleep(random.uniform(0.5, 1))
        
        # 点击搜索按钮
        search_btn = driver.find_element(By.XPATH, '//button[contains(text(),"搜索")]')
        search_btn.click()
        time.sleep(random.uniform(2, 3))
        
        # 切换到【问答】标签(核心:筛选问答内容)
        question_tab = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((By.XPATH, '//div[text()="问答"]'))
        )
        question_tab.click()
        time.sleep(random.uniform(2, 3))
        print(f"✅ 成功搜索关键词:{keyword},并切换到问答标签")
        
        # 模拟页面滚动,加载动态数据(滚动3次,加载更多问答)
        for i in range(3):
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            time.sleep(random.uniform(1.5, 2.5))
        print("✅ 动态问答列表加载完成")
        
    except Exception as e:
        print(f"❌ 搜索失败:{str(e)}")

4.4 解析问答列表,获取详情链接

滚动加载完成后,页面渲染完整问答列表,通过 XPath 提取每条问答的标题、链接等基础信息:

python

运行

def get_question_list(driver):
    question_links = []
    try:
        # 获取所有问答项(XPath路径:通过浏览器F12开发者工具获取)
        question_items = driver.find_elements(By.XPATH, '//div[contains(@class,"search-result-item")]//a[contains(@href,"question")]')
        
        for item in question_items:
            # 提取标题和链接
            title = item.text
            link = item.get_attribute("href")
            if title and link and link not in [q["link"] for q in question_links]:
                question_links.append({
                    "title": title,
                    "link": link
                })
        print(f"✅ 共获取到{len(question_links)}条问答链接")
        return question_links
    
    except Exception as e:
        print(f"❌ 获取问答列表失败:{str(e)}")
        return []

技巧:XPath 路径可通过浏览器「检查元素」-「复制 XPath」快速获取,适配页面结构。

4.5 爬取问答详情(核心数据提取)

进入详情页后,等待动态内容加载,提取问题、回答、作者等核心数据:

python

运行

def parse_question_detail(driver, link):
    data = {}
    try:
        # 访问详情页
        driver.get(link)
        time.sleep(random.uniform(2, 3))
        
        # 1. 提取问题标题
        data["question_title"] = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.XPATH, '//h1[@class="question-title"]'))
        ).text
        
        # 2. 提取问题发布者
        try:
            data["question_author"] = driver.find_element(By.XPATH, '//div[@class="author-name"]').text
        except:
            data["question_author"] = "匿名用户"
        
        # 3. 提取问题发布时间
        try:
            data["publish_time"] = driver.find_element(By.XPATH, '//span[@class="publish-time"]').text
        except:
            data["publish_time"] = "未知时间"
        
        # 4. 提取优质回答内容(头条问答默认展示优质回答)
        answer_contents = driver.find_elements(By.XPATH, '//div[@class="answer-content"]//p')
        data["answer_content"] = "\n".join([p.text for p in answer_contents if p.text.strip()])
        
        # 5. 提取回答者信息
        try:
            data["answer_author"] = driver.find_element(By.XPATH, '//div[@class="answer-author"]').text
        except:
            data["answer_author"] = "匿名用户"
        
        # 6. 提取点赞数、评论数
        try:
            data["like_count"] = driver.find_element(By.XPATH, '//span[@class="like-count"]').text
            data["comment_count"] = driver.find_element(By.XPATH, '//span[@class="comment-count"]').text
        except:
            data["like_count"] = "0"
            data["comment_count"] = "0"
        
        # 7. 存储问答链接
        data["question_link"] = link
        
        print(f"✅ 成功爬取:{data['question_title']}")
        return data
    
    except Exception as e:
        print(f"❌ 爬取详情失败:{link},错误:{str(e)}")
        return None

4.6 数据存储与主函数调用

将爬取的数据保存为 Excel 文件,方便后续分析,同时编写主函数整合所有流程:

python

运行

def save_data(data_list, keyword):
    # 转换为DataFrame格式
    df = pd.DataFrame(data_list)
    # 去重
    df = df.drop_duplicates(subset=["question_title"])
    # 保存为Excel文件
    filename = f"头条问答_{keyword}_数据.xlsx"
    df.to_excel(filename, index=False, encoding="utf-8")
    print(f"\n🎉 数据保存成功!共{len(df)}条数据,文件名为:{filename}")
def main():
    # 1. 初始化浏览器
    driver = init_browser()
    # 2. 设置爬取关键词(可自定义修改)
    keyword = "Python学习方法"
    # 3. 搜索关键词并加载问答列表
    search_question(driver, keyword)
    # 4. 获取问答链接
    question_links = get_question_list(driver)
    if not question_links:
        driver.quit()
        return
    
    # 5. 循环爬取详情数据
    all_data = []
    for index, q in enumerate(question_links, 1):
        print(f"\n正在爬取第{index}/{len(question_links)}条:{q['title']}")
        detail_data = parse_question_detail(driver, q["link"])
        if detail_data:
            all_data.append(detail_data)
        # 每爬取1条随机延时,避免请求过快
        time.sleep(random.uniform(1, 2))
    
    # 6. 保存数据
    save_data(all_data, keyword)
    # 7. 关闭浏览器
    driver.quit()
    print("\n🚀 头条问答爬取任务全部完成!")
if __name__ == "__main__":
    main()

五、反爬规避:降低被封禁风险

头条问答对爬虫有基础限制,若爬取过快会出现验证码、IP 封禁,以下是实用反爬策略:

  1. 随机延时:在点击、滚动、页面切换时添加random.uniform(1,3)延时,模拟人类操作频率;
  2. 禁用无头模式调试:调试时显示浏览器窗口,确认操作正常,正式爬取再开启无头模式;
  3. 控制爬取数量:单次爬取不超过 50 条,分时段爬取,避免高频请求;
  4. IP 代理池(进阶):大规模爬取时,配置代理 IP,避免单 IP 封禁;
  5. 不爬取敏感数据:遵守《网络安全法》,仅爬取公开数据,不获取用户隐私信息。

六、问题排查与优化方案

6.1 常见问题

  1. 元素找不到:页面未加载完成,增加WebDriverWait显式等待,替代固定time.sleep
  2. 被检测为爬虫:检查浏览器配置,确保禁用了webdriver特征;
  3. 动态内容未加载:增加页面滚动次数,或等待目标元素出现再解析。

6.2 性能优化

  1. 禁用图片 / JS 加载:减少浏览器资源消耗,提升爬取速度;
  2. 批量爬取:将多个关键词存入列表,循环爬取;
  3. 异常重试:为失败的链接添加重试机制,提升数据完整性。

七、技术总结与合规说明

7.1 技术核心总结

本文通过Selenium+Python完美解决了头条问答动态页面的爬取难题,核心价值:

  1. 无需分析复杂 AJAX 接口与加密参数,降低动态爬虫开发门槛;
  2. 模拟真实浏览器操作,适配 90% 以上动态渲染网站;
  3. 完整实现数据采集、清洗、存储全流程,可直接复用至其他动态网站。

Selenium 不仅适用于爬虫,还可用于自动化测试、表单自动填写、定时任务等场景,是 Python 开发者必备技能。

相关文章
|
6天前
|
人工智能 数据可视化 安全
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
本文详解如何用阿里云Lighthouse一键部署OpenClaw,结合飞书CLI等工具,让AI真正“动手”——自动群发、生成科研日报、整理知识库。核心理念:未来软件应为AI而生,CLI即AI的“手脚”,实现高效、安全、可控的智能自动化。
23064 14
王炸组合!阿里云 OpenClaw X 飞书 CLI,开启 Agent 基建狂潮!(附带免费使用6个月服务器)
|
18天前
|
人工智能 JSON 机器人
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
本文带你零成本玩转OpenClaw:学生认证白嫖6个月阿里云服务器,手把手配置飞书机器人、接入免费/高性价比AI模型(NVIDIA/通义),并打造微信公众号“全自动分身”——实时抓热榜、AI选题拆解、一键发布草稿,5分钟完成热点→文章全流程!
34363 141
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
|
7天前
|
人工智能 JSON 监控
Claude Code 源码泄露:一份价值亿元的 AI 工程公开课
我以为顶级 AI 产品的护城河是模型。读完这 51.2 万行泄露的源码,我发现自己错了。
4663 20
|
6天前
|
人工智能 API 开发者
阿里云百炼 Coding Plan 售罄、Lite 停售、Pro 抢不到?最新解决方案
阿里云百炼Coding Plan Lite已停售,Pro版每日9:30限量抢购难度大。本文解析原因,并提供两大方案:①掌握技巧抢购Pro版;②直接使用百炼平台按量付费——新用户赠100万Tokens,支持Qwen3.5-Max等满血模型,灵活低成本。
1509 3
阿里云百炼 Coding Plan 售罄、Lite 停售、Pro 抢不到?最新解决方案