利用Playwright Python库实现站点抓取:从基础操作到问题解决
一、环境准备:Playwright初始化
在开始抓取前,需先完成Playwright的安装和环境配置。Playwright支持Python、JavaScript/TypeScript等多种语言,本文以Python为例(应用最广泛,上手门槛低)。
1.1 安装依赖
执行以下命令安装Playwright及浏览器驱动(Playwright会自动下载对应浏览器的驱动,无需手动配置):
# 安装Playwright
pip install playwright
# 下载浏览器驱动(Chromium、Firefox、WebKit)
playwright install
1.2 核心概念说明
在编写代码前,需了解Playwright的3个核心对象,这是后续所有操作的基础:
Playwright实例:全局入口,用于启动和管理浏览器;
Browser(浏览器):可理解为真实的浏览器窗口,支持无头模式(headless=True,不显示界面,适合服务器运行)和有头模式(headless=False,便于调试);
Page(页面):浏览器中的一个标签页,所有页面操作(浏览、点击、注入JS等)都通过该对象完成。
二、核心操作:前端站点抓取的4个关键步骤
前端站点抓取的核心需求的是获取页面的“可见内容”和“结构化数据”,结合Playwright的特性,我们可通过以下4个步骤实现完整抓取流程。
2.1 利用Playwright浏览页面
Playwright的优势之一是“自动等待”——无需手动设置sleep等待页面加载,它会自动等待页面元素渲染完成、网络请求结束,避免因页面未加载完成导致的抓取失败。
基础浏览操作示例(以抓取某前端演示站点为例):
from playwright.sync_api import sync_playwright
# 启动Playwright,使用with语句自动释放资源
with sync_playwright() as p:
# 启动Chromium浏览器,headless=False便于调试
browser = p.chromium.launch(headless=False)
# 新建一个页面标签
page = browser.new_page()
# 浏览目标页面,自动等待页面加载完成
page.goto("https://example.com") # 替换为目标站点URL
# 可选:设置页面视口大小(模拟不同设备)
page.set_viewport_size({"width": 1920, "height": 1080})
# 可选:模拟用户操作(如滚动页面,加载更多内容)
page.mouse.wheel(0, 1000) # 向下滚动1000像素
page.wait_for_timeout(1000) # 可选:手动等待1秒(应对极特殊的异步加载)
# 后续操作...(导出DOM、截图等)
# 关闭浏览器
browser.close()
关键说明:page.goto() 方法会等待页面的load事件完成,对于SPA(单页应用),可使用page.wait_for_load_state("networkidle")等待网络请求完全空闲,确保所有动态内容加载完毕。
2.2 利用JS导出document DOM
抓取页面的核心是获取DOM结构,Playwright支持通过page.evaluate()方法注入JavaScript代码,直接操作页面DOM,导出完整的DOM结构(包括动态渲染的内容)。
导出DOM的两种常用方式(按需选择):
方式1:导出完整DOM字符串
将整个页面的DOM序列化为字符串,可保存为HTML文件,便于后续分析或二次解析:
# 接上面的代码,在page.goto()之后添加
# 注入JS,序列化document.documentElement(整个HTML根节点)
dom_str = page.evaluate("""() => {
const serializer = new XMLSerializer();
return serializer.serializeToString(document.documentElement);
}""")
# 将DOM字符串保存为HTML文件
with open("page_dom.html", "w", encoding="utf-8") as f:
f.write(dom_str)
print("DOM导出完成,已保存至page_dom.html")
原理:通过XMLSerializer将DOM节点转为字符串,相比直接获取document.documentElement.outerHTML,这种方式能更完整地保留DOM结构和属性,尤其适合复杂页面。
方式2:导出指定DOM节点的内容
如果不需要完整DOM,只需抓取页面中特定区域(如列表、详情),可通过JS定位节点并导出内容,减少数据冗余:
# 注入JS,获取指定类名的节点内容(示例:抓取列表项)
list_items = page.evaluate("""() => {
// 定位所有class为"list-item"的节点
const items = document.querySelectorAll(".list-item");
// 提取节点的文本和链接,返回数组
return Array.from(items).map(item => ({
text: item.textContent.trim(),
link: item.querySelector("a")?.href || "" // 提取链接(可选)
}));
}""")
# 打印结果
print("抓取的列表项:", list_items)
关键说明:page.evaluate()中注入的JS代码运行在浏览器环境中,可直接使用原生JS的DOM操作方法(如querySelectorAll、textContent),返回的数据会自动序列化并传递到Python环境中。
2.3 利用屏幕截图抓取快照
在某些场景下(如页面内容无法通过DOM解析、需要留存页面视觉证据),需抓取页面快照(截图)。Playwright的截图功能支持全屏截图、区域截图、元素截图,灵活满足不同需求。
截图操作示例(3种常用场景):
# 1. 全屏截图(包含整个页面,自动滚动)
page.screenshot(
path="full_page_screenshot.png", # 保存路径
full_page=True, # 关键:开启全屏截图
quality=90 # 图片质量(0-100),仅对jpg有效
)
# 2. 区域截图(指定坐标和大小)
page.screenshot(
path="area_screenshot.png",
clip={"x": 100, "y": 100, "width": 800, "height": 600} # x,y为左上角坐标
)
# 3. 元素截图(抓取指定DOM元素)
# 先定位元素,再截图
target_element = page.locator(".header-banner") # 定位头部横幅元素
target_element.screenshot(path="element_screenshot.png")
print("截图完成,已保存对应文件")
补充说明:Playwright的截图API支持多种参数,如设置图片格式(type="jpeg")、忽略背景(omit_background=True)等,可根据需求调整,具体参数可参考官方文档。
2.4 管理页面链接列表,抓取二级页面
很多场景下,我们需要先抓取一级页面的所有链接,再批量访问这些链接(二级页面),实现多页面抓取。通过JS代码可轻松管理页面内的链接列表,过滤无效链接后批量处理。
完整示例(一级页面提取链接 → 批量抓取二级页面):
from playwright.sync_api import sync_playwright
def crawl_secondary_pages(base_url):
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto(base_url)
# 第一步:提取一级页面的所有有效链接(过滤无效链接)
links = page.evaluate("""() => {
const allLinks = Array.from(document.querySelectorAll("a"));
// 过滤条件:1. 非空 2. 以http开头(排除锚点、javascript链接)3. 包含当前域名(避免跳转到外部站点)
return allLinks
.map(link => link.href.trim())
.filter(href => href && href.startsWith("http") && href.includes(window.location.host));
}""")
# 去重(避免重复抓取同一链接)
unique_links = list(set(links))
print(f"一级页面共提取到 {len(unique_links)} 个有效二级页面链接")
# 第二步:批量抓取二级页面的内容(以提取二级页面标题为例)
secondary_data = []
for link in unique_links[:5]: # 先抓取前5个,避免请求过多被限制
try:
# 新建页面打开二级链接(避免影响原页面)
new_page = browser.new_page()
new_page.goto(link, timeout=10000) # 设置超时时间10秒
# 提取二级页面标题和关键内容
page_info = new_page.evaluate("""() => ({
title: document.title,
content: document.querySelector(".content")?.textContent?.trim() || "无内容"
})""")
page_info["url"] = link
secondary_data.append(page_info)
new_page.close() # 关闭当前二级页面,节省资源
print(f"成功抓取二级页面:{link}")
except Exception as e:
print(f"抓取二级页面 {link} 失败:{str(e)}")
# 保存二级页面数据
with open("secondary_pages_data.json", "w", encoding="utf-8") as f:
import json
json.dump(secondary_data, f, ensure_ascii=False, indent=2)
browser.close()
print("二级页面抓取完成,数据已保存至secondary_pages_data.json")
# 调用函数,替换为目标一级页面URL
crawl_secondary_pages("https://example.com")
关键技巧:
链接过滤:通过
href.includes(window.location.host)过滤外部链接,避免抓取无关站点;去重处理:使用
set去重,防止同一链接被多次抓取;资源控制:每次抓取二级页面后关闭页面(
new_page.close()),避免浏览器占用过多内存;超时设置:
goto()设置timeout,防止因页面加载过慢导致程序卡死。
三、常见问题:人机验证的应对方案
在前端抓取过程中,最常见的障碍就是人机验证(如滑块验证、图形验证、reCAPTCHA等)。这类验证的核心目的是区分人类和机器,而Playwright的自动化行为容易被检测到,导致抓取失败。
以下是3种实用的应对方案,从简单到复杂,按需选择:
3.1 方案1:模拟人类行为,降低检测概率
很多站点的人机验证是基于“异常行为”触发的(如页面加载后立即操作、请求频率过快、无鼠标移动等),通过模拟人类操作节奏,可降低触发验证的概率:
# 优化后的浏览代码,模拟人类行为
with sync_playwright() as p:
browser = p.chromium.launch(
headless=False,
# 禁用自动化标识,避免被检测
args=["--disable-blink-features=AutomationControlled"]
)
# 创建浏览器上下文,设置用户代理(模拟真实浏览器)
context = browser.new_context(
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",
viewport={"width": 1920, "height": 1080}
)
page = context.new_page()
# 模拟人类浏览节奏:缓慢加载页面、随机鼠标移动
page.goto("https://example.com", wait_until="networkidle")
# 随机等待1-3秒(模拟人类阅读页面)
import random
page.wait_for_timeout(random.randint(1000, 3000))
# 模拟鼠标移动(从页面左上角移动到中间)
page.mouse.move(100, 100)
page.wait_for_timeout(500)
page.mouse.move(500, 500)
# 后续抓取操作...
context.close()
browser.close()
关键优化点:
禁用自动化标识:
--disable-blink-features=AutomationControlled可隐藏Playwright的自动化痕迹;设置真实用户代理:避免使用Playwright默认的用户代理,模拟普通浏览器;
随机等待和鼠标移动:模拟人类的操作节奏,避免机械性操作。
3.2 方案2:复用浏览器上下文,绕过重复验证
如果站点的人机验证是一次性的(登录后或验证一次后,后续请求不再验证),可通过保存浏览器上下文(如Cookie、LocalStorage),复用登录状态或验证状态,避免重复触发验证:
# 第一步:首次运行,手动完成人机验证,保存上下文状态
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
page.goto("https://example.com")
# 手动完成人机验证(此时页面已通过验证)
input("请手动完成人机验证,完成后按Enter继续...")
# 保存上下文状态(包含Cookie、LocalStorage等)到文件
context.storage_state(path="auth_state.json")
browser.close()
# 第二步:后续抓取,加载已保存的上下文,无需再次验证
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
# 加载保存的上下文状态
context = browser.new_context(storage_state="auth_state.json")
page = context.new_page()
# 直接访问目标页面,已通过验证
page.goto("https://example.com")
# 后续抓取操作...(无需再次验证)
browser.close()
适用场景:站点的验证状态通过Cookie或LocalStorage保存,一次验证后可长期复用(如论坛、电商站点)。
3.3 方案3:借助第三方服务,自动识别验证
如果上述两种方案无效,可借助第三方验证码识别服务(如2Captcha、超级鹰),自动识别滑块、图形等验证,实现全自动抓取。以2Captcha为例,核心思路是:
当页面出现验证时,截图验证区域;
将截图上传到第三方服务,获取验证结果;
通过Playwright模拟操作,输入验证结果,完成验证。
核心代码示例(简化版):
import requests
# 第三方验证码识别API(以2Captcha为例,需注册获取API_KEY)
API_KEY = "你的2Captcha API_KEY"
SITE_KEY = "目标站点的reCAPTCHA Site Key" # 从目标页面源码中获取
def solve_captcha(url):
# 1. 提交验证码任务
submit_url = f"http://2captcha.com/in.php?key={API_KEY}&method=userrecaptcha&googlekey={SITE_KEY}&pageurl={url}"
response = requests.get(submit_url)
if "OK" not in response.text:
raise Exception("提交验证码任务失败")
captcha_id = response.text.split("|")[1]
# 2. 等待识别结果
import time
while True:
result_url = f"http://2captcha.com/res.php?key={API_KEY}&action=get&id={captcha_id}"
result = requests.get(result_url).text
if "OK|" in result:
return result.split("|")[1] # 返回验证结果
time.sleep(2) # 每2秒查询一次
# 在Playwright中调用,完成验证
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://example.com")
# 检测到验证框时,调用识别函数
if page.locator("#captcha-box").is_visible():
captcha_result = solve_captcha(page.url)
# 注入JS,提交验证结果(需根据目标站点的验证逻辑调整)
page.evaluate(f"""() => {{
document.getElementById("captcha-input").value = "{captcha_result}";
document.getElementById("captcha-submit").click();
}}""")
page.wait_for_load_state("networkidle")
# 后续抓取操作...
browser.close()
注意事项:第三方服务需要付费(按验证次数计费),且需严格遵守目标站点的 robots.txt 协议,避免违规抓取。
四、总结与注意事项
4.1 核心总结
Playwright作为前端抓取工具,其核心优势在于“原生支持动态页面”和“灵活的JS注入能力”,通过本文的4个核心操作(页面浏览、DOM导出、截图、二级页面抓取),可覆盖绝大多数前端站点的抓取需求;而针对人机验证问题,可根据站点的验证强度,选择“模拟人类行为”“复用上下文”“第三方识别”三种方案逐步突破。
4.2 重要注意事项
合规性:抓取前需查看目标站点的
robots.txt协议,避免抓取违规内容;不要频繁发送请求,可设置合理的等待时间,防止给服务器造成压力。稳定性:实际抓取中,建议增加异常捕获(
try-except),处理页面加载超时、元素未找到等问题;对于动态加载频繁的页面,可使用page.wait_for_selector()等待目标元素出现。版本兼容:Playwright版本更新较快,部分API可能会有变化,建议使用稳定版本(如1.40.0),并参考官方文档进行开发。
通过本文的实操教程,相信你已经掌握了Playwright前端抓取的核心技巧。在实际应用中,可根据目标站点的特性,灵活调整代码逻辑,实现高效、稳定的抓取。如果遇到复杂的动态页面或验证场景,可进一步探索Playwright的高级API(如网络请求拦截、模拟手机设备等),解锁更多抓取能力。