Post

0703Pypeteer

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。参数:

参数名称参数类型参数说明 
ignoreHTTPSErrorsbool是否忽略 Https 报错信息,默认为 false 
headlessbool是否以”无头”的模式运行 . 默认为 true 
executablePathstr可执行文件的路径。实际就是指定chromium浏览器路径(也可以指定Chrome路径) 
slowMoint|float操作减速,单位是毫秒。减缓Pyppeteer的一些模拟操作 
argsList[String]在执行过程中传入的额外参数.args=[’–disable-infobars’,#禁用提示条
‘–window-size=1366,768’#页面大小设置
]
 
ignoreDefaultArgsbool是否忽略Pyppeteer的默认参数。如果使用这个参数,那么最好通过 args 设置一些参数,否则可能会出现一些意想不到的问题。这个参数相对比较危险,慎用。 
handleSIGINTbool是否响应 SIGINT 信号,也就是是否可以使用 Ctrl+C终止测览器程序默认是 True 
handleSIGTERMbool是否响应 SIGTERM 信号(一般是 ki11 命令),默认是 True 
handleSIcHUPbool是否响应 SIGHUP信号,即挂起信号,例如终端退出操作,默认是True。 
dumpiobool是否将浏览器进程stdout和stderr导入到和process.stderr中。默认为false
userDataDirstr用户数据文件夹,可以保留一些个性化配置和操作记录(比如Cookies).比如:userDataDir=’./userdata’ 
envdict环境变量,可以传人字典形式的数据 
devtoolsbool是否自动为每一个页面开启调试工具,默认是 false。如果将这个参数设置参数就会无效,会被强制设置为 false为 True 那么 headless 
logLevelint|str日志级别,默认和root logger 对象的级别相同。 
autoclosebool当一些命令执行完之后,是否自动关闭浏览器,默认是 True 
loopasyncio.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)

  • 还是Selenium中的例子
  • 请求
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')
This post is licensed under CC BY 4.0 by the author.