requests+BeautifulSoup失效是因为动态网页的初始HTML无真实数据,仅含空容器,数据由JS通过AJAX加载;需用Selenium模拟浏览器执行JS,或逆向分析XHR接口直取JSON,Playwright则提供更现代稳定的替代方案。

直接用 requests 拿不到动态加载的数据,因为页面初始 HTML 里没有那些内容——它们是 JavaScript 运行后才插入的。必须模拟浏览器行为,或绕过前端、直取数据接口。
为什么 requests + BeautifulSoup 失效?
这类网页(比如用 Vue/React 渲染的列表、滚动加载的评论、登录后才显示的内容)在服务器返回的原始 HTML 中,目标数据往往只占一个空容器,例如 <div id="list"></div>。JS 脚本随后发起 AJAX 请求,把 JSON 数据拉回来再渲染。所以你用 requests.get() 拿到的只是“壳”,不是“肉”。
常见错误现象:
- 用
BeautifulSoup查找元素始终返回None - 爬下来全是占位符、loading 动画或骨架屏 HTML
- 手动右键“查看网页源代码”和浏览器开发者工具中看到的 DOM 不一致
用 selenium 启动真实浏览器最稳妥
适合需要完整交互场景:登录、点击、下拉滚动、等待元素出现。它不解析 JS,而是让 Chrome/Firefox 真正执行它。
立即学习“Python免费学习笔记(深入)”;
实操建议:
- 优先用
webdriver.Chrome()配合chromedriver,别用无头模式起步(先看到浏览器动作,方便调试) - 等元素出现别用
time.sleep(),改用WebDriverWait+expected_conditions,例如presence_of_element_located((By.CSS_SELECTOR, ".item")) - 滚动到底部触发懒加载时,执行
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);"),再等新内容出现 - 提取数据前,确认元素已渲染完成——可检查
element.text是否非空,或用get_attribute("innerHTML")看原始结构
示例片段:
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 10)
items = wait.until(EC.presence_of_all_elements_located((By.CLASS_NAME, "product-item")))
for item in items:
print(item.find_element(By.TAG_NAME, "h3").text)
更高效的方式:逆向分析 XHR/Fetch 请求
绝大多数动态内容背后都是一个 API 接口,返回 JSON。打开浏览器开发者工具 → Network → XHR 或 Fetch/XHR 标签页,刷新页面、操作交互(如翻页、筛选),观察哪些请求返回了你要的数据。
关键点:
- 注意请求头:
Referer、User-Agent、Cookie可能被校验;登录态常靠sessionid或token维持 - 参数可能加密或带时间戳(如
sign=xxx、ts=1712345678),这时不能直接复用 URL,得还原生成逻辑 - 部分接口返回压缩数据(
Content-Encoding: gzip),requests默认能解,但要注意响应体是否需.json()解析失败(可能是字符串嵌套 JSON) - 频率限制严格:有些接口会检测请求间隔、IP、User-Agent 频次,加
time.sleep(1)很必要
用 playwright 替代 selenium 的理由
如果你需要同时支持多浏览器、更好处理单页应用(SPA)、或自动化更稳定,playwright 是更现代的选择。它原生支持等待网络空闲、自动等待元素、截取渲染后快照,API 更简洁。
典型差异:
-
page.wait_for_selector(".result-list li", state="visible")比 Selenium 的WebDriverWait更直观 -
page.route()可拦截并修改请求(比如注入 token、伪造 referer) - 默认启用无头模式,但调试图模式只需加
headless=False和slow_mo=500 - 对 Shadow DOM、iframe 内容支持更自然,不用反复
switch_to.frame
性能上,playwright 启动更快、内存占用更低,尤其适合批量任务;但学习成本略高,老项目迁移到它要重写等待逻辑。
真正难的从来不是“怎么跑起来”,而是判断该用哪种方式——是花 2 小时逆向出 API 参数,还是花 10 分钟写个 selenium 脚本跑通;是忽略反爬直接被封 IP,还是加随机延时+代理池撑过 1000 次请求。选错路径,后面全是补丁。


















