Pypeteer(p241)对标 Google的Puppeteer(Node.js)实现,不用安装ChromeDriver配置环境变量,但是它会去下载chromium(这在墙内就够折腾人了!!)。由于它用async执行一些支持异步的操作,比Selenium效率高(无论Selenium还是pyppeteer都是通过DevTools Protocol跨进程的方式来和浏览器交互)。
安装:
- 1.python库:
pip3 install pyppeteer
- 2.安装chromium:
pyppeteer-install
- 问题:
HTTPSConnectionPool(host='storage.googleapis.com', port=443): Max retries exceeded with url: /chromium-browser-snapshots/Mac/588429/chrome-mac.zip (Caused by ProtocolError('Connection aborted.', ConnectionResetError(54, 'Connection reset by peer')))
- 科学上网下载:https://storage.googleapis.com/chromium-browser-snapshots/Mac/588429/chrome-mac.zip (588429对应 pyppeteer版本号)
- 解压到:/Users/xxx/Library/Application Support/pyppeteer/local-chromium/588429/chrome-mac/
使用
典型例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| import asyncio
from pyppeteer import launch
async def main():
browser = await launch() #获取browser对象
page = await browser.newPage() #新建一个浏览器tab
await page.setViewport({'width': 1366, 'height': 768})
await page.goto('https://dynamic2.scrape.cuiqingcai.com/') #打开url
await page.waitForSelector('.item .name') #等待节点加载呈现(相当于Selenium的WebDriverWait)
await asyncio.sleep(2)
await page.screenshot(path='example.png')
dimensions = await page.evaluate('''() => {
return { width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
deviceScaleFactor: window.devicePixelRatio } }''' ) #evaluate执行js
print(dimensions)
await browser.close() #关闭浏览器
asyncio.get_event_loop().run_until_complete(main())
|
launch方法:
async def launch(options: dict = None, **kwargs: Any) -> Browser
。参数:
参数名称 | 参数类型 | 参数说明 | |
---|
ignoreHTTPSErrors | bool | 是否忽略 Https 报错信息,默认为 false | |
headless | bool | 是否以”无头”的模式运行 . 默认为 true | |
executablePath | str | 可执行文件的路径。实际就是指定chromium浏览器路径(也可以指定Chrome路径) | |
slowMo | int|float | 操作减速,单位是毫秒。减缓Pyppeteer的一些模拟操作 | |
args | List[String] | 在执行过程中传入的额外参数.args=[’–disable-infobars’,#禁用提示条 ‘–window-size=1366,768’#页面大小设置 ] | |
ignoreDefaultArgs | bool | 是否忽略Pyppeteer的默认参数。如果使用这个参数,那么最好通过 args 设置一些参数,否则可能会出现一些意想不到的问题。这个参数相对比较危险,慎用。 | |
handleSIGINT | bool | 是否响应 SIGINT 信号,也就是是否可以使用 Ctrl+C终止测览器程序默认是 True | |
handleSIGTERM | bool | 是否响应 SIGTERM 信号(一般是 ki11 命令),默认是 True | |
handleSIcHUP | bool | 是否响应 SIGHUP信号,即挂起信号,例如终端退出操作,默认是True。 | |
dumpio | bool | 是否将浏览器进程stdout和stderr导入到 | 和process.stderr中。默认为false |
userDataDir | str | 用户数据文件夹,可以保留一些个性化配置和操作记录(比如Cookies).比如:userDataDir=’./userdata’ | |
env | dict | 环境变量,可以传人字典形式的数据 | |
devtools | bool | 是否自动为每一个页面开启调试工具,默认是 false。如果将这个参数设置参数就会无效,会被强制设置为 false为 True 那么 headless | |
logLevel | int|str | 日志级别,默认和root logger 对象的级别相同。 | |
autoclose | bool | 当一些命令执行完之后,是否自动关闭浏览器,默认是 True | |
loop | asyncio.AbstractEventLoop | 事件循环对象。 | |
反检查
1
2
| await page.evaluateOnNewDocument('Object.defineProperty(navigator, "webdriver", {get: () => undefined})')#跟Slenium一样的js
await page.goto('https://antispider1.scrape.center/')
|
无痕模式
1
2
| context = await browser.createIncognitoBrowserContext()
page = await context.newPage()
|
选择器和XPth
1
2
3
4
5
6
7
8
9
| #css选择器
await page.J('.item .name') #J=querySelector都是匹配单个节点
await page.JJ('.item .name') #JJ=querySelectorAll都是匹配多个节点
await page.Jeval('h2', 'node => node.innerText') #Jeval=querySelectorEval:对匹配元素执行js
await page.JJeval('.categories button span', 'nodes => nodes.map(node => node.innerText)')# #JJeval=querySelectorAllEval
#xpath
await page.xpath('//div[contains(@class,"item")]//a[@class="name"]') #jx=xpath
await page.xpath('//a[@class="name" and contains(@href,"/detail/")]')
|
选项卡(Tab)操作
1
2
| pages= await browser.pages() #获取所有打开的tab
await pages[index].bringToFront() #切换到索引为index的tab
|
页面操作
1
2
3
4
5
6
7
8
9
| await page.goBack() #前进(后退goForward)
await page.reload() #刷新
await page.pdf() #保存pdf
await page.screenshot() #截图
await page.setUserAgent('Python') #设置user-agent
await page.setContent(HTML_CODE) #设置页面html
await page.setExtraHTTPHeaders(headers={}) #设置header
await page.close() #关闭页面
await page.authenticate({'username': 'admin', 'password': 'admin'})
|
延时等待
方法 | 描述 |
---|
waitForSelector | 等待符合 css选择器 的节点加载出来。 |
waitForXPath | 等待符合 XPath 的节点加载出来。 |
waitForFunction | 等待某个JavaScript 方法执行完毕或返回结果, |
waitForNavigation | 等待页面跳转,如果没加载出来,就报错 |
waitForRequest | 等待某个特定的请求发出。 |
waitFwaitForResponse | 等待某个特定请求对应的响应。 |
waitFor | 通用的等待方法。 |
点击&输入模拟
1
2
3
4
5
6
7
8
9
10
11
| await page.waitForSelector('.item .name')
#鼠标点击
await page.click('.item .name', options={
'button': 'right', #鼠标按钮,取值left、middle、right
'clickCount': 1, #点击次数
'delay': 3000, #延迟点击(毫秒)
})
#输入
await page.type('#q','iPad')
|
信息获取
1
2
| await page.content()
await page.cookies()
|
执行js
1
2
3
4
5
| await page.evaluate('window.scrollBy(0, document.body.scrollHeight)')# 滚动到页面底部
await page.evaluate('''()=>{
for (var i = 0; i <= 1000*i; y += 100) {
window.scrollTo(0,i);
}''')
|
事件监听
1
2
3
4
| def on_response(res):
print(f"on_response-- {res.status}: {res.url}")
page.on("response",on_response)
|
实例(p276)
1
2
3
4
5
6
7
8
9
| async def scrape_page(url, selector):
logging.info('scraping %s', url)
try:
await tab.goto(url)
await tab.waitForSelector(selector, options={
'timeout': TIMEOUT * 1000
})
except TimeoutError:
logging.error('error occurred while scraping %s', url, exc_info=True)
|
大量使用了querySelectorAllEval
,看了下返回的 ElementHandle(继承自JSHandle)里面既然没有像Selenium的text属性,果然是对标Puppeteer完全是js搞定(JSHandle提供的方法大部分也是通过执行js来完成的)
1
2
3
4
5
| name = await page.Jeval('h2', 'n => n.innerText')
categories = await page.JJeval('.categories button span', 'nodes => nodes.map(n => n.innerText)')
cover = await page.Jeval('.cover', 'n => n.src')
score = await page.Jeval('.score', 'n => n.innerText')
drama = await page.Jeval('.drama p', 'n => n.innerText')
|