如何抓取由 JavaScript 渲染的网站
许多现代网站发送的 HTML 文档几乎是空的,内容由浏览器通过 JavaScript 构建。如果你的抓取器获取了页面却找不到任何商品、价格或文章正文,内容其实是存在的,只是来得更晚。本指南说明如何判断你拿到的是哪一类页面,如何在有可行方案时找到比浏览器更快的途径,以及如何用 Python 的 Playwright 抓取渲染后的页面。最后会说明,渲染后的页面出现验证码时该怎么办。
第 1 步:检查内容是否在 HTML 中
在浏览器中用 view-source: 打开页面,或者用普通的 HTTP 请求获取页面,然后在源码中搜索屏幕上能看到的一段文字。常见的结果有三种:
- 文字在里面。 页面是服务端渲染的。普通的 HTTP 客户端加上 HTML 解析器就足够了,而且你应该使用它们,因为它们更快、更简单。
- 文字不在,但有 JSON 数据块。 许多网站把状态嵌入在 script 标签中,例如
__NEXT_DATA__。直接解析这段 JSON 即可。 - 文字不在,也没有数据块。 页面通过后续的请求加载内容。在页面加载时,打开开发者工具的网络面板,筛选 Fetch/XHR,然后查看请求。
第 2 步:如果存在 JSON 接口就调用它
当页面从某个接口获取数据时,直接调用该接口通常是最好的选择。它比渲染后的页面更小、更快、更稳定。复制请求 URL,检查网站要求的请求头,然后从脚本中尝试调用。只有在网站条款允许的情况下才这样做,并且要遵守网站期望的速率限制或认证方式。未公开的接口可能在没有通知的情况下变更,因此请让你的解析器在响应结构改变时明确报错,而不是悄悄出错。
第 3 步:必要时渲染页面
如果内容确实需要浏览器才能呈现,就驱动一个无头浏览器。Playwright 是 Python 中的一个好选择。下面的示例打开一个商品列表,等待内容出现,然后读取每张卡片中的文字:
from playwright.sync_api import sync_playwright
URL = "https://example.com/products" # 你有权抓取的页面
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(URL, wait_until="domcontentloaded", timeout=60_000)
# 等待能证明数据已加载的元素出现。
page.wait_for_selector(".product-card", timeout=30_000)
titles = page.locator(".product-card h2").all_inner_texts()
browser.close()
for title in titles:
print(title)
等待特定选择器,远比使用固定的 sleep 可靠。固定的停顿在网速慢的日子可能不够,在网速快的日子又会白白浪费时间。请选择那个只有在数据加载完成后才会出现的元素。
第 4 步:处理无限滚动和分页
有些页面在你滚动时加载更多条目。滚动到底部,等待条目数量增长,并在数量不再增长时停止。带编号的分页则更简单:找到下一页的链接,点击进入,并在最后一页停止。务必设置最大页数或滚动次数,避免程序的 bug 让抓取器永远运行下去。
def scroll_until_done(page, selector: str, max_rounds: int = 20) -> int:
previous = -1
for _ in range(max_rounds):
count = page.locator(selector).count()
if count == previous:
break
previous = count
page.mouse.wheel(0, 4000)
page.wait_for_timeout(1500) # 留出时间让下一批内容加载
return previous
第 5 步:保持浏览器的开销可控
渲染的开销很大。屏蔽你不需要的图片、字体和媒体资源,并且复用同一个浏览器来访问多个页面,而不是每次都启动一个新的浏览器。完成后关闭页面和上下文,这样在长时间运行时内存不会持续增长。
def block_heavy_assets(route):
if route.request.resource_type in ("image", "font", "media"):
route.abort()
else:
route.continue_()
page.route("**/*", block_heavy_assets)
第 6 步:渲染页面出现验证码时
有时渲染后的页面会在显示任何内容之前先出现验证码。如果这个验证码是 PopularCaptcha(Scarnox 目前上线的验证码类型),你就从页面中读取 sitekey,请求一个 token,然后把 token 放入网站期望的表单字段中。PopularCaptcha Python 指南会逐步讲解这一步。其他控件尚未上线,因此在规划之前,请先查看验证码类型详解。
对浏览器本身也要小心。明显看起来是自动化的无头浏览器,可能会触发更多检查,而不是更少。请阅读无头浏览器检测,了解需要注意的信号,并优先选择速度更慢、加载次数更少的方式。
负责任地抓取
- 在渲染页面或调用其接口之前,检查
robots.txt和网站的条款。 - 保持较低的速率限制,并缓存结果,使每个页面只抓取一次。
- 没有合法依据时,不要抓取个人数据。
- 如果网站限制了访问,就停下来,不要试图突破限制。
下一步
免费试用额度,无需信用卡:注册账户,并在你自己的页面上测试 PopularCaptcha 流程。当前费率请查看定价页面。