尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

爬虫提速:接口直连优先,用 Playwright 侦察动态页面

发布时间:2026/9/29 5:31:24

资讯中心
01
ARTICLE

爬虫提速:接口直连优先,用 Playwright 侦察动态页面

爬虫提速:接口直连优先,用 Playwright 侦察动态页面
1. 先别急着渲染页面接口直连为什么更香很多刚接触动态页面爬虫的朋友第一反应就是掏出 Playwright 把整个页面渲染出来然后慢慢等 DOM 出现再定位元素拿数据。这个思路不能说错但当你需要抓取的数据量一大、页面一多很快就会发现直接渲染是又慢又脆的路子。我做了这么几年爬虫碰见动态页面时的第一直觉永远是——先打开 Network 面板看一眼数据是不是从某个接口回来的。如果是赶紧回到 Requests 去请求这个接口比 Playwright 稳定太多速度还快一个数量级。这一节咱们就把“优先 API”这套思路彻底拆开来讲从怎么找接口、怎么把接口搬运成 Requests 代码到 401、429 这类翻车现场怎么处理一条龙给你盘明白。标题里虽然是 Playwright 的章节但 Playwright 在这里真正的角色是“侦察兵”帮你把接口摸清楚然后你就该把 Requests 这杆枪掏出来干活了。1.1 直接渲染的隐性成本先说一个每位爬虫工程师都绕不开的痛点Playwright 渲染一个页面到底要消耗多少资源一个典型的动态网页浏览器要先把 HTML 下载下来然后解析里面的 CSS、执行 JavaScript、发起一堆 XHR 请求、等待图片字体加载、等 React/Vue 框架渲染完成可能还要处理懒加载和 IntersectionObserver 触发的滚动加载。这一套流程下来单页耗时动不动就是 5 到 10 秒。而接口直连呢一个 JSON 请求几十到几百毫秒就完事了差距不是一星半点。更麻烦的是稳定性。页面渲染是“黑盒”你永远不知道这次渲染会不会因为某个静态资源超时而失败会不会因为网络抖动导致某个 JS 没加载出来从而导致页面结构错乱。用了 Playwright 的朋友多少都经历过这种折磨wait_for_selector超时、元素不可见、iframe 嵌套找半天、懒加载内容死活不出来。这些问题的根源是——你在跟“浏览器环境”这个不可控的东西打交道而接口直连只需要跟“一个返回 JSON 的 URL”打交道。还有一点容易被忽略渲染页面时你的爬虫特征也暴露得更多。浏览器的自动化痕迹、WebDriver 检测、Canvas 指纹、行为轨迹这些东西在接口直连的方案下统统不存在。你只是发了一个很普通的 HTTP 请求目标服务器看到的和普通用户刷新页面时发起的请求几乎一样这反而是最不容易被盯上的姿势。1.2 API 优先方案省在哪所谓 API 优先就是一句话动态页面里的数据大部分都是通过接口从后端拿的。页面渲染只是把这些数据“画”到屏幕上。你既然要数据为什么不直接去源头取我打个比方。你去餐厅吃饭想看菜是怎么做的直接去后厨找厨师问绝对比把整本菜单背下来再回家自己猜来得快。页面上的 DOM 是“菜单”XHR 接口才是“后厨”。直接把接口抓回来省掉了解析 HTML 的体力活拿到的是结构清晰、字段明确的 JSON 数据处理起来不知道有多顺手。接口直连还有一个隐藏优势可重试、可并发。Requests 挂了重试三次只需要 300 毫秒Playwright 页面渲染到一半崩了重来又是 10 秒。做爬虫最怕的数据抓了一半程序崩了这种事故里接口直连续跑的稳定性简直是人品保证。那什么时候该用 API 优先只要看到页面的 XHR/Fetch 请求返回的是 JSON 格式的数据并且这个接口返回的数据就是你需要的字段那就可以直接用 Requests。什么时候不能硬上接口路径带了加密的 sign、token、时间戳签名或者数据是通过 WebSocket 持续推送的那就别硬怼了老老实实走渲染路线后文我会专门讲这事的兜底方案。2. 用 Playwright 当侦察兵三步锁定数据接口既然要先找接口那 Playwright 到底怎么用其实一点也不复杂核心就一句话监听网络请求把页面在运行过程中发出的所有 XHR/Fetch 请求都打印出来然后从里面挑出那个返回了你要的数据的接口。我带你们走一遍完整的侦察流程。2.1 监听网络请求的脚手架代码在 Playwright 里监听网络请求有两种姿势一种是听response一种是听request。我建议听response因为你可以直接拿到响应状态码和响应体判断这个接口是不是真的返回了数据。下面这段代码是日常最常用的脚手架import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: browser await p.chromium.launch(headlessFalse) # 用有头模式方便观察 page await browser.new_page() def on_response(response): # 只关注 XHR 和 Fetch 请求过滤掉图片、CSS、JS 文件 if response.request.resource_type in (xhr, fetch): url response.url status response.status content_type response.headers.get(content-type, ) print(f[{status}] {content_type} | {url}) page.on(response, on_response) await page.goto(https://example.com/your-target-page, wait_untilnetworkidle) await asyncio.sleep(5) # 多等几秒让懒加载也触发 await browser.close() asyncio.run(main())resource_type是关键。页面加载时会发出一大堆请求图片、字体、CSS、JS 都是资源只有xhr和fetch才可能是数据接口。跑完这段代码后你会看到控制台疯狂输出页面发出的所有接口请求这就是你的“接口清单”。2.2 怎么从几十个请求里挑出真正的数据接口一个稍微复杂点的页面清单里可能会有几十个 XHR。怎么判断哪个是你想要的我有三条经验第一看返回内容类型。优先锁定content-type是application/json的请求。如果一个请求返回的是 JSON那大概率就是数据接口。第二步直接在on_response里把响应体打出来看一眼def on_response(response): if response.request.resource_type in (xhr, fetch): try: data response.json() if isinstance(data, (dict, list)): print(fURL: {response.url}) print(fBODY: {str(data)[:200]}) except Exception: pass第二看 URL 特征。接口地址里通常带着api、data、list、get、search这类词汇很容易和那些埋点统计接口区分开。埋点接口一般长这样/collect?eventclick数据接口一般长这样/api/v1/products?page1。第三看触发时机。你要抓的那个数据一定是在页面某个动作之后才出现的。比如你点了一个按钮、滚动了一下页面、切换了一个 Tab那个新发起的 XHR 大概率就是对应的数据接口。这招比你挨个 URL 去猜要高效一百倍。2.3 从浏览器界面反向定位接口如果嫌写监听代码麻烦还有更直观的办法直接用 Playwright 启动有头浏览器打开目标页面后按 F12 打开开发者工具切到 Network 面板手动操作页面然后看着请求队列里一个个冒出来的 XHR。找到那个返回 JSON 的接口后右键它选择“复制 → 复制为 cURL”。这个操作是很多爬虫老手都离不开的杀手锏。复制出来的 cURL 命令会把整个请求的 URL、请求头、Cookie、请求体全都带出来下一步你要做的事就是把它翻译成 Python 的 Requests 代码。后面讲到搬运的时候我再细说。我经常把这个“手动看面板”和“自动监听”配合着用先用脚本监听找到候选接口再用有头浏览器打开 Network 面板去确认它的请求头和数据格式。两手抓稳得很。3. 把 Network 里的信息搬运成 Requests 代码接口找到了接下来就是机械活把 Network 面板里看到的信息翻译成 Requests 代码。这一步不难但特别考验细心程度。很多人的请求发出去拿不到数据不是姿势不对而是漏了某个请求头或者某个参数。3.1 先搞清楚请求的“四件套”要复用一个接口你最少需要搞清楚四件事URL、请求方法、请求头、请求体。在 Network 面板里点开一个请求General 区域写的是 URL 和请求方法Headers 区域写的是请求头Payload 或 Query String Parameters 区域写的是请求参数。这里我要特别提醒一个新手常踩的坑URL 里的查询参数和 POST 的 Form Data 不是一回事。URL 里?后面的keyvalue是 Query 参数在 Requests 里要用params参数传递POST 提交的键值对是 Form Data在 Requests 里要用data参数传递如果请求体里是一段 JSON 字符串要用json参数传递。这三者搞混了服务器就会一直报参数缺失或者 400 错误。我列个表你们对着搬就行Network 面板里看到的位置含义Requests 里的写法URL 中 ? 后面的内容Query String Parametersparams{page: 1}请求体为键值对Form Datadata{username: test}请求体为一串 JSONRequest Payloadjson{username: test}请求头Headersheaders{User-Agent: ...}3.2 Session、Headers、Cookies 一个都不能少复用一个接口最稳妥的做法是创建一个requests.Session()因为这个 Session 会自动管理 Cookie而且可以在一个地方统一设置 Headers。我写搬运代码的习惯是先把这个页面里所有请求共用的请求头提取出来放到 Session 上import requests s requests.Session() s.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://example.com/your-target-page, Origin: https://example.com, })超时和重试也建议一开始就配好不要裸奔着用。给 Session 装一个 HTTPAdapter设置总重试次数from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry retry Retry(total3, status_forcelist[500, 502, 503, 504]) adapter HTTPAdapter(max_retriesretry) s.mount(https://, adapter) s.mount(http://, adapter)这三件事做完你的请求才算是具备了一个“合格浏览器”的基本素质。3.3 三种常见请求体的写法具体到发请求我给你们展示三种最常见的形态。第一种GET 查询参数resp s.get( https://api.example.com/v1/products, params{page: 1, size: 20}, timeout10, ) print(resp.json())第二种POST Form Data这种情况常见于登录接口、搜索接口resp s.post( https://api.example.com/v1/login, data{username: your_name, password: your_pass}, timeout10, )第三种POST JSON 请求体现在前后端分离的项目里特别多resp s.post( https://api.example.com/v1/query, json{filters: {category: books}, page: 1}, timeout10, )这三种在代码层面区别就是params、data、json三个参数的区别。运行完之后如果服务器返回的是 HTML 而不是 JSON十有八九是你请求头或者参数没搬全回 Network 面板再仔细对一遍。如果返回的是 403 或者跳转到登录页那大概率是 Cookie 没带上。4. 稳定性翻车现场401 和 429 到底怎么解接口直连最让人头痛的就是各种状态码。日常爬虫里403 和 401 是权限问题429 是频率限制500 是服务器问题。这里我重点展开 401 和 429因为这俩在所有状态码里出现频率是真的高很多人第一次跑分分钟就扑在它们面前。4.1 401 鉴权失败登录态和 Token 的传导401 的意思是“你没有权限访问”。很多动态站点并不需要你登录但需要你在请求头里带上一个访问令牌。比如某些数据接口要求Authorization: Bearer eyJhbGci...这个令牌可能是你点击某个按钮时前端临时生成的也可能是在登录之后存储在浏览器的 localStorage 里的。遇到这种情况我的常规操作是回到 Playwright先把登录态跑出来然后从浏览器上下文里把 Token 或者 Cookie 提取出来再传给 Requests。提取 Cookie 的代码特别简单# 在 Playwright 里登录之后 cookies await context.cookies() cookie_dict {c[name]: c[value] for c in cookies}如果是 Bearer Token直接让 Playwright 在页面里执行 JS 去取比如这个 Token 存在 localStorage 里token await page.evaluate(() localStorage.getItem(token))然后回到 Requests 这边在 Session 的 Headers 里加上Authorization。这套“Playwright 负责登录、Requests 负责干活”的组合是我做爬虫以来觉得最舒服的模式。之前见过用纯 Requests 模拟登录流程的费了半天劲还不一定过得了验证码不如让 Playwright 把登录这一步啃下来剩下的批量抓取全交给 Requests 干效率直接翻倍。4.2 429 频率限制重试、退避与随机延迟429 是我见惯了的状态码全称是 Too Many Requests。某个接口短时间内请求次数太多服务器直接给你限流。很多朋友一收到 429 就慌了其实这是最“温和”的反爬策略因为它只是想让你慢一点。处理方案就是三步等一下、再试一次、每次等待时间递增。我一般是这样处理的import time import random def fetch_with_retry(session, method, url, max_retries5, **kwargs): for attempt in range(max_retries): resp session.request(method, url, **kwargs) if resp.status_code 429: retry_after resp.headers.get(Retry-After) wait float(retry_after) if retry_after else (2 ** attempt random.random()) print(f收到 429等待 {wait:.2f}s 后重试) time.sleep(wait) continue return resp raise RuntimeError(f重试 {max_retries} 次仍然失败最后一次状态码: {resp.status_code})这个函数有两个地方值得注意。第一读取响应头里的Retry-After服务器如果贴心的话会直接告诉你等几秒这个提示比你自己瞎猜要准得多。第二没给提示时采用指数退避加随机扰动2 秒、4 秒、8 秒这样递增并且加上一点随机数避免所有重试请求在同一时刻整齐划一地打过去。4.3 别把请求频率搞得像机枪扫射除了上面说的机制问题我还想说一个容易被忽略的心态问题。很多人拿到接口之后第一反应就是写一个循环一口气抓几千页。这种写法等于直接对着服务器扫射不 429 才怪。高质量爬虫的第一原则是“看起来像人”。批量抓取时每个请求之间至少睡个几百毫秒到一两秒网页浏览通常是有停顿的import random for page_num in range(1, 101): data fetch_with_retry(s, GET, https://api.example.com/v1/products, params{page: page_num}) # 处理数据... time.sleep(random.uniform(0.8, 2.0)) # 随机延迟别固定在一个值记住一个铁律宁可抓得慢一点也不要被对方封了 IP 再从头来过。被封了 IP 之后除了换代理几乎没有别的路子那成本比慢速抓取高太多了。你是在做公开数据的合规采集就要确保整个过程不给对方服务器造成压力这也是爬虫工程里一项非常重要的职业素养。5. 接口拿不到数据的兜底路线签名加密与 Playwright 回退接口优先的思路虽然好但不是所有网站都给你留这么一条康庄大道。有些动态页面的数据接口明明就在 Network 面板里躺着但你把参数原封不动地拼到 Requests 里发过去服务器就是不给你数据比如返回一个{code: -1, msg: invalid request}。这时候就要看看是不是参数里带了动态签名。5.1 当 URL 和参数里出现 sign/token/ts常见的情况是接口的参数里带着sign、_signature、timestamp、nonce这类字段。这些字段是前端在发请求前通过固定的算法对参数和时间戳做签名运算生成的。服务器收到后会检验这个签名是否合法如果不对就拒绝返回数据。这类接口用纯 Requests 复制下来是没法直接跑的因为每次请求的签名都不一样而且签名算法往往藏在压缩混淆过的 JS 文件里。面对这种情况我的建议分三个层次第一个层次如果这个站点有官方开放 API优先用官方的。很多数据其实都能通过正规渠道拿到不用死磕反爬签名。第二个层次如果你只是偶尔抓一次最省事的方式就是让 Playwright 渲染页面之后从 DOM 里拿数据或者拦截接口的响应——反正页面本身已经帮你把签名算好了你直接捡现成的。第三个层次人工把 JS 里的签名逻辑逆向出来用 Python 模拟生成。这个工作量最大只适合你准备长期维护这个数据源的情况。5.2 兜底方案Playwright 渲染加数据抽取既然 Playwright 已经在我们手里了那在 API 方案走不通的时候直接把它拉上场作为兜底就行。比如找一个支持懒加载的列表页用 Playwright 滚动到底部触发加载然后通过page.locator去定位页面上的数据元素逐条抽出来。虽然慢但也是能稳定拿到数据的路子。还有一种更省事的方法既然页面渲染后已经把数据挂到了全局变量里比如某些站点把初始数据写进了window.__INITIAL_STATE__那你完全可以不解析 DOM直接执行一波page.evaluate把这段数据拿出来本质上还是在跟数据打交道只不过藏在页面而不是接口里# 在 Playwright 中 data await page.evaluate(() window.__INITIAL_STATE__)这招在不少前端框架项目里都通用速度比逐个定位 DOM 高一个档次。还有的站点将数据放到了document.querySelector(#__NEXT_DATA__).innerText里理论都是一个路子注意灵活应变就行。5.3 两条腿走路的取舍标准我自己的取舍标准是这样的先花 5 分钟全局扫描 Network 面板的 XHR 列表看看有没有明文的 JSON 接口。有就立刻复制成 cURL 测试能用就直接上 Requests接口带了签名或者要过复杂鉴权就开始评估成本如果只是单个页面数据就用 Playwright 渲染一下如果是要大规模采集再去考虑抠 JS、写签名算法否则不值得。关键词是“评估成本”。爬虫本质是工程不是炫技。能用最小成本拿到稳定数据的方案就是好方案。接口优先是一条最优路线但不代表你必须所有项目都吊死在接口上。学会在 API 和渲染之间灵活切换才算是把动态页面的抓取玩明白了。6. 我的接口优先工作流从 Network 到 Requests 的一气呵成写了这么多最后把整个工作流串起来。这不是什么高深的东西只是一套我反复使用、验证过好用的操作步骤你们可以直接照搬。6.1 标准流程七连打开目标页面用 Playwright 有头模式跑一次同时在on_response里监听 XHR/Fetch 请求。如果是手动操作就打开开发者工具看 Network 面板。从请求清单里挑出返回 JSON、URL 看起来像数据接口的那一两个候选。在 Network 面板里点开候选接口看清楚请求方法、请求头、Query String、Payload 的具体结构。右键这个请求选择“复制为 cURL”。这个操作会自动带上全部请求头、Cookie 和请求体。把 cURL 命令翻译成 Requests 代码。你有两种偷懒路子一个是手动搬 URL、请求头、参数另一个是用现成的在线转换工具把 cURL 直接变成 Python 代码搬完再微调。放入一个 Session固定好 User-Agent、Referer、Cookie加上超时和重试先单发一次测试通。确认单次请求没问题再写批量循环。循环里务必带上随机延迟和 429 退避重试。走到第七步这个数据源你就已经稳定拿下了。后面无非是根据抓取结果的字段名去调整参数、翻页逻辑之类的细节。6.2 两个容易被忽视的实操细节一个细节是“先复制 cURL 再转换成代码”这个操作真的很香。很多新手喜欢从 Network 面板里一个个字段手动搬到 Python 里一旦某个请求头太长、加密字段太多搬着搬着就漏了。复制 cURL 属于“原样搬运”能最大限度保证请求头完整性出错概率低很多。前提是你对转换出来的代码稍微懂一点能看出哪些是必须留的、哪些是埋点参数可以删。另一个细节是 Playwright 在整个流程里的定位。我把“接口优先”和“Playwright 侦察”当成一个整体来看而不是非此即彼。接口不明确时Playwright 负责把登录态和 Token 跑出来再把 Cookie 传给 Requests接口明确时Playwright 也负责在页面里触发各种操作让你看到数据请求是在哪个动作之后发出的。Requests 是干重活的主力Playwright 是指路的侦察兵两个配合着用才是这套方案的完全体。我个人现在的习惯是接到动态页面需求后先不要急着写抓取代码先花几分钟把 Network 面板翻一遍。大部分情况下数据接口就是明晃晃地躺在里面找到它然后回到 Requests你的抓取任务就已经完成了八成。别低估“先分析、后编码”这件事的价值磨刀不误砍柴工在爬虫开发里体现得淋漓尽致。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。