安装
安装:pip install playwright
安装完playwright
后还需要安装playwright
自带的浏览器内核才能使用
安装浏览器:playwright install
此命令安装多个浏览器,如需自定义安装浏览器可使用以下代码:
#安装Chrome浏览器
playwright install chromium
#安装Firefox浏览器
playwright install firefox
#安装Webkit浏览器
playwright install webkit
playwright
还可以录制脚本,使用命令开启后,当在浏览器进相关操作时,playwright
会自动给出相应操作的代码:
playwright codegen [options] [url]
-o, --output <file name> :保存生成脚本
--target <language> :生成的脚本语言,可以设置javascript, test, python, python-async和csharp,默认为python。
-b, --browser <browserType> :要使用的浏览器,可以选择cr, chromium, ff, firefox, wk和webkit,默认chromium。
--channel <channel>:chromium版本,比如chrome, chrome-beta, msedge-dev等,
--color-scheme <scheme>:模拟器的颜色主题,可选择light 或者 dark样式。
--device <deviceName> :模拟的设备,比如iPhone 11。
--save-storage <filename> :保存上下文状态,用于保存cookies 和localStorage,可用它来实现重用。例如playwright codegen --save-storage=auth.json
--load-storage <filename> :加载--save-storage 保存的数据,重用认证数据。
--proxy-server <proxy> :指定代理服务器
--timezone <time zone> : 指定时区
--geolocation <coordinates> :指定地理位置坐标
--lang <language> :指定语言/地区,比如中国大陆:zh-CN
--timeout <timeout> :超时时间,定位毫秒,默认10000ms
--user-agent <ua string> :用户代理
--viewport-size <size> :浏览器窗口大小
-h, --help :查看帮助信息
开始使用
from playwright.sync_api import sync_playwright
async with sync_playwright() as p:
# 使用Chrome内核 创建一个新的浏览器页面
browser = await p.chromium.launch()
# 创建一个新的页面,并且返回页面句柄
page = await browser.new_page()
# 进入指定页面,在登录完毕之后输入任意内容继续
await page.goto("http://www.baidu.com")
print(page.title())
browser.close()
p.*.launch()
常见参数说明:
-
executable_path : Chromium的执行路径(如果需要使用本机浏览器的话) args : 递给浏览器实例的附加参数。具体可参考浏览器启动配置项
eg:`args=["--disable-infobars", "--enable-automation=False""--disable-blink-features=AutomationControlled","--start-maximized"]`
-
timeout : 等待浏览器实例启动的最长时间(以毫秒为单位)。 默认为
30000
(30 秒)。 传递0
以禁用超时。 -
env : 指定浏览器可见的环境变量。 默认为
process.env
。 -
headless : 是否以无头模式运行浏览器。
-
devtools : 仅限 Chromium 是否为每个选项卡自动打开开发者工具面板。 如果此选项为
true
,则headless
选项将设置为false
。 -
proxy : 网络代理设置。
-
downloads_path : 如果指定,接受的下载将下载到此目录中。 否则,创建临时目录并当浏览器关闭时被删除。 在任何一种情况下,下载都会在浏览器上下文中被删除创建于已关闭。
-
slow_mo : 将 Playwright 操作减慢指定的毫秒数
-
chromium_sandbox : 启用 Chromium 沙盒。 默认为
false
.
Browser常用方法
# 打开一个新页面
browser.new_page()
#创建无痕浏览器。
browser.contexts()
# 关闭窗口
browser.close()
# 浏览器类型 (chromium, firefox or webkit)
browser.browser_type()
# 表示浏览器是否连接。
browser.is_connected()
# 返回新创建的浏览器会话。
browser.new_browser_cdp_session()
# 终止Playwright实例
playwright.stop()
new_page()和new_context()区别
在playwright中,new_page()和new_context()都是创建新的浏览器页面的方法,但它们有以下区别:
-
new_page()方法会在当前的浏览器上下文中创建一个新的页面,而new_context()方法会创建一个全新的浏览器上下文,其中包含一个或多个页面。
-
new_page()方法返回一个Promise,该Promise在成功时解析为一个Page对象,该对象表示新创建的页面。而new_context()方法返回一个Promise,该Promise在成功时解析为一个BrowserContext对象,该对象表示新创建的浏览器上下文。
-
在同一浏览器实例中,可以使用new_page()方法创建多个页面。但是,在同一浏览器实例中,只能使用new_context()方法创建一个浏览器上下文。
-
new_page()方法创建的页面与当前页面共享相同的浏览器上下文和cookies等信息。而new_context()方法创建的浏览器上下文是完全独立的,不与其他上下文共享任何信息。
因此,如果需要在同一浏览器实例中创建多个页面,则应使用new_page()方法。如果需要在不同的浏览器上下文中创建多个页面,则应使用new_context()方法。
new_page 方法并不是直接通过 browser 调用的,而是通过 context 变量调用的,这个 context 又是由 browser 通过调用 new_context 方法生成的。有读者可能就会问了,这个 context 究竟是做什么的呢?
其实这个 context 变量对应的是一个 BrowserContext 对象,BrowserContext 是一个类似隐身模式的独立上下文环境,其运行资源是单独隔离的,在做一些自动化测试过程中,每个测试用例我们都可以单独创建一个 BrowserContext 对象,这样可以保证每个测试用例之间互不干扰,具体的 API 可以参考 https://playwright.dev/python/docs/api/class-browsercontext。
API
ConsoleMessage
ConsoleMessage
对象通过page.on("console")
事件按页面调度。对于页面中记录的每个控制台消息,Playwright
上下文中都会有相应的事件。
# 监听所有控制台日志
page.on("console", lambda msg: print(msg.text))
# 监听所有控制台事件并处理错误
page.on("console", lambda msg: print(f"error: {msg.text}") if msg.type == "error" else None)
# 获取下一个控制台日志
async with page.expect_console_message() as msg_info:
# Issue console.log inside the page
await page.evaluate("console.log('hello', 42, { foo: 'bar' })")
msg = await msg_info.value
# 解构打印参数
await msg.args[0].json_value() # hello
await msg.args[1].json_value() # 42
ElementHandle
ElementHandle
表示页内 DOM
元素。ElementHandles
可以使用page.query_selector()
方法创建。
href_element = await page.query_selector("a")
await href_element.click()
参数链接:https://playwright.dev/python/docs/api/class-elementhandle
ElementHandle常用方法
box = await element_handle.bounding_box()
await page.mouse.click(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2)
element_handle.bounding_box()
:返回元素 xy 坐标和宽高。- 如果要点击元素的中心位置请使用:
box["x"] + box["width"] / 2, box["y"] + box["height"] / 2
- 如果要点击元素的中心位置请使用:
element_handle.click(**kwargs)
:点击元素,参数很多。element_handle.dblclick(**kwargs)
:双击元素。element_handle.content_frame()
:返回 iframe 的内容框架await element_handle.dispatch_event("click")
:触发元素 click 事件。无论元素的可见性状态如何。element_handle.fill(value, **kwargs)
:为指定元素设置值element_handle.focus()
:让元素获得焦点。element_handle.get_attribute(name)
:获取元素的指定属性值。element_handle.hover(**kwargs)
:将鼠标移动到指定元素上element_handle.scroll_into_view_if_needed(**kwargs)
:等待元素可以被操作后将元素移动到视图中。element_handle.select_option(**kwargs)
:选择下拉框的选项,可以通过index,value
进行选择。不是下拉框则报错。element_handle.select_text(**kwargs)
:选择一个元素上所有文本,相当于鼠标拖动选择。element_handle.set_checked(checked, **kwargs)
:勾选,或取消勾选复选框元素。element_handle.set_input_files(files, **kwargs)
:设置 input 上传文件。element_handle.text_content()
:返回node.textContent
Keyboard
参数链接:https://playwright.dev/python/docs/api/class-keyboard
element_handle.press(key, **kwargs)
:模拟键盘按键,支持组合键element_handle.press("a")
:直接输入字母aelement_handle.press("Control+v")
:Ctrl+vpage.keyboard.press('Tab')
:Tabelement_handle.press("Control+Shift+T")
:Ctrl+Shift+T
Mouse
鼠标对于左上角开始的坐标进行移动
await page.mouse.move(0, 0)
:鼠标移动到指定位置await page.mouse.down()
:鼠标按下await page.mouse.up()
:鼠标弹起mouse.click(x, y, **kwargs)
:鼠标单击mouse.dblclick(x, y, **kwargs)
:鼠标双击mouse.wheel(delta_x, delta_y)
:鼠标滚轮,x,y 像素。
自定义操作(执行 JS)
在各种情况下,将元素传入 JS 进行操作。
evaluate
:如果传入的是函数,那么将解析为函数,否则以表达式运算。- 如果结果是一个
Promise
或者函数是异步的,evaluate
将自动等待直到它被解决:*await* page.evaluate("([x, y]) => Promise.resolve(x * y)", [7, 8])
*print*(*await* page.evaluate("1 + 2"))
eval_on_selector
:将定位到的元素传入 JS 中操作。await page.eval_on_selector('div', 'el => window.getComputedStyle(el).fontSize')
eval_on_selector_all
:将定位到的多个元素传入 JS 中操作await page.eval_on_selector_all('li.selected', '(items) => items.length')
- 具体内容可以见官网:https://playwright.dev/python/docs/core-concepts
重用认证状态
浏览器在登录后,保存状态,下次启动之后读取,无须重新登录。
- 保存状态到本地:
await context.storage_state(path="state.json")
- 读取本地状态:
context = await browser.new_context(storage_state="state.json")
:读取本地状态,并创建一个新的上下文。
这里的两步操作都需要创建一个无痕浏览器进行操作
browser = await p.chromium.launch(headless=False)
context = await browser.new_context()
# 将本地数据用于创建无痕浏览器
# context = await browser.new_context(storage_state="state.json")
# 创建新页面进行访问
page = await context.new_page()
await page.goto("https://www.baidu.com")
# 保存状态到本地
await context.storage_state(path="state.json")
会话存储
import os
# Get session storage and store as env variable
session_storage = await page.evaluate("() => JSON.stringify(sessionStorage)")
os.environ["SESSION_STORAGE"] = session_storage
# Set session storage in a new context
session_storage = os.environ["SESSION_STORAGE"]
await context.add_init_script("""storage => {
if (window.location.hostname == 'example.com') {
entries = JSON.parse(storage)
Object.keys(entries).forEach(key => {
window.sessionStorage.setItem(key, entries[key])
})
}
}""", session_storage)
模拟移动设备
Playwright 另外一个特色功能就是可以支持移动端浏览器的模拟,比如模拟打开 iPhone 12 Pro Max 上的 Safari 浏览器,然后手动设置定位,并打开百度地图并截图。首先我们可以选定一个经纬度,比如故宫的经纬度是 39.913904, 116.39014,我们可以通过 geolocation 参数传递给 Webkit 浏览器并初始化。
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
iphone_12_pro_max = p.devices['iPhone 12 Pro Max']
browser = p.webkit.launch(headless=False)
context = browser.new_context(
**iphone_12_pro_max,
locale='zh-CN',
geolocation={'longitude': 116.39014, 'latitude': 39.913904},
permissions=['geolocation']
)
page = context.new_page()
page.goto('https://amap.com')
page.wait_for_load_state(state='networkidle')
page.screenshot(path='location-iphone.png')
browser.close()
这里我们先用 PlaywrightContextManager 对象的 devices 属性指定了一台移动设备,这里传入的是手机的型号,比如 iPhone 12 Pro Max,当然也可以传其他名称,比如 iPhone 8,Pixel 2 等。 前面我们已经了解了 BrowserContext 对象,BrowserContext 对象也可以用来模拟移动端浏览器,初始化一些移动设备信息、语言、权限、位置等信息,这里我们就用它来创建了一个移动端 BrowserContext 对象,通过 geolocation 参数传入了经纬度信息,通过 permissions 参数传入了赋予的权限信息,最后将得到的 BrowserContext 对象赋值为 context 变量。
接着我们就可以用 BrowserContext 对象来新建一个页面,还是调用 new_page 方法创建一个新的选项卡,然后跳转到高德地图,并调用了 wait_for_load_state 方法等待页面某个状态完成,这里我们传入的 state 是 networkidle,也就是网络空闲状态。因为在页面初始化和加载过程中,肯定是伴随有网络请求的,所以加载过程中肯定不算 networkidle 状态,所以这里我们传入 networkidle 就可以标识当前页面和数据加载完成的状态。加载完成之后,我们再调用 screenshot 方法获取当前页面截图,最后关闭浏览器。
运行下代码,可以发现这里就弹出了一个移动版浏览器,然后加载了高德地图,并定位到了故宫的位置,如图所示: 输出的截图也是浏览器中显示的结果。
所以这样我们就成功实现了移动端浏览器的模拟和一些设置,其操作 API 和 PC 版浏览器是完全一样的。
操作 frame
具体查看:https://playwright.dev/python/docs/api/class-frame#frame-add-script-tag
当你定位到 frame 之后,可以像正常页面一样操作。
frame.add_script_tag(**kwargs)
:添加 js 代码到 frameframe.child_frames
:返回当前页 frame 列表
# 定位到网页的iframe
frame_element_handle = await page.query_selector('.frame-class')
# 返回内容进行操作
frame = await frame_element_handle.content_frame()
# 之后这个frame就可以操作内部元素。
await frame.fill('#username-input', 'John')
操作浏览器弹出的对话框
# 如果没有对话框监听器,那么对话框都会被自动关闭。
# 但如果有对话框监听器,浏览器却没有弹出对话框,那么会一直等待
# 等待弹出窗口,输出窗口的信息
page.on("dialog", lambda dialog: print(dialog.message))
# 等待弹出框,并点击确定。
page.on("dialog", lambda dialog: dialog.accept())
# 等待窗口弹出,并且在窗口中输入指定内容后点击确定。
page.on("dialog", lambda dialog: dialog.accept('66666'))
# 等待弹出框,并点击取消。
page.on("dialog", lambda dialog: dialog.dismiss())
# 输入指定内容后点击取消(我想没有人会这么做)
page.on("dialog", lambda dialog: dialog.dismiss())
# 返回浏览器默认提示内容,没有返回空
page.on("dialog", lambda dialog: dialog.default_value)
# 返回浏览器对话框类型 可以是一个alert,beforeunload,confirm或prompt
page.on("dialog", lambda dialog: dialog.type)
操作浏览器弹出的对话框
# 如果没有对话框监听器,那么对话框都会被自动关闭。
# 但如果有对话框监听器,浏览器却没有弹出对话框,那么会一直等待
# 等待弹出窗口,输出窗口的信息
page.on("dialog", lambda dialog: print(dialog.message))
# 等待弹出框,并点击确定。
page.on("dialog", lambda dialog: dialog.accept())
# 等待窗口弹出,并且在窗口中输入指定内容后点击确定。
page.on("dialog", lambda dialog: dialog.accept('66666'))
# 等待弹出框,并点击取消。
page.on("dialog", lambda dialog: dialog.dismiss())
# 输入指定内容后点击取消(我想没有人会这么做)
page.on("dialog", lambda dialog: dialog.dismiss())
# 返回浏览器默认提示内容,没有返回空
page.on("dialog", lambda dialog: dialog.default_value)
# 返回浏览器对话框类型 可以是一个alert,beforeunload,confirm或prompt
page.on("dialog", lambda dialog: dialog.type)
文件选择器
这里文件选择器记得修改弹出的页面,如果是新窗口要修改监听的 page。
这里注意,如果网页限制了,不让上传的文件是没有办法上传的。
比如限制 3M,但文件超过了 3M 可能就会报错。
# 进入上传文件界面
await page.goto('https://www.wenshushu.cn/')
# 定位按钮
but = await page.query_selector('//*[@id="page_content"]')
# 开始监听文件选择窗口
async with page.expect_file_chooser() as fc_info:
# 点击按钮,弹出上传窗口
await but.click()
# 获取上传窗口信息
file_chooser = await fc_info.value
# 选择需要上传的路径
await file_chooser.set_files(["back.jpeg", 'driver.py'])
# --------------------------------------------------------
# 返回与此文件选择器相关联的元素
but2 = file_chooser.element
# 返回次文件选择器是否接受选择多个文件
file_chooser.is_multiple()
# 返回此文件选择器所属页面
url = file_chooser.page
# 上传文件
await file_chooser.set_files(files, **kwargs)
文本选择
文本选择支持直接使用 text=
这样的语法进行筛选,示例如下:
page.click("text=Log in")
这就代表选择文本是 Log in 的节点,并点击。
CSS 选择器
CSS 选择器可以根据 id
或者 class
筛选:
page.click("button")
page.click("#nav-bar .contact-us-item")
根据特定的节点属性筛选:
page.click("[data-test=login-button]")
page.click("[aria-label='Sign in']")
CSS 选择器 + 文本
我们还可以使用 CSS 选择器结合文本值进行海选,比较常用的就是 has-text 和 text,前者代表包含指定的字符串,后者代表字符串完全匹配,示例如下:
page.click("article:has-text('Playwright')")
page.click("#nav-bar :text('Contact us')")
第一个就是选择文本中包含 Playwright 的 article 节点,第二个就是选择 id 为 nav-bar 节点中文本值等于 Contact us 的节点。
CSS 选择器 + 节点关系
还可以结合节点关系来筛选节点,比如使用 has 来指定另外一个选择器,示例如下:
page.click(".item-description:has(.item-promo-banner)")
比如这里选择的就是选择 class 为 item-description 的节点,且该节点还要包含 class 为 item-promo-banner 的子节点。
另外还有一些相对位置关系,比如 right-of 可以指定位于某个节点右侧的节点,示例如下:
page.click("input:right-of(:text('Username'))")
这里选择的就是一个 input 节点,并且该 input 节点要位于文本值为 Username 的节点的右侧。
XPath
当然 XPath 也是支持的,不过 xpath 这个关键字需要我们自行制定,示例如下:
page.click("xpath=//button")
这里需要在开头指定 xpath=
字符串,代表后面是一个 XPath 表达式。
关于更多选择器的用法和最佳实践,可以参考官方文档:https://playwright.dev/python/docs/selectors。
处理弹出新窗口
# 等待新窗口的弹出
async with page.expect_popup() as popup_info:
# 获取页面操作句柄
new_page = await popup_info.value
# 等待加载,但只是等待页面的加载,动态加载内容无法处理。
await new_page.wait_for_load_state()
await new_page.click('...')
鼠标滑动轨迹
async def get_tracks(distance):
"""
匀变速公式:
v = v0 + at
x = v0t + 1/2 * a*t**2
"""
# 定义初速度
v = 0
# 定义单位时间
t = 0.6
# 定义加速运动和减速运动的分界线
mid = distance * 4 / 5
# 定义当前位移
current = 0
# 定义运动轨迹列表
tracks = []
# 为了一直移动
while current < distance:
if mid > current:
# 定义加速运动的加速度
a = 2
else:
# 定义减速运动的加速度
a = -3
v0 = v
# 计算位移
x = v0 * t + 1 / 2 * a * t ** 2
# 计算当前位移
current += x
# 计算每一次移动的末速度
v = v0 + a * t
tracks.append(round(x))
return tracks
为浏览器增加代理访问
proxy = {
'server': "127.0.0.1:7777",
'username': "",
'password': "",
}
playwright = await async_playwright().start()
browser = await playwright.chromium.launch(headless=False, proxy=proxy)
context = await browser.new_context()
page = await context.new_page()
await page.goto("https://ip138.com/")
playwright基本操作
基本创建代码
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
# p.chromium.launch 创建一个新的浏览器页面 headless 是否设置无头浏览器
browser = await p.chromium.launch(headless=False)
# 创建一个新的页面,并且返回页面句柄
page = await browser.new_page()
# 通过句柄操作浏览器
await page.goto("http://www.baidu.com")
# 输出页面标题
print(await page.title())
# 关闭浏览器
await browser.close()
asyncio.run(main())
创建无痕浏览器
import asyncio
from playwright.async_api import async_playwright
if __name__ == '__main__':
async def main():
async with async_playwright() as p:
# p.chromium.launch 创建一个新的浏览器页面 headless 是否设置无头浏览器
browser = await p.chromium.launch(headless=False,args=["--disable-infobars", "--enable-automation=False",
"--disable-blink-features=AutomationControlled", "--start-maximized"])
# 创建无痕浏览器上下文
context = await browser.new_context(no_viewport=True)
# 创建新页面进行访问
page = await context.new_page()
await page.goto("https://example.com")
asyncio.run(main())
事件监听
Page 对象提供了一个 on 方法,它可以用来监听页面中发生的各个事件,比如 close
、console
、load
、request
、response
等等。
比如这里我们可以监听 response
事件,response
事件可以在每次网络请求得到响应的时候触发,我们可以设置对应的回调方法获取到对应 Response
的全部信息,示例如下:
from playwright.sync_api import sync_playwright
def on_response(response):
print(f'Statue {response.status}: {response.url}')
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.on('response', on_response)
page.goto('https://spa6.scrape.center/')
page.wait_for_load_state('networkidle')
browser.close()
这里我们在创建 Page 对象之后,就开始监听 response
事件,同时将回调方法设置为 on_response
,on_response
对象接收一个参数,然后把 Response
的状态码和链接都输出出来了。
运行之后,可以看到控制台输出结果如下:
Statue 200: https://spa6.scrape.center/
Statue 200: https://spa6.scrape.center/css/app.ea9d802a.css
Statue 200: https://spa6.scrape.center/js/app.5ef0d454.js
Statue 200: https://spa6.scrape.center/js/chunk-vendors.77daf991.js
Statue 200: https://spa6.scrape.center/css/chunk-19c920f8.2a6496e0.css
...
Statue 200: https://spa6.scrape.center/css/chunk-19c920f8.2a6496e0.css
Statue 200: https://spa6.scrape.center/js/chunk-19c920f8.c3a1129d.js
Statue 200: https://spa6.scrape.center/img/logo.a508a8f0.png
Statue 200: https://spa6.scrape.center/fonts/element-icons.535877f5.woff
Statue 301: https://spa6.scrape.center/api/movie?limit=10&offset=0&token=NGMwMzFhNGEzMTFiMzJkOGE0ZTQ1YjUzMTc2OWNiYTI1Yzk0ZDM3MSwxNjIyOTE4NTE5
Statue 200: https://spa6.scrape.center/api/movie/?limit=10&offset=0&token=NGMwMzFhNGEzMTFiMzJkOGE0ZTQ1YjUzMTc2OWNiYTI1Yzk0ZDM3MSwxNjIyOTE4NTE5
Statue 200: https://p0.meituan.net/movie/da64660f82b98cdc1b8a3804e69609e041108.jpg@464w_644h_1e_1c
Statue 200: https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@464w_644h_1e_1c
....
Statue 200: https://p1.meituan.net/movie/b607fba7513e7f15eab170aac1e1400d878112.jpg@464w_644h_1e_1c
可以看到,这里的输出结果其实正好对应浏览器 Network 面板中所有的请求和响应内容,和下图是一一对应的:
这个网站我们之前分析过,其真实的数据都是 Ajax 加载的,同时 Ajax 请求中还带有加密参数,不好轻易获取。
但有了这个方法,这里如果我们想要截获 Ajax 请求,岂不是就非常容易了?
改写一下判定条件,输出对应的 JSON 结果,改写如下:
from playwright.sync_api import sync_playwright
def on_response(response):
if '/api/movie/' in response.url and response.status == 200:
print(response.json())
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.on('response', on_response)
page.goto('https://spa6.scrape.center/')
page.wait_for_load_state('networkidle')
browser.close()
控制台输出如下:
{'count': 100, 'results': [{'id': 1, 'name': '霸王别姬', 'alias': 'Farewell My Concubine', 'cover': 'https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@464w_644h_1e_1c', 'categories': ['剧情', '爱情'], 'published_at': '1993-07-26', 'minute': 171, 'score': 9.5, 'regions': ['中国大陆', '中国香港']},
...
'published_at': None, 'minute': 103, 'score': 9.0, 'regions': ['美国']}, {'id': 10, 'name': '狮子王', 'alias': 'The Lion King', 'cover': 'https://p0.meituan.net/movie/27b76fe6cf3903f3d74963f70786001e1438406.jpg@464w_644h_1e_1c', 'categories': ['动画', '歌舞', '冒险'], 'published_at': '1995-07-15', 'minute': 89, 'score': 9.0, 'regions': ['美国']}]}
简直是得来全不费工夫,我们直接通过这个方法拦截了 Ajax 请求,直接把响应结果拿到了,即使这个 Ajax 请求有加密参数,我们也不用关心,因为我们直接截获了 Ajax 最后响应的结果,这对数据爬取来说实在是太方便了。
另外还有很多其他的事件监听,这里不再一一介绍了,可以查阅官方文档,参考类似的写法实现。
获取页面源码
要获取页面的 HTML 代码其实很简单,我们直接通过 content 方法获取即可,用法如下:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto('https://spa6.scrape.center/')
page.wait_for_load_state('networkidle')
html = page.content()
print(html)
browser.close()
运行结果就是页面的 HTML 代码。获取了 HTML 代码之后,我们通过一些解析工具就可以提取想要的信息了。
页面点击
刚才我们通过示例也了解了页面点击的方法,那就是 click,这里详细说一下其使用方法。
页面点击的 API 定义如下:
page.click(selector, **kwargs)
这里可以看到必传的参数是 selector,其他的参数都是可选的。第一个 selector 就代表选择器,可以用来匹配想要点击的节点,如果传入的选择器匹配了多个节点,那么只会用第一个节点。
这个方法的内部执行逻辑如下:
- 根据 selector 找到匹配的节点,如果没有找到,那就一直等待直到超时,超时时间可以由额外的 timeout 参数设置,默认是 30 秒。
- 等待对该节点的可操作性检查的结果,比如说如果某个按钮设置了不可点击,那它会等待该按钮变成了可点击的时候才去点击,除非通过 force 参数设置跳过可操作性检查步骤强制点击。
- 如果需要的话,就滚动下页面,将需要被点击的节点呈现出来。
- 调用 page 对象的 mouse 方法,点击节点中心的位置,如果指定了 position 参数,那就点击指定的位置。
click 方法的一些比较重要的参数如下:
click_count
:点击次数,默认为 1。timeout
:等待要点击的节点的超时时间,默认是 30 秒。position
:需要传入一个字典,带有 x 和 y 属性,代表点击位置相对节点左上角的偏移位置。force
:即使不可点击,那也强制点击。默认是 False。 具体的 API 设置参数可以参考官方文档:https://playwright.dev/python/docs/api/class-page/#pageclickselector-kwargs。
文本输入
文本输入对应的方法是 fill,API 定义如下:
page.fill(selector, value, **kwargs)
这个方法有两个必传参数,第一个参数也是 selector
,第二个参数是 value
,代表输入的内容,另外还可以通过 timeout 参数指定对应节点的最长等待时间。
获取节点属性
除了对节点进行操作,我们还可以获取节点的属性,方法就是 get_attribute
,API 定义如下:
page.get_attribute(selector, name, **kwargs)
这个方法有两个必传参数,第一个参数也是 selector,第二个参数是 name,代表要获取的属性名称,另外还可以通过 timeout 参数指定对应节点的最长等待时间。
示例如下:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto('https://spa6.scrape.center/')
page.wait_for_load_state('networkidle')
href = page.get_attribute('a.name', 'href')
print(href)
browser.close()
这里我们调用了 get_attribute
方法,传入的 selector
是 a.name
,选定了 class
为 name
的 a
节点,然后第二个参数传入了 href
,获取超链接的内容,输出结果如下:
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx
可以看到对应 href 属性就获取出来了,但这里只有一条结果,因为这里有个条件,那就是如果传入的选择器匹配了多个节点,那么只会用第一个节点。
那怎么获取所有的节点呢?
获取多个节点
获取所有节点可以使用 query_selector_all
方法,它可以返回节点列表,通过遍历获取到单个节点之后,我们可以接着调用单个节点的方法来进行一些操作和属性获取,示例如下:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto('https://spa6.scrape.center/')
page.wait_for_load_state('networkidle')
elements = page.query_selector_all('a.name')
for element in elements:
print(element.get_attribute('href'))
print(element.text_content())
browser.close()
这里我们通过 query_selector_all
方法获取了所有匹配到的节点,每个节点对应的是一个 ElementHandle
对象,然后 ElementHandle 对象也有 get_attribute
方法来获取节点属性,另外还可以通过 text_content
方法获取节点文本。
运行结果如下:
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx
霸王别姬 - Farewell My Concubine
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIy
这个杀手不太冷 - Léon
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIz
肖申克的救赎 - The Shawshank Redemption
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI0
泰坦尼克号 - Titanic
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI1
罗马假日 - Roman Holiday
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI2
唐伯虎点秋香 - Flirting Scholar
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI3
乱世佳人 - Gone with the Wind
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI4
喜剧之王 - The King of Comedy
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI5
楚门的世界 - The Truman Show
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIxMA==
狮子王 - The Lion King
获取单个节点
获取单个节点也有特定的方法,就是 query_selector
,如果传入的选择器匹配到多个节点,那它只会返回第一个节点,示例如下:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto('https://spa6.scrape.center/')
page.wait_for_load_state('networkidle')
element = page.query_selector('a.name')
print(element.get_attribute('href'))
print(element.text_content())
browser.close()
运行结果如下:
/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx
霸王别姬 - Farewell My Concubine
可以看到这里只输出了第一个匹配节点的信息。
网络劫持
最后再介绍一个实用的方法 route
,利用 route
方法,我们可以实现一些网络劫持和修改操作,比如修改 request
的属性,修改 response
响应结果等。
看一个实例:
from playwright.sync_api import sync_playwright
import re
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
def cancel_request(route, request):
route.abort()
page.route(re.compile(r"(\.png)|(\.jpg)"), cancel_request)
page.goto("https://spa6.scrape.center/")
page.wait_for_load_state('networkidle')
page.screenshot(path='no_picture.png')
browser.close()
这里我们调用了 route
方法,第一个参数通过正则表达式传入了匹配的 URL 路径,这里代表的是任何包含 .png
或 .jpg
的链接,遇到这样的请求,会回调 cancel_request
方法处理, cancel_request
方法可以接收两个参数,一个是 route
,代表一个 CallableRoute
对象,另外一个是 request
,代表 Request
对象。这里我们直接调用了 route
的 abort
方法,取消了这次请求,所以最终导致的结果就是图片的加载全部取消了。
观察下运行结果,如图所示:
可以看到图片全都加载失败了。
这个设置有什么用呢?其实是有用的,因为图片资源都是二进制文件,而我们在做爬取过程中可能并不想关心其具体的二进制文件的内容,可能只关心图片的 URL 是什么,所以在浏览器中是否把图片加载出来就不重要了。所以如此设置之后,我们可以提高整个页面的加载速度,提高爬取效率。
另外,利用这个功能,我们还可以将一些响应内容进行修改,比如直接修改 Response 的结果为自定义的文本文件内容。
首先这里定义一个 HTML 文本文件,命名为 custom_response.html
,内容如下:
<!DOCTYPE html>
<html>
<head>
<title>Hack Response</title>
</head>
<body>
<h1>Hack Response</h1>
</body>
</html>
代码编写如下:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
def modify_response(route, request):
route.fulfill(path="./custom_response.html")
page.route('/', modify_response)
page.goto("https://spa6.scrape.center/")
browser.close()
这里我们使用 route
的 fulfill
方法指定了一个本地文件,就是刚才我们定义的 HTML 文件,运行结果如下: