在互联网数据采集领域,静态网页爬取早已是基础操作,但随着前端技术的迭代,大量网站采用 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 爬取目标明确
本文爬取目标:头条问答指定关键词下的问答列表 + 详情数据,包含字段:
- 问答标题
- 问题发布者
- 问题发布时间
- 优质回答内容
- 回答者昵称
- 回答点赞数、评论数
- 问答链接
二、环境配置:Python+Selenium 基础搭建
2.1 开发环境准备
- Python 版本:3.8 及以上(推荐 3.9/3.10)
- 操作系统:Windows/Mac/Linux 均可
- 核心依赖库:
<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">selenium</font>:Web 自动化核心库<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">lxml</font>:XPath 解析页面元素<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">pandas</font>:数据格式化存储<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">time/random</font>:反爬延时控制
2.2 浏览器驱动配置(关键步骤)
Selenium 需要浏览器驱动来控制浏览器,最常用 Chrome 浏览器,需下载对应版本的 ChromeDriver:
- 查看 Chrome 版本:打开 Chrome → 右上角三个点 → 设置 → 关于 Chrome,记录版本号(如 124.0.6367.60);
- 下载 ChromeDriver:访问ChromeDriver 官方镜像,下载对应版本驱动;
- 驱动配置:
- Windows:将
<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">chromedriver.exe</font>放到 Python 安装根目录(与 python.exe 同级); - Mac/Linux:将驱动放到
<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">/usr/local/bin/</font>目录,或代码中指定路径。
- Windows:将
进阶:Selenium 4.6 + 版本支持自动驱动管理,无需手动下载,代码中会自动匹配驱动,新手推荐使用该特性。
三、核心思路:头条问答爬取流程设计
本次爬取采用模拟浏览器操作 + 动态等待 + XPath 解析的方案,完整流程:
- 初始化 Selenium 浏览器对象,配置反爬参数;
- 访问头条问答搜索页面,输入目标关键词;
- 模拟页面滚动,加载动态问答列表;
- 遍历问答列表,获取详情链接;
- 进入详情页,等待动态内容加载完成;
- 解析页面 DOM,提取目标数据;
- 自动分页爬取,循环获取多页数据;
- 数据清洗、去重,保存为 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
核心作用:通过修改浏览器<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">webdriver</font>属性,隐藏自动化特征,绕过网站的爬虫检测。
4.3 搜索关键词 + 加载动态列表
头条问答的搜索入口为<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">https://m.toutiao.com/search</font>(移动端页面,结构更简洁,适合爬取),代码实现关键词搜索与动态滚动加载:
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 封禁,以下是实用反爬策略:
- 随机延时:在点击、滚动、页面切换时添加
<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">random.uniform(1,3)</font>延时,模拟人类操作频率; - 禁用无头模式调试:调试时显示浏览器窗口,确认操作正常,正式爬取再开启无头模式;
- 控制爬取数量:单次爬取不超过 50 条,分时段爬取,避免高频请求;
- IP 代理池(进阶):大规模爬取时,配置代理 IP,避免单 IP 封禁;
- 不爬取敏感数据:遵守《网络安全法》,仅爬取公开数据,不获取用户隐私信息。
六、问题排查与优化方案
6.1 常见问题
- 元素找不到:页面未加载完成,增加
<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">WebDriverWait</font>显式等待,替代固定<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">time.sleep</font>; - 被检测为爬虫:检查浏览器配置,确保禁用了
<font style="color:rgb(31, 35, 41);background-color:rgba(0, 0, 0, 0);">webdriver</font>特征; - 动态内容未加载:增加页面滚动次数,或等待目标元素出现再解析。
6.2 性能优化
- 禁用图片 / JS 加载:减少浏览器资源消耗,提升爬取速度;
- 批量爬取:将多个关键词存入列表,循环爬取;
- 异常重试:为失败的链接添加重试机制,提升数据完整性。
七、技术总结与合规说明
7.1 技术核心总结
本文通过Selenium+Python完美解决了头条问答动态页面的爬取难题,核心价值:
- 无需分析复杂 AJAX 接口与加密参数,降低动态爬虫开发门槛;
- 模拟真实浏览器操作,适配 90% 以上动态渲染网站;
- 完整实现数据采集、清洗、存储全流程,可直接复用至其他动态网站。
Selenium 不仅适用于爬虫,还可用于自动化测试、表单自动填写、定时任务等场景,是 Python 开发者必备技能。