Node.js 浏览器引擎 + Python 大脑:Playwright 混合架构爬虫系统深度解析

简介: 当requests拿不到数据、Selenium扛不住并发时,我构建了Node.js+Python的Playwright混合架构:Node高效管理浏览器实例,Python专注调度与解析,通过REST API协同,兼顾性能与灵活性,成功支撑电商SPA页面高并发实时采集。

当 requests 拿不到数据、Selenium 跑不动并发时,这套架构救了我

一个让我失眠的夜晚
去年秋天接了一个电商价格监控的项目,甲方要求实时抓取日本雅虎拍卖的商品数据——当前出价、剩余时间、竞拍人数,延迟不能超过 5 秒。

一开始我想得挺简单:requests + BeautifulSoup,半小时写完,跑起来一看——页面一片空白。

原因不复杂:雅虎拍卖的商品页是典型的 SPA(单页应用),价格和倒计时全部通过 JavaScript 异步加载。requests 拿到的只是一个空壳 HTML,真正的数据根本不在里面。

换 Selenium?我试了。开了 20 个浏览器实例,机器 CPU 直接飙到 96%,内存暴涨,跑不到 20 分钟容器就 OOM 崩了。甲方还在催,我差点想跑路。

后来折腾出来的这套方案,就是今天想跟大家聊的——Node.js 做浏览器引擎,Python 做大脑的 Playwright 混合架构。

为什么要搞混合架构?
直接说结论:纯 Python Playwright 不是不能跑,但在高并发场景下,资源开销是个大问题。

Playwright 本身是多语言支持的——Python、Java、.NET、Node.js 都能用。但有一个细节很多人没注意到:Node.js 版本的 Playwright 不会像 Python 版本那样为每个浏览器窗口生成一个新进程,因此在管理多个标签页时,CPU 和内存开销要小得多。

什么意思呢?简单说——

Python Playwright:每个浏览器上下文会启动独立的子进程,开 20 个实例基本就把机器榨干了
Node.js Playwright:事件驱动模型,同一个进程里可以高效管理多个浏览器实例
所以我的思路很直接:用 Node.js 专门管浏览器(体力活),用 Python 做调度和数据处理(脑力活) 。两者通过 REST API 通信。

架构长什么样?
整体拆成三层:

┌─────────────────────────────────────────────────┐
│ Python 调度层 │
│ 任务队列 → 调用渲染接口 → 解析数据 → 落库 │
└────────────────────┬────────────────────────────┘
│ REST API / JSON
┌────────────────────▼────────────────────────────┐
│ Node.js 浏览器服务层 │
│ 浏览器池管理 → 页面渲染 → 执行 JS 交互 │
└────────────────────┬────────────────────────────┘
│ CDP 协议
┌────────────────────▼────────────────────────────┐
│ 无头 Chromium 实例 │
└─────────────────────────────────────────────────┘
这个分层的好处很明显:浏览器服务可以独立扩容,Python 调度器完全不用关心浏览器怎么跑。想加并发?多部署几个 Node.js 服务实例就行。

Node.js 浏览器服务:核心代码
浏览器服务我用 Express 搭了一个 HTTP 接口,核心是维护一个浏览器实例池:

const express = require('express');
const { chromium } = require('playwright');
const app = express();
app.use(express.json());

const PORT = 3000;
const browserPool = {};
const MAX_BROWSERS = 5;

asyncfunction getBrowserInstance(id) {
if (!browserPool[id]) {
browserPool[id] = await chromium.launch({
headless: true,
args: ['--no-sandbox']
});
}
return browserPool[id];
}

app.post('/render', async (req, res) => {
const { url, js_actions, session_id = 'default' } = req.body;
try {
const browser = await getBrowserInstance(session_id);
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
});
const page = await context.newPage();

    await page.goto(url, { waitUntil: 'networkidle', timeout: 60000 });

    // 执行自定义 JS 操作(点击、滚动等)
    for (const action of js_actions || []) {
        if (action.type === 'click') {
            await page.click(action.selector);
            await page.waitForTimeout(2000);
        } elseif (action.type === 'scroll') {
            await page.evaluate(() =>window.scrollTo(0, document.body.scrollHeight));
            await page.waitForTimeout(1000);
        }
    }

    const content = await page.content();
    const screenshot = await page.screenshot({ fullPage: true });

    await context.close();
    res.json({
        success: true,
        html: content,
        screenshot: screenshot.toString('base64')
    });
} catch (error) {
    res.status(500).json({ success: false, error: error.message });
}

});

app.listen(PORT, () => console.log(Browser service running on port ${PORT}));
踩坑提醒:浏览器实例池的大小要根据机器配置来调。我实测 8 核 16G 的机器,每个浏览器进程大概吃 150~250MB 内存,MAX_BROWSERS 设到 5 是比较稳妥的。想跑更多?上容器拆分。

Python 调度器:大脑怎么指挥手脚
Python 这边就简单多了——发 HTTP 请求让 Node.js 去干活,拿到 HTML 后用 BeautifulSoup 解析:

import requests
from bs4 import BeautifulSoup
import time

BROWSER_API = "http://localhost:3000/render"

def fetch_rendered_page(url, actions=None):
payload = {
"url": url,
"js_actions": actions or [],
"session_id": "default"
}
resp = requests.post(BROWSER_API, json=payload, timeout=120)
if resp.status_code == 200:
data = resp.json()
if data.get('success'):
return data.get('html')
returnNone

def parse_product_data(html):
soup = BeautifulSoup(html, 'html.parser')

# 这里写具体的解析逻辑
price = soup.select_one('.product-price')
title = soup.select_one('.product-title')
return {
    'price': price.text.strip() if price elseNone,
    'title': title.text.strip() if title elseNone
}

调度循环

urls = ['https://example.com/item/1', 'https://example.com/item/2']
for url in urls:
html = fetch_rendered_page(url, [
{'type': 'scroll'}, # 先滚动加载更多
{'type': 'click', 'selector': '.load-more'} # 再点加载按钮
])
if html:
data = parse_product_data(html)

    # 存数据库...
time.sleep(2)  # 礼貌爬虫,控制频率

关键点:js_actions 这个设计让 Python 可以灵活控制浏览器的行为——滚动、点击、输入,全都能远程指挥。这样就算页面逻辑再复杂,Python 调度层也不需要改动,只需调整 actions 参数就行。

反爬怎么办?三层防御
这套架构跑通之后,第二个问题来了:雅虎的反爬不是吃素的。

我踩过的坑包括:IP 封禁、自动化特征检测、请求频率限制。解决方案分三层:

第一层:指纹伪装
普通的 Playwright 跑的是 Chromium 标准构建版本,反爬系统早就把特征研究透了——哪些 JavaScript API 行为和真实 Chrome 不一样、Canvas 渲染有什么细微差异、WebGL 特征码对不上,全都有档案。

最基本的伪装:

browser = await p.chromium.launch(
headless=True,
args=['--disable-blink-features=AutomationControlled'] # 关掉自动化标记
)
context = await browser.new_context(
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36',
viewport={'width': 1920, 'height': 1080}
)
更狠的可以用 playwright-stealth 这类库来抹掉自动化痕迹。但注意,老版的 playwright-stealth 已经停更了,现在推荐用 pw-stealth-enhanced。

第二层:代理轮换
高并发请求下,固定出口 IP 很快就会被封。我的做法是给每个浏览器会话分配独立代理:

proxies = get_proxy_from_pool() # 从代理池取一个
context = await browser.new_context(
proxy={
'server': f'http://{proxies["host"]}:{proxies["port"]}',
'username': proxies['user'],
'password': proxies['pass']
}
)
第三层:行为模拟
很多反爬系统会分析鼠标轨迹、点击节奏这些行为特征。纯粹的无头浏览器很容易被识破。解决方案是引入 humanization-playwright 这类库,模拟贝塞尔曲线的鼠标移动、随机延迟等人类行为。

这三层叠起来,我的采集系统现在已经稳定跑了 3 个月,日均采集超 10 万条数据。

异步改造:从串行到并发
一开始我用的是同步模式,一个一个爬,效率惨不忍睹。后来改成了 asyncio + Playwright 异步模式:

import asyncio
from playwright.async_api import async_playwright

asyncdef fetch_page(url, proxy=None):
asyncwith async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await browser.new_context(proxy=proxy)
page = await context.new_page()
await page.goto(url, wait_until='networkidle')
content = await page.content()
await browser.close()
return content

asyncdef main():
urls = ['url1', 'url2', 'url3', ...]
semaphore = asyncio.Semaphore(10) # 限制并发数

asyncdef fetch_with_limit(url):
    asyncwith semaphore:
        returnawait fetch_page(url)

results = await asyncio.gather(*[fetch_with_limit(url) for url in urls])
# 处理结果...

asyncio.run(main())
注意:并发数不是越大越好。我实测 10 个并发是比较舒服的数字,再高就容易触发反爬或者把机器搞崩。用 asyncio.Semaphore 做限流是基本操作。

这套架构的适用场景
折腾完这套东西之后,我总结了一下它最适合的场景:

SPA 单页应用:React/Vue 渲染的页面,requests 完全拿不到数据的那种
需要交互操作的采集:要点击、滚动、登录才能看到完整内容的页面
高并发的动态渲染需求:纯 Python Playwright 扛不住并发的时候
不适合的场景:静态页面、简单的 API 接口采集——杀鸡用牛刀了,requests 一把梭更高效。

最后说几句
这套架构我从立项到稳定运行大概花了三周时间。最大的感悟是:不要在一个技术栈里死磕,合适的事情交给合适的工具。

Node.js 的 Playwright 管浏览器就是比 Python 版本省资源,这是架构层面的优势,不是写代码能弥补的。Python 做调度和数据处理就是方便,生态丰富。两者结合起来,各取所长。

如果你也在被 SPA 爬虫折磨,或者正愁 Python Playwright 跑不动并发,不妨试试这个思路。

相关文章
|
17小时前
|
人工智能 自然语言处理 供应链
TID质量竞争大会分享议题 丨基于业务建模的多智能体测试用例生成方案
TID质量竞争大会分享:联想诺谛测试总监详解《基于业务建模的多智能体测试用例生成方案》。融合MBT与多智能体技术,构建从PRD到可评审用例的自动化链路,破解AI Coding时代“实现快、验证慢”质量瓶颈。
|
2天前
|
人工智能 关系型数据库 MySQL
Agent、Skill、MCP 到底是什么关系?零基础小白也能看懂的三层拆解
本文用“数字打工人”比喻,通俗解析AI提效核心概念:Agent是能自主规划执行的智能体,Skill是其掌握的原子能力(如查天气、发消息),MCP则是统一调用协议——如同USB-C接口,让Skill可即插即用、跨平台共享。零代码基础也能秒懂三者关系。
|
3天前
|
人工智能 监控 中间件
TID质量竞争大会分享议题丨AI 驱动性能测试实践:性能工程正在进入智能化新阶段
性能测试正从单点压测迈向智能工程。面对微服务、云原生等复杂架构,传统依赖专家经验的模式难以为继。盾山科技CEO高楼将分享《AI驱动性能测试实践》,系统阐述如何以AI+Agent重构性能工程:沉淀Performance Skills、构建多智能体协作体系,分阶段实现从辅助提效到受控智能闭环的升级。
|
17小时前
|
人工智能 自然语言处理 数据可视化
TID质量竞争大会分享议题丨从暗箱到透镜:AI Agent 评测与可观测性,正在成为智能研发的关键底座
中兴通讯AI技术教练刘振凯将分享《从暗箱到透镜》,揭秘SpecDrivenDev下AI工程化落地核心:构建“评测—评审—观测—回流”全链路闭环,实现AI研发质量可测、过程可视、问题可溯、系统可优。
|
17小时前
|
人工智能 算法 Java
手工测试岗正在消失:2026秋招,企业到底需要什么样的测试人才?
2026年测试行业正经历深刻变革:手工测试岗断崖式减少(需求降47%),AI与自动化覆盖超80%测试场景。企业亟需会编程、懂自动化、能开发测试工具的“测试开发工程师”,而非仅会执行用例的手工测试员。转型,刻不容缓。
|
17小时前
|
人工智能 自然语言处理 测试技术
TID质量竞争大会分享议题|科大讯飞:端到端大模型效果评测,从人工周级到自动化天级
大模型落地加速,效果评测面临效率低、标准不一、端到端难评估等挑战。科大讯飞陈明将在TID大会分享《端到端大模型效果评测》实践,揭秘如何实现从人工周级到自动化天级的工程突破,覆盖内容创作等真实场景。
|
17小时前
|
人工智能 自然语言处理 数据库
Skill vs Prompt:别再傻傻分不清,一篇文章看懂两者的本质区别
用了半年Skill才懂:Prompt是教AI“怎么做”,每次重写指令;Skill是告诉AI“做什么”,自动调用工具、维护状态、复用流程。前者适合临时任务,后者专攻高频重复工作——从手动40分钟到自动2分钟,本质是从用工具升级为造工具。
|
17小时前
|
测试技术 决策智能
Agent 是怎么真正跑任务的?4 张图讲清楚
Agent不止会聊天生成,更核心是目标驱动的自主任务闭环:规划→执行→校验→修复→交付。图解单/多Agent差异、协作机制与自愈能力,揭示其在测试开发中从用例生成到缺陷定位的落地潜力。
|
18小时前
|
人工智能 自然语言处理 安全
生成式 AI 提升网络攻击效能的技术机理与分层防御研究
ESET报告指出,生成式AI已深度融入黑产全流程,催生AI钓鱼、自适应恶意软件、深度伪造语音、自动化渗透四大攻击形态。本文系统拆解其技术链路,提供语义检测与网页识别两段可复现代码,并构建网络边界、云邮件、终端EDR、人员运营四层闭环防御体系,助力企业应对AI时代“攻强守弱”新挑战。(239字)
38 0
|
12小时前
|
人工智能 弹性计算 API
安装Hermes Agent并接入阿里云百炼,按量计费、Coding Plan或Token Plan三种方法任选
Hermes Agent 是一款终端AI编程工具,支持按量计费、Coding Plan 或 Token Plan 团队版接入阿里云百炼。一键安装、三步配置(设 provider=custom、base_url、API Key),即可调用通义千问等大模型,赋能本地开发与自动化任务。Hermes阿里云官方部署入口:https://t.aliyun.com/U/EfvSK0