Pyppeteer(1)

一、常规操作

点击
  点击用page.click方法,默认是css-selector。

await page.click('#J_QRCodeLogin > div.login-links > a.forget-pwd.J_Quick2Static')

输入
  输入用page.type方法,第一个参数是选择器,第二个参数是要输入的字符串,第三个参数是延时设置。

await page.type('#TPL_username_1', '123123', {'delay': input_time_random() - 50})
await page.type('#TPL_password_1', '232322332', {'delay': input_time_random()})
import asyncio
import random
from pyppeteer import launch

def input_time_random():
    return random.randint(100, 151)

async def main():
    browser = await launch({'headless':False})
    page = await browser.newPage()
    await page.goto('https://login.taobao.com')
    await page.waitFor(3 * 1000)
    await page.click('#J_QRCodeLogin > div.login-links > a.forget-pwd.J_Quick2Static')
    await page.waitFor(3 * 1000)
    await page.type('#TPL_username_1', '123123', {'delay': input_time_random() - 50})
    await page.type('#TPL_password_1', '232322332', {'delay': input_time_random()})
    await page.waitFor(3 * 1000)
    await browser.close()

asyncio.get_event_loop().run_until_complete(main())

滚动

await page.evaluate('window.scrollBy(0, window.innerHeight)') #淘宝滚动加载用

获取元素坐标

常用于获取验证码相关坐标

el = await page.querySelector('#nc_1_n1z')
box = await el.boundingBox()
await page.hover('#nc_1_n1z') #鼠标移动方块上
await page.mouse.down() #鼠标拖动操作包括按下、移动、放开
await page.mouse.move(box['x']+1000,box['y'], {'delay': random.randint(1000, 2000),'steps':3})
await page.mouse.up()

二、常用函数

page.goto(url)

请求指定url
比较常用的用法是:page.goto(url,{'waitUntil':'load'})
waitUntil的参数有:load,domcontentloaded,networkidle0,networkidle2

networkidle0表示when there are no more than 0 network connections for at least 500 ms.
newwordidle2表示when there are no more than 2 network connections for at least 500 ms

DOM文档的加载步骤为:

  • 解析HTML结构。
  • 加载外部脚本和样式表文件。
  • 解析并执行脚本代码。
  • DOM树构建完成。 //domcontentloaded
  • 加载图片等外部文件。
  • 页面加载完毕。 //load

page.waitfor(time)
设置页面等待时间,单位是毫秒,常用于设置操作间隔,让page能加载完成指定目标,如等待3秒钟:

page.waitfor(3*1000)

page.waitForSelector(selector)/page.waitForXPath(xpath)
等待目标元素加载完成,默认timeout是30秒,可以辅助指定位置元素是否已经加载完成。

page.waitForNavigation()
等到某动作完成,常用的是配合其他动作一起使用,如:

await asyncio.wait([
    page.click('a.my-link'),
    page.waitForNavigation(),
])

这段代码表示,等待连接点击并跳转完成。

page.J(css selector)/page.querySelector(css selector)
通过css selector定位元素,前面是缩写函数

page.Jx(xpath)/page.xpath(xpath)
通过xpath定位元素,前面是缩写函数

page.content()
获取页面当前加载网页的document,用法:

doc = await page.content()

page.cookies()
获取页面当前的cookies,常用如:

...登录后...
cookies= await page.cookies()
dosomething(cookies)

page.eveluate(jsstr)
执行js,js代码用字符串书写,注意引号的使用

page.evaluateOnNewDocument(jsstr)
用法同上,不过在页面新打开一个document时才生效,上面的函数是当前document生效。

page.hover(selector)
指针移动到selector定位的元素位置

page.screenshot()
页面截屏

page.setCacheEnabled()
是否启用缓存,默认是True

page.setJavaScriptEnabled()
是否允许加载js,默认是True

page.setRequestInterception()
是否允许请求和返回注入,默认是False

page.setUserAgent()
设置UA

page.setViewport()
用法:

await page.setViewport({'width':xx,'height':xx})

2. 元素选择器方法名 $变为querySelector

# Pyppeteer使用Python风格的函数名
Page.querySelector()
Page.querySelectorAll()
Page.xpath() 

# 简写方式为:
Page.J(), Page.JJ(), and Page.Jx()

Page.evaluate() 和 Page.querySelectorEval()的参数

Pyppeteer的evaluate()方法只使用JavaScript字符串,该字符串可以是函数也可以是表达式,Pyppeteer会进行自动判断。但有时会判断错误,如果字符串被判断成了函数,并且报错,可以添加选项force_expr=True,强制Pyppeteer作为表达式处理。

获取页面内容:

content = await page.evaluate('document.body.textContent', force_expr=True)

获取元素的内部文字:

element = await page.querySelector('h1')
title = await page.evaluate('(element) => element.textContent', element)

示例

import asyncio
from pyppeteer import launch


async def main():
    # headless参数设为False,则变成有头模式
    # Pyppeteer支持字典和关键字传参,Puppeteer只支持字典传参

    # 指定引擎路径
    # exepath = r'C:\Users\Administrator\AppData\Local\pyppeteer\pyppeteer\local-chromium\575458\chrome-win32/chrome.exe'
    # browser = await launch({'executablePath': exepath, 'headless': False, 'slowMo': 30})

    browser = await launch(
        # headless=False,
        {'headless': False}
    )
    page = await browser.newPage()
    await page.setViewport(viewport={'width': 1280, 'height': 800})         # 设置页面视图大小
    await page.setJavaScriptEnabled(enabled=True)       # 是否启用JS,enabled设为False,则无渲染效果
    res = await page.goto('https://www.toutiao.com/', options={'timeout': 1000})# 超时间见 1000 毫秒
    resp_headers = res.headers                              # 响应头
    resp_status = res.status                                # 响应状态

    # 等待
    await asyncio.sleep(2)
    # 第二种方法,在while循环里强行查询某元素进行等待
    while not await page.querySelector('.t'):
        pass

    await page.evaluate('window.scrollBy(0, document.body.scrollHeight)')# 滚动到页面底部
    await asyncio.sleep(2)
    await page.screenshot({'path': 'toutiao.png'})          # 截图 保存图片
    print(await page.cookies())                             # 打印页面cookies

    """  打印页面文本 """
    print(await page.content())                             # 获取所有 html 内容

    # 在网页上执行js 脚本
    dimensions = await page.evaluate(pageFunction='''() => {
            return {
                width: document.documentElement.clientWidth,        // 页面宽度
                height: document.documentElement.clientHeight,      // 页面高度
                deviceScaleFactor: window.devicePixelRatio,         // 像素比 1.0000000149011612
            }
        }''', force_expr=False)                                     # force_expr=False  执行的是函数
    print(dimensions)

    #  只获取文本  执行 js 脚本  force_expr为True则执行的是表达式
    content = await page.evaluate(pageFunction='document.body.textContent', force_expr=True)
    print(content)
    print(await page.title())                                           # 打印当前页标题

    # 抓取新闻内容  可以使用 xpath 表达式
    """
    # Pyppeteer 三种解析方式
    Page.querySelector()        # 选择器
    Page.querySelectorAll()
    Page.xpath()                # xpath  表达式
    
    # 简写方式为:
    Page.J(), Page.JJ(), and Page.Jx()
    """
    element = await page.querySelector(".feed-infinite-wrapper > ul>li")  # 纸抓取一个
    print(element)
    # 获取所有文本内容  执行 js
    content = await page.evaluate('(element) => element.textContent', element)
    print(content)

    # elements = await page.xpath('//div[@class="title-box"]/a')
    elements = await page.querySelectorAll(".title-box a")
    for item in elements:
        print(await item.getProperty('textContent'))
        # <pyppeteer.execution_context.JSHandle object at 0x000002220E7FE518>

        # 获取文本
        title_str = await (await item.getProperty('textContent')).jsonValue()

        # 获取链接
        title_link = await (await item.getProperty('href')).jsonValue()
        print(title_str)
        print(title_link)

    # 关闭浏览器
    await browser.close()


asyncio.get_event_loop().run_until_complete(main())
import asyncio
import pyppeteer
from collections import namedtuple
 
headers = {
    'date': 'Sun, 28 Apr 2019 06:50:20 GMT',
    'server': 'Cmcc',
    'x-frame-options': 'SAMEORIGIN\nSAMEORIGIN',
    'last-modified': 'Fri, 26 Apr 2019 09:58:09 GMT',
    'accept-ranges': 'bytes',
    'cache-control': 'max-age=43200',
    'expires': 'Sun, 28 Apr 2019 18:50:20 GMT',
    'vary': 'Accept-Encoding,User-Agent',
    'content-encoding': 'gzip',
    'content-length': '19823',
    'content-type': 'text/html',
    'connection': 'Keep-alive',
    'via': '1.1 ID-0314217270751344 uproxy-17'
}
 
Response = namedtuple("rs", "title url html cookies headers history status")
 
 
async def get_html(url):
    browser = await pyppeteer.launch(headless=True, args=['--no-sandbox'])
    page = await browser.newPage()
    res = await page.goto(url, options={'timeout': 10000})
    data = await page.content()
    title = await page.title()
    resp_cookies = await page.cookies()  # cookie
    resp_headers = res.headers  # 响应头
    resp_status = res.status  # 响应状态
    print(data)
    print(title)
    print(resp_headers)
    print(resp_status)
    return title
 
 
if __name__ == '__main__':
    url_list = [
        "https://www.toutiao.com",
        "http://jandan.net/ooxx/page-8#comments",
        "https://www.12306.cn/index"
    ]
    task = [get_html(url) for url in url_list]
 
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(asyncio.gather(*task))
    for res in results:
        print(res)

三、快速上手

例一:爬取http://quotes.toscrape.com/js/ 全部页面数据

import asyncio
from pyppeteer import launch
from pyquery import PyQuery as pq

async def main():
    browser = await launch()
    page = await browser.newPage()
    await page.goto('http://quotes.toscrape.com/js/')
    doc = pq(await page.content())
    print('Quotes:', doc('.quote').length)
    await browser.close()

asyncio.get_event_loop().run_until_complete(main())

运行结果:10
1. launch 方法会新建一个 Browser 对象;
2. 调用 newPage方法相当于浏览器中新建了一个选项卡,同时新建了一个 Page 对象;
3. 然后Page对象调用了goto方法就相当于在浏览器中输入了这个URL,浏览器跳转到了对应的页面进行加载;
4. 加载完成之后再调用 content 方法,返回当前浏览器页面的源代码;
5. 然后进一步地,我们用 pyquery 进行同样地解析,就可以得到 JavaScript 渲染的结果了;

在这个过程中,我们没有配置 Chrome 浏览器,没有配置浏览器驱动,免去了一些繁琐的步骤,同样达到了 Selenium 的效果,还实现了异步抓取,爽歪歪!

例二:模拟网页截图,保存 PDF,另外还可以执行自定义的 JavaScript 获得特定的内容

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch()
    page = await browser.newPage()
    await page.goto('http://quotes.toscrape.com/js/')
    await page.screenshot(path='example.png')
    await page.pdf(path='example.pdf')
    dimensions = await page.evaluate('''() => {
        return {
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight,
            deviceScaleFactor: window.devicePixelRatio,
        }
    }''')

    print(dimensions)
    # >>> {'width': 800, 'height': 600, 'deviceScaleFactor': 1}
    await browser.close()

asyncio.get_event_loop().run_until_complete(main())

这里我们又用到了几个新的 API,完成**了网页截图保存、网页导出 PDF 保存、执行 JavaScript **并返回对应数据。

  • 首先 screenshot 方法可以传入保存的图片路径,另外还可以指定保存格式 type、清晰度 quality、是否全屏 fullPage、裁切 clip 等各个参数实现截图。
  • 然后,可见其内容也是 JavaScript 渲染后的内容,另外这个方法还可以指定放缩大小 scale、页码范围 pageRanges、宽高 width 和 height、方向 landscape 等等参数,导出定制化的 pdf 用这个方法就十分方便。
  • 最后我们又调用了 evaluate 方法执行了一些 JavaScript,JavaScript 传入的是一个函数,使用 return 方法返回了网页的宽高、像素大小比率三个值,最后得到的是个 JSON 格式的对象,内容如下:
{'width': 800, 'height': 600, 'deviceScaleFactor': 1}

总之,利用 Pyppeteer 我们可以控制浏览器执行几乎所有动作,想要的操作和功能基本都可以实现,用它来自由地控制爬虫当然就不在话下了。

例三:今日头条

import asyncio
from pyppeteer import launch

async def main():
    # headless参数设为False,则变成有头模式
    browser = await launch(
        # headless=False
    )
    
    page = await browser.newPage()
    
    # 设置页面视图大小
    await page.setViewport(viewport={'width':1280, 'height':800})
    
    # 是否启用JS,enabled设为False,则无渲染效果
    await page.setJavaScriptEnabled(enabled=True)
    
    await page.goto('https://www.toutiao.com/')
    
    # 打印页面cookies
    print(await page.cookies())
    
    # 打印页面文本
    print(await page.content())
    
    # 打印当前页标题
    print(await page.title())
    
    # 抓取新闻标题
    title_elements = await page.xpath('//div[@class="title-box"]/a')
    for item in title_elements:
        # 获取文本
        title_str = await (await item.getProperty('textContent')).jsonValue()
        print(await item.getProperty('textContent'))
        # 获取链接
        title_link = await (await item.getProperty('href')).jsonValue()
        print(title_str)
        print(title_link)
    
    # 关闭浏览器
    await browser.close()

asyncio.get_event_loop().run_until_complete(main())

例四:与百度首页交互

import time
import asyncio
from pyppeteer import launch


async def main():
    browser = await launch(headless=False)
    page = await browser.newPage()
    await page.setViewport({'width': 1200, 'height': 800})
    await page.goto('https://www.baidu.com')
    await page.type('input#kw.s_ipt', 'python')             # 在搜索框中输入python
    await page.click('input#su')                            # 点击搜索按钮

    # 第一种方法等待元素加载,强行等待5秒
    # await asyncio.sleep(5)

    # 第二种方法等待元素加载,在while循环里强行查询某元素进行等待
    while not await page.querySelector('.t'):
        pass

    # 滚动到页面底部
    await page.evaluate('window.scrollBy(0, window.innerHeight)')

    # 这些等待方法都不好用
    # await page.waitForXPath('h3', timeout=300)
    # await page.waitForNavigation(waitUntil="networkidle0")
    # await page.waitForFunction('document.getElementByTag("h3")')
    # await page.waitForSelector('.t')
    # await page.waitFor('document.querySelector("#t")')
    # await page.waitForNavigation(waitUntil='networkidle0')
    # await page.waitForFunction('document.querySelector("").inner‌​Text.length == 7')

    title_elements = await page.xpath('//h3[contains(@class,"t")]/a')
    for item in title_elements:
        title_str = await (await item.getProperty('textContent')).jsonValue()
        print(title_str)
    await browser.close()


asyncio.get_event_loop().run_until_complete(main())

问题1:css选择器定位

image

image

问题2:找各个标题

.t就是这些标题所在

image
image

例五:

class GetJsEncryptPage():
​
    def __init__(self):
        self.loop = asyncio.get_event_loop()
        self.log = ICrawlerLog('spider').save
​
    async def main(self, url, ):  # 定义main协程函数,
        # 以下使用await 可以针对耗时的操作进行挂起
        browser = await launch({'headless': True, 'args': ['--no-sandbox', '--disable-infobars',
                                                           # '--proxy-server={}'.format(get_ip()),
                                                           ],})  # 启动pyppeteer 属于内存中实现交互的模拟器
        page = await browser.newPage()  # 启动个新的浏览器页面标签
        await page.setUserAgent("Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36")
        cookies = {}
        try:
            await page.goto(url)  # 访问页面
            # 始终让window.navigator.webdriver=false
            # navigator是windiw对象的一个属性,同时修改plugins,languages,navigator 且让
            # await page.setJavaScriptEnabled(enabled=True)  # 使用 JS 渲染
            await page.evaluate('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')  # 以下为插入中间js,将淘宝会为了检测浏览器而调用的js修改其结果。
            await page.evaluate('''() =>{ window.navigator.chrome = { runtime: {},  }; }''')
            await page.evaluate('''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }''')
            await page.evaluate('''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''')
            await page.goto(url)  # 访问页面
            # content = await page.content()  # 获取页面内容
            await asyncio.sleep(2)
        except:
            await page.evaluate('''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')
            await page.evaluate('''() =>{ window.navigator.chrome = { runtime: {},  }; }''')
            await page.evaluate('''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }''')
            await page.evaluate('''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''')
            # await page.evaluate('window.open("{}");'.format(url))
            await page.evaluate('window.location="{}";'.format(url))
            # await page.goto(url)  # 访问登录页面
        try:
            cookies = await self.get_cookie(page)
        except Exception as e:
            await browser.close()
        finally:
            await browser.close()
​
        return cookies
​
    async def get_cookie(self, page):
        # res = await page.content()
        cookies_list = await page.cookies()
        cookies = {}
        for cookie in cookies_list:
            cookies[cookie.get('name')] =  cookie.get('value')
        return cookies
​
    def retry_if_result_none(self, result):
        return result is None
​
    def input_time_random(self, ):
        return random.randint(100, 151)
​
    def work(self, url):
        pass
​
    def run(self, url, func):
        result = {}
        try:
            # task = asyncio.wait([])
            result = self.loop.run_until_complete(func(url))  # 将协程注册到事件循环,并启动事件循环
        except Exception as e:
            self.log.info('协程被动结束, chrome关闭')
            for task in asyncio.Task.all_tasks():
                task.cancel()
                self.loop.stop()
                self.loop.run_forever()
        # self.loop.close()
        return result

四、详细用法

https://miyakogi.github.io/pyppeteer/reference.html

1. 开启浏览器

启动 Chrome 进程并返回浏览器实例

使用 Pyppeteer 的第一步便是启动浏览器,首先我们看下怎样启动一个浏览器,其实就相当于我们点击桌面上的浏览器图标一样,把它开起来。用 Pyppeteer 完成同样的操作,只需要调用 launch 方法即可。

我们先看下 launch 方法的 API,链接为:https://miyakogi.github.io/pyppeteer/reference.html#pyppeteer.launcher.launch,其方法定义如下:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">pyppeteer.launcher.launch(options: dict = None, **kwargs) → pyppeteer.browser.Browser</pre>

可以看到它处于 launcher 模块中,参数没有在声明中特别指定,返回类型是 browser 模块中的 Browser 对象,也就是说浏览器对象实例。另外观察源码发现这是一个 async 修饰的方法,所以调用它的时候需要使用 await。

调用 launch 方法即可,相关参数介绍:

  • ignoreHTTPSErrors (bool): 是否要忽略 HTTPS 的错误,默认是 False。
  • headless (bool): 是否启用 Headless 模式,即无界面模式,如果 devtools 这个参数是 True 的话,那么该参数就会被设置为 False,否则为 True,即默认是开启无界面模式的。
  • executablePath (str): 可执行文件的路径,如果指定之后就不需要使用默认的 Chromium 了,可以指定为已有的 Chrome 或 Chromium。
  • slowMo (int|float): 通过传入指定的时间,可以减缓 Pyppeteer 的一些模拟操作。
  • args (List[str]): 在执行过程中可以传入的额外参数。
  • ignoreDefaultArgs (bool): 不使用 Pyppeteer 的默认参数,如果使用了这个参数,那么最好通过 args 参数来设定一些参数,否则可能会出现一些意想不到的问题。这个参数相对比较危险,慎用。
  • handleSIGINT (bool): 是否响应 SIGINT 信号,也就是可以使用 Ctrl + C 来终止浏览器程序,默认是 True。
  • handleSIGTERM (bool): 是否响应 SIGTERM 信号,一般是 kill 命令,默认是 True。
  • handleSIGHUP (bool): 是否响应 SIGHUP 信号,即挂起信号,比如终端退出操作,默认是 True。
  • dumpio (bool): 是否将 Pyppeteer 的输出内容传给 process.stdout 和 process.stderr 对象,默认是 False。
  • userDataDir (str): 即用户数据文件夹,即可以保留一些个性化配置和操作记录。
  • env (dict): 环境变量,可以通过字典形式传入。
  • devtools (bool): 是否为每一个页面自动开启调试工具,默认是 False。如果这个参数设置为 True,那么 headless 参数就会无效,会被强制设置为 False。
  • logLevel (int|str): 日志级别,默认和 root logger 对象的级别相同。
  • autoClose (bool): 当一些命令执行完之后,是否自动关闭浏览器,默认是 True。
  • loop (asyncio.AbstractEventLoop): 时间循环对象。
  • devtools (bool): 是否为每一个页面自动开启调试工具,默认是 False。如果这个参数设置为 True,那么 headless 参数就会无效,会被强制设置为 False。

好了,知道这些参数之后,我们可以先试试看。

示例一:首先可以试用下最常用的参数 headless,如果我们将它设置为 True 或者默认不设置它,在启动的时候我们是看不到任何界面的,如果把它设置为 False,那么在启动的时候就可以看到界面了,一般我们在调试的时候会把它设置为 False,在生产环境上就可以设置为 True,我们先尝试一下关闭 headless 模式:

import asyncio
from pyppeteer import launch

async def main():
    await launch(headless=False)
    await asyncio.sleep(100)

asyncio.get_event_loop().run_until_complete(main())

运行之后看不到任何控制台输出,但是这时候就会出现一个空白的 Chromium 界面了。但是可以看到这就是一个光秃秃的浏览器而已,看一下相关信息


image.png

看到了,这就是 Chromium,上面还写了开发者内部版本,可以认为是开发版的 Chrome 浏览器就好。

示例二:开启调试模式。比如在写爬虫的时候会经常需要分析网页结构还有网络请求,所以开启调试工具还是很有必要的,我们可以将 devtools 参数设置为 True,这样每开启一个界面就会弹出一个调试窗口,非常方便,示例如下:

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch(devtools=True)
    page = await browser.newPage()
    await page.goto('https://www.baidu.com')
    await asyncio.sleep(100)

asyncio.get_event_loop().run_until_complete(main())

刚才说过 devtools 这个参数如果设置为了 True,那么 headless 就会被关闭了,界面始终会显现出来。在这里我们新建了一个页面,打开了百度,界面运行效果如下:


示例三:可以看到上面的一条提示:"Chrome 正受到自动测试软件的控制",这个提示条有点烦,那咋关闭呢?这时候就需要用到 args 参数了,禁用操作如下

browser = await launch(headless=False, args=['--disable-infobars'])

另外有人就说了,这里你只是把提示关闭了,有些网站还是会检测到是 webdriver 吧,比如淘宝检测到是 webdriver 就会禁止登录了,我们可以试试:

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch(headless=False)
    page = await browser.newPage()
    await page.goto('https://www.taobao.com')
    await asyncio.sleep(100)

asyncio.get_event_loop().run_until_complete(main())

运行时候进行一下登录,然后就会弹出滑块,自己手动拖动一下,然后就报错了,界面如下:

image.png

2. 最大化窗口

如果你运行了上面的代码,你会发现,打开的页面只在窗口左上角一小块显示,看着很别扭,这是因为pyppeteer默认窗口大小是800*600,所以,调整一下吧。需要设置下 window-size 还有 viewport,代码如下:

import asyncio
from pyppeteer import launch

width, height = 1366, 768

async def main():
    browser = await launch(headless=False,args=[f'--window-size={width},{height}'])
    page = await browser.newPage()
    await page.setViewport({'width': width, 'height': height})
    await page.goto('https://www.taobao.com')
    await asyncio.sleep(100)

asyncio.get_event_loop().run_until_complete(main())

这样整个界面就正常了:

image.png

3. 执行js脚本

(1)规避webdriver检测:

有时候,为了达成某些目的(例如屏蔽网站原有js),我们不可避免得需要执行一些js脚本。执行js脚本通过evaluate方法。如下所示,我们通过js来修改window.navigator.webdriver属性的值,由此绕过网站对webdriver的检测:

import asyncio
from pyppeteer import launch
 
async def main():
js1 = '''() =>{
 
    Object.defineProperties(navigator,{
    webdriver:{
        get: () => false
        }
    })
}'''
 
js2 = '''() => {
    alert (
        window.navigator.webdriver
    )
}'''
browser = await launch({'headless':False, 'args':['--no-sandbox'],})
 
page = await browser.newPage()
await page.goto('https://h5.ele.me/login/')
await page.evaluate(js1)
await page.evaluate(js2)
 
asyncio.get_event_loop().run_until_complete(main())

在上面代码中,通过page.evalute方法执行了两段js脚本,第一段脚本将webdriver的属性值设为false,第二段代码在此读取 webdriver属性值,输出为false。

OK,那刚才所说的 webdriver 检测问题怎样来解决呢?其实淘宝主要通过 window.navigator.webdriver 来对 webdriver 进行检测,所以我们只需要使用 JavaScript 将它设置为 false 即可,代码如下:

import asyncio
from pyppeteer import launch


async def main():
    browser = await launch(headless=False, args=['--disable-infobars'])
    page = await browser.newPage()
    await page.goto('https://login.taobao.com/member/login.jhtml?redirectURL=https://www.taobao.com/')
    await page.evaluate(
        '''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')
    await asyncio.sleep(100)

asyncio.get_event_loop().run_until_complete(main())

Object.defineProperty()
  会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。如果不指定configurable, writable, enumerable ,则这些属性默认值为false,如果不指定value, get, set,则这些属性默认值为undefined

这里没加输入用户名密码的代码,当然后面可以自行添加,下面打开之后,我们点击输入用户名密码,然后这时候会出现一个滑动条,这里滑动的话,就可以通过了,如图所示:

import asyncio
import random
from pyppeteer import launch

def input_time_random():
    return random.randint(100, 151)

async def main():
    browser = await launch({'headless':False})
    page = await browser.newPage()
    await page.evaluateOnNewDocument(
        '''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')
    await page.evaluateOnNewDocument('''() =>{ window.navigator.chrome = { runtime: {},  }; }''')
    await page.evaluateOnNewDocument('''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }''')
    await page.evaluateOnNewDocument('''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''')

    await page.goto('https://login.taobao.com')
    await page.waitFor(4 * 1000)
    # await page.click('#J_QRCodeLogin > div.login-links > a.forget-pwd.J_Quick2Static')
    await page.waitFor(3 * 1000)
    await page.type('#TPL_username_1', '123123', {'delay': input_time_random() - 50})
    await page.type('#TPL_password_1', '232322332', {'delay': input_time_random()})
    await page.waitFor(2 * 1000)
    el = await page.querySelector('#nc_1_n1z')
    box = await el.boundingBox()
    await page.hover('#nc_1_n1z')
    await page.mouse.down()
    await page.mouse.move(box['x']+1000,box['y'], {'delay': random.randint(1000, 2000),'steps':3})
    await page.mouse.up()
    await page.waitFor(5 * 1000)
    await browser.close()

asyncio.get_event_loop().run_until_complete(main())

OK,这样的话我们就成功规避了 webdriver 的检测,使用鼠标拖动模拟就可以完成淘宝的登录了。

还有另一种方法可以进一步免去淘宝登录的烦恼,那就是设置用户目录。平时我们已经注意到,当我们登录淘宝之后,如果下次再次打开浏览器发现还是登录的状态。这是因为淘宝的一些关键 Cookies 已经保存到本地了,下次登录的时候可以直接读取并保持登录状态。那么这些信息保存在哪里了呢?其实就是保存在用户目录下了,里面不仅包含了浏览器的基本配置信息,还有一些 Cache、Cookies 等各种信息都在里面,如果我们能在浏览器启动的时候读取这些信息,那么启动的时候就可以恢复一些历史记录甚至一些登录状态信息了。这也就解决了一个问题:很多朋友在每次动 Selenium 或 Pyppeteer 的时候总是是一个全新的浏览器,那就是没有设置用户目录,如果设置了它,每次打开就不再是一个全新的浏览器了,它可以恢复之前的历史记录,也可以恢复很多网站的登录信息。

那么这个怎么来做呢?很简单,在启动的时候设置 userDataDir 就好了,示例如下:

import asyncio
from pyppeteer import launch

async def main():
    browser = await launch(headless=False, userDataDir='./userdata', args=['--disable-infobars'])
    page = await browser.newPage()
    await page.goto('https://www.taobao.com')
    await asyncio.sleep(100)

asyncio.get_event_loop().run_until_complete(main())

好,这里就是加了一个 userDataDir 的属性,值为 userdata,即当前目录的 userdata 文件夹。我们可以首先运行一下,然后登录一次淘宝,这时候我们同时可以观察到在当前运行目录下又多了一个 userdata 的文件夹,里面的结构是这样子的:

[图片上传失败...(image-4a5a94-1587565252771)]

具体的介绍可以看官方的一些说明,如:https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md,这里面介绍了 userdatadir 的相关内容。

再次运行上面的代码,这时候可以发现现在就已经是登录状态了,不需要再次登录了,这样就成功跳过了登录的流程。当然可能时间太久了,Cookies 都过期了,那还是需要登录的。

(2)执行js程序:拖动滚轮。调用evaluate方法。

import asyncio
from pyppeteer import launch

width, height = 1366, 768

async def main():
    browser = await launch(headless=False)
    page = await browser.newPage()
    await page.setViewport({'width': width, 'height': height})
    await page.goto('https://movie.douban.com/typerank?type_name=%E5%8A%A8%E4%BD%9C&type=5&interval_id=100:90&action=')
    await asyncio.sleep(3)
    #evaluate可以返回js程序的返回值
    dimensions = await page.evaluate('window.scrollTo(0,document.body.scrollHeight)')
    await asyncio.sleep(3)
    print(dimensions)
    await browser.close()

asyncio.get_event_loop().run_until_complete(main())

5. 设置userAgent设置代理IP

browser = await launch({'headless': True, 'timeout': 500, 'args': ['--disable-extensions', 
                                                                   '--hide-scrollbars',
                                                                   '--disable-bundled-ppapi-flash',
                                                                   '--mute-audio', 
                                                                   '--no-sandbox',
                                                                   '--disable-setuid-sandbox',
                                                                   '--disable-gpu', 
                                                                   '--proxy-server={}'.format(get_ip()),
                                                                   ], })
page = await browser.newPage()
await page.setUserAgent("Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36")
await page.setViewport({'width': 1000,'height': 3480,})

6.模拟操作

pyppeteer提供了Keyboard和Mouse两个类来实现模拟操作,前者是用来实现键盘模拟,后者实现鼠标模拟(还有其他触屏之类的就不说了)。

主要来说说输入和点击:

import os

os.environ['PYPPETEER_HOME'] = 'D:\Program Files'
import asyncio
from pyppeteer import launch


async def main():
    browser = await launch(headless=False, args=['--disable-infobars'])
    page = await browser.newPage()
    await page.goto('https://h5.ele.me/login/')
    await page.type('form section input', '12345678999')    # 模拟键盘输入手机号
    await page.click('form section button')                 # 模拟鼠标点击获取验证码
    await asyncio.sleep(200)
    await browser.close()

asyncio.get_event_loop().run_until_complete(main()) 

上面的模拟操作中,无论是模拟键盘输入还是鼠标点击定位都是通过css选择器,似乎pyppeteer的type和click直接模拟操作定位都只能通过css选择器(或者是我在官方文档中没找到方法),当然,要间接通过xpath先定位,然后再模拟操作也是可以的。下一小节中模拟登陆外卖平台就是用这种方法,不过,这种方法要麻烦一些,不推荐。

7. 节点交互

import asyncio
from pyppeteer import launch


async def main():
    # headless参数设为False,则变成有头模式
    browser = await launch(
        headless=False
    )
    page = await browser.newPage()

    await page.setViewport(viewport={'width': 1280, 'height': 800})  # 设置页面视图大小
    await page.goto('https://www.baidu.com/')
    # 节点交互
    await page.type('#kw', '周杰伦', {'delay': 1000})      # id选择器
    await asyncio.sleep(3)
    await page.click('#su')
    await asyncio.sleep(3)

    # 使用选择器选中标签进行点击
    alist = await page.querySelectorAll('.s_tab_inner > a')
    a = alist[3]
    await a.click()
    await asyncio.sleep(3)
    await browser.close()
asyncio.get_event_loop().run_until_complete(main())

8. 执行自定义js---

注入拦截和筛选请求和返回。page.on监听请求与响应, 并对请求和响应进行修改和过滤

page.on(event, function) ,指定监听事件, 与处理函数
例如: page.on('request', intercept_response) 
​
# 请求处理函数
async def request_check(req):
        '''请求过滤'''
        if req.resourceType in ['image', 'media', 'eventsource', 'websocket']:
            await req.abort()
        else:
            await req.continue_()
​
# 响应处理函数
async def intercept_response(res):
        resourceType = res.request.resourceType
        if resourceType in ['image', 'media']:
            resp = await res.text()
            print(resp)

下面这个例子经常用来:

  • 加快网页加载速度
  • 快速筛选数据api接口

做新闻爬虫的时候,遇到网页有视频其实挺尴尬的,首先如果加载视频会导致打开网页比较慢,有时甚至会导致浏览器超时崩溃,其次是视频的加载可能不同时带入一些广告的超链接,对于提取新闻内容会造成干扰。
通过page.setRequestInterception参数开启注入。

import asyncio
from pyppeteer import launch

async def inject_request(req):
    """
    resourceType:
        document, stylesheet, image, media, font, script, texttrack, xhr, fetch, eventsource, websocket, manifest, other
    """
    if req.resourceType in ['media','image']:
        await req.abort()
    else:
        await req.continue_()

async def inject_response(res):

    if res.request.resourceType in ['xhr']:
        print(res.request.url)
    
async def main():
    browser = await launch({'headless':False})
    page = await browser.newPage()
    await page.setRequestInterception(True)
    page.on('request', inject_request)
    page.on('response',inject_response)
    await page.goto('https://movie.douban.com/explore#!type=movie&tag=%E7%83%AD%E9%97%A8&sort=recommend&page_limit=20&page_start=0')
    await page.waitFor(5 * 1000)
    await browser.close()

asyncio.get_event_loop().run_until_complete(main())
image

输出:

https://m.douban.com/j/puppy/frodo_landing?include=anony_home
https://movie.douban.com/j/search_tags?type=movie&source=
https://movie.douban.com/j/search_tags?type=movie&tag=%E7%83%AD%E9%97%A8&source=
https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%83%AD%E9%97%A8&sort=recommend&page_limit=20&page_start=0
https://movie.douban.com/j/subject_abstract?subject_id=24389792
https://movie.douban.com/j/subject_abstract?subject_id=27119724

先分析inject_request部分:不请求图片和媒体资源。

async def inject_request(req):
    if req.resourceType in ['media','image']:
        await req.abort()
    else:
        await req.continue_()

一般用得比较多的是一个属性两个方法:

一个属性:
  resourceType,表示请求的资源类型,有document, stylesheet, image, media, font, script, texttrack, xhr, fetch, eventsource, websocket, manifest, other(加粗的是比较常用的资源类型)

两个方法:
  abort(),跳过当前请求
  continue_(),继续当前请求

inject_response部分

async def inject_response(res):

    if res.request.resourceType in ['xhr']:
        print(res.request.url)

一般js动态加载的数据连接在xhr资源,所以我这里把网页请求的xhr资源都打印出来,如果这里没有数据连接,那就是在document里面了,比F12清晰一点。

9. 获取浏览器依据加载的图片内容, Selenium与Pyppeteer相同


执行JS, 返回图片的二进制的Base64编码, 参照: https://www.w3ctech.com/topic/767
'''
() => {
var img = document.getElementById("%s");
var canvas = document.createElement("canvas");
canvas.width = %s;
canvas.height = %s;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var dataURL = canvas.toDataURL("image/png");
return dataURL.replace(/^data:image\/(png|jpg);base64,/, "");}''' % (id, width, height)

10. 切换浏览器的tag

# 在Pyppeteer中每一个标签页就是一个page对象, 切换page对象就是切换标签页
for _page in await browser.pages() :
   if _page != page:
      await _page.close()

三、案例

综合应用:爬取头条和网易的新闻标题

import asyncio
from pyppeteer import launch
from lxml import etree

async def main():
    browser = await launch(headless=False)                              # headless参数设为False,则变成有头模式
    page1 = await browser.newPage()
    await page1.setViewport(viewport={'width': 1280, 'height': 800})    # 设置页面视图大小
    await page1.goto('https://www.toutiao.com/')
    await asyncio.sleep(2)    
    page_text = await page1.content()                                   # 打印页面文本
    
    page2 = await browser.newPage()
    await page2.setViewport(viewport={'width': 1280, 'height': 800})
    await page2.goto('https://news.163.com/domestic/')
    await page2.evaluate('window.scrollTo(0,document.body.scrollHeight)')
    page_text1 = await page2.content()
    await browser.close()
    return {'wangyi':page_text1,'toutiao':page_text}
    
def parse(task):
    content_dic = task.result()
    wangyi = content_dic['wangyi']
    toutiao = content_dic['toutiao']
    tree = etree.HTML(toutiao)
    a_list = tree.xpath('//div[@class="title-box"]/a')
    for a in a_list:
        title = a.xpath('./text()')[0]
        print('toutiao:',title)
    tree = etree.HTML(wangyi)
    div_list = tree.xpath('//div[@class="data_row news_article clearfix "]')
    print(len(div_list))
    
    for div in div_list:
        title = div.xpath('.//div[@class="news_title"]/h3/a/text()')[0]
        print('wangyi:',title)
        
tasks = []
task1 = asyncio.ensure_future(main())
task1.add_done_callback(parse)
tasks.append(task1)
asyncio.get_event_loop().run_until_complete(asyncio.wait(tasks))

爬取结果:
toutiao: 「央视快评」坚守初心 为国奉献
toutiao: 南航一A380客机北京降落时遭冰雹风挡现裂痕 已平安降落无人受伤
toutiao: 美国正开启第二战场:围猎中国高科技企业 |“双线作战”战略意图
toutiao: 云南省陆良县:农民给供销社打“白条”
toutiao: 媒体:90后副县长若非靠拼爹上位 需拿出业绩服众
toutiao: 南航A380飞北京客机遭遇冰雹袭击,挡风玻璃全碎
toutiao: 秘鲁北部发生7.8级地震
toutiao: 1958年,由捷克斯洛伐克援建的北京电影洗印厂曾为全国行业的老大
toutiao: 一箭60星,发射成功!马斯克卫星互联网计划启动
69
wangyi: 中美经贸摩擦背后:有人在干,有人在骗
wangyi: 华为回应个别标准组织撤销资格:产品服务不受影响
wangyi: 隔空约架?中方主播刘欣23年前就赢得国际演讲比赛
wangyi: 从钱学森到任正非 中国教育有多少底气应对全球化
wangyi: 2个月内二度履新 35岁清华博士任安徽省直单位领导
wangyi: 南阳“水氢发动机汽车”引热议 官方回应四大疑问
wangyi: 31岁北大博士跻身县委常委 主笔6万字全县发展规划
wangyi: 干部退休15年后投案自首 省委巡视办:头一次碰到
wangyi: 台湾被标注"中国台湾省" 台外事部门要求更正被拒
wangyi: 190天3次现场办公!南阳领导为何钟爱青年汽车项目

2. 淘宝登录

import time
import random
import asyncio
import pyppeteer


class LoginTaoBao:
    """
    类异步
    """
    pyppeteer.DEBUG = True
    page = None

    async def _injection_js(self):
        """注入js
        """
        await self.page.evaluate('''() =>{
                   Object.defineProperties(navigator,{
                     webdriver:{
                       get: () => false
                     }
                   })
                }''')

    async def _init(self):
        """初始化浏览器
        """
        browser = await pyppeteer.launch({'headless': False,
                                          'args': [
                                              '--window-size={1300},{600}'
                                              '--disable-extensions',
                                              '--hide-scrollbars',
                                              '--disable-bundled-ppapi-flash',
                                              '--mute-audio',
                                              '--no-sandbox',
                                              '--disable-setuid-sandbox',
                                              '--disable-gpu',
                                          ],
                                          'dumpio': True,
                                          })
        self.page = await browser.newPage()
        # 设置浏览器头部
        await self.page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
                                     '(KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299')
        # 设置浏览器大小
        await self.page.setViewport({'width': 1200, 'height': 600})

    async def get_cookie(self):
        cookies_list = await self.page.cookies()
        cookies = ''
        for cookie in cookies_list:
            str_cookie = '{0}={1};'
            str_cookie = str_cookie.format(cookie.get('name'), cookie.get('value'))
            cookies += str_cookie
        print(cookies)
        return cookies

    async def mouse_slider(self):
        """滑动滑块
        """
        await asyncio.sleep(3)
        try:
            await self.page.hover('#nc_1_n1z')           
            await self.page.mouse.down()                        # 鼠标按下按钮            
            await self.page.mouse.move(2000, 0, {'steps': 30})  # 移动鼠标            
            await self.page.mouse.up()                          # 松开鼠标
            await asyncio.sleep(2)
        except Exception as e:
            print(e, '      :错误')
            return None
        else:
            await asyncio.sleep(3)
            # 获取元素内容
            slider_again = await self.page.querySelectorEval('#nc_1__scale_text', 'node => node.textContent')
            if slider_again != '验证通过':
                return None
            else:
                print('验证通过')
                return True

    async def main(self, username_, pwd_):
        """登陆
        """        
        await self._init()                                      # 初始化浏览器       
        await self.page.goto('https://login.taobao.com')        # 打开淘宝登陆页面        
        await self._injection_js()                              # 注入js        
        await self.page.click('div.login-switch')               # 点击密码登陆按钮
        time.sleep(random.random() * 2)
        
        await self.page.type('#TPL_username_1', username_, {'delay': random.randint(100, 151) - 50})    # 输入用户名        
        await self.page.type('#TPL_password_1', pwd_, {'delay': random.randint(100, 151)})              # 输入密码
        time.sleep(random.random() * 2)
        
        slider = await self.page.querySelector('#nc_1__scale_text')             # 获取滑块元素
        if slider:
            print('有滑块')
            # 移动滑块
            flag = await self.mouse_slider()
            if not flag:
                print('滑动滑块失败')
                return None
            time.sleep(random.random() + 1.5)
            # 点击登陆
            print('点击登陆')
            await self.page.click('#J_SubmitStatic')
            await asyncio.sleep(100)
        else:
            print('没滑块')
            # 按下回车
            await self.page.keyboard.press('Enter')


if __name__ == '__main__':
    username = input('淘宝用户名')
    pwd = input('密码')
    login = LoginTaoBao()
    loop = asyncio.get_event_loop()
    task = asyncio.ensure_future(login.main(username, pwd))
    loop.run_until_complete(task)

3. 某电商平台模拟登陆

我曾经用selenium + chrome 实现了模拟登陆这个电商平台,但是实在是有些麻烦,绕过对webdriver的检测不难,但是,通过webdriver对浏览器的每一步操作都会留下特殊的痕迹,会被平台识别,这个必须通过重新编译chrome的webdriver才能实现,麻烦得让人想哭。不说了,都是泪,下面直接上用pyppeteer实现的代码:

import os
os.environ['PYPPETEER_HOME'] = 'D:\Program Files'
import asyncio
from pyppeteer import launch
 
def screen_size():
    """使用tkinter获取屏幕大小"""
    import tkinter
    tk = tkinter.Tk()
    width = tk.winfo_screenwidth()
    height = tk.winfo_screenheight()
    tk.quit()
    return width, height
 
 
async def main():
    js1 = '''() =>{
 
        Object.defineProperties(navigator,{
        webdriver:{
            get: () => false
            }
        })
    }'''
 
    js2 = '''() => {
        alert (
            window.navigator.webdriver
        )
    }'''
    browser = await launch({'headless':False, 'args':['--no-sandbox'],})
 
    page = await browser.newPage()
    width, height = screen_size()
    await page.setViewport({ # 最大化窗口
        "width": width,
        "height": height
    })
    await page.goto('https://h5.ele.me/login/')
    await page.evaluate(js1)
    await page.evaluate(js2)
    input_sjh = await page.xpath('//form/section[1]/input[1]')
    click_yzm = await page.xpath('//form/section[1]/button[1]')
    input_yzm = await page.xpath('//form/section[2]/input[1]')
    but = await page.xpath('//form/section[2]/input[1]')
    print(input_sjh)
    await input_sjh[0].type('*****手机号********')
    await click_yzm[0].click()
    ya = input('请输入验证码:')
    await input_yzm[0].type(str(ya))
    await but[0].click()
    await asyncio.sleep(3)
    await page.goto('https://www.ele.me/home/')
    await asyncio.sleep(100)
    await browser.close()
 
asyncio.get_event_loop().run_until_complete(main())

登录时,由于等待时间过长(我猜的)导致出现以下错误:

pyppeteer.errors.NetworkError: Protocol Error (Runtime.callFunctionOn): Session closed. Most likely the page has been closed.

在github上找到了解决方法,似乎只能改源码,找到pyppeteer包下的connection.py模块,在其43行和44行改为下面这样:

self._ws = websockets.client.connect(
# self._url, max_size=None, loop=self._loop)
self._url, max_size=None, loop=self._loop, ping_interval=None, ping_timeout=None)

再次运行就没问题了。可以成功绕过官方对webdriver的检测,登录成功,诸位可以自己尝试一下。

四、爬取京东商城

from pyppeteer import launch
import asyncio


def screen_size():
    """使用tkinter获取屏幕大小"""
    import tkinter
    tk = tkinter.Tk()
    width = tk.winfo_screenwidth()
    height = tk.winfo_screenheight()
    tk.quit()
    return width, height


async def main(url):
    browser = await launch({'headless': False, 'args': ['--no-sandbox'], })
    # browser = await launch({'args': ['--no-sandbox'], })
    page = await browser.newPage()
    width, height = screen_size()
    await page.setViewport(viewport={"width": width, "height": height})
    await page.setJavaScriptEnabled(enabled=True)
    await page.setUserAgent(
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
        '(KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299'
    )
    await page.goto(url)
    await asyncio.sleep(2*1000)
    await page.evaluate('window.scrollBy(0, document.body.scrollHeight)')
    # await asyncio.sleep(1)

    # content = await page.content()
    li_list = await page.xpath('//*[@id="J_goodsList"]/ul/li')
    print(li_list)
    '''
    [
    <pyppeteer.element_handle.ElementHandle object at 0x000000000B994128>, 
    <pyppeteer.element_handle.ElementHandle object at 0x000000000B8CDD68>, 
    ...一个个pyppeteer对象]
    '''
    item_list = []
    for li in li_list:
        a = await li.xpath('.//div[@class="p-img"]/a')
        detail_url = await (await a[0].getProperty("href")).jsonValue()
        promo_words = await (await a[0].getProperty("title")).jsonValue()
        a_ = await li.xpath('.//div[@class="p-commit"]/strong/a')
        p_commit = await (await a_[0].getProperty("textContent")).jsonValue()
        i = await li.xpath('./div/div[3]/strong/i')
        price = await (await i[0].getProperty("textContent")).jsonValue()
        em = await li.xpath('./div/div[4]/a/em')
        title = await (await em[0].getProperty("textContent")).jsonValue()
        item = {
            "title": title,
            "detail_url": detail_url,
            "promo_words": promo_words,
            'p_commit': p_commit,
            'price': price
        }
        item_list.append(item)
        # print(item)
        # break
    # print(content)

    await page_close(browser)
    return item_list


async def page_close(browser):
    for _page in await browser.pages():
        await _page.close()
    await browser.close()


msg = "手机"
url = "https://search.jd.com/Search?keyword={}&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq={}&cid2=653&cid3=655&page={}"

task_list = []
for i in range(1, 2):
    page = i * 2 - 1
    url = url.format(msg, msg, page)
    task_list.append(main(url))

loop = asyncio.get_event_loop()
results = loop.run_until_complete(asyncio.gather(*task_list))
# print(results, len(results))
for i in results:
    print(i, len(i))

print('*' * 100)
# soup = BeautifulSoup(content, 'lxml')
# div = soup.find('div', id='J_goodsList')
# for i, li in enumerate(div.find_all('li', class_='gl-item')):
#     if li.select('.p-img a'):
#         print(li.select('.p-img a')[0]['href'], i)
#         print(li.select('.p-price i')[0].get_text(), i)
#         print(li.select('.p-name em')[0].text, i)
#     else:
#         print("#" * 200)
#         print(li)

image

detail_url = await (await a[0].getProperty("href")).jsonValue()    # 取属性值
i = await li.xpath('./div/div[3]/strong/i')
price = await (await i[0].getProperty("textContent")).jsonValue()   # 取文本
em = await li.xpath('./div/div[4]/a/em')
title = await (await em[0].getProperty("textContent")).jsonValue()

REF
https://www.jianshu.com/p/84f39941f3ea
https://blog.csdn.net/freeking101/article/details/93331204
https://www.cnblogs.com/baihuitestsoftware/p/10531462.html

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word;">pyppeteer github 地址:https://github.com/miyakogi/pyppeteer
pyppeteer 英文文档地址:https://miyakogi.github.io/pyppeteer/
pyppeteer 官方文档 API Reference :https://miyakogi.github.io/pyppeteer/reference.html
puppeteer( Nodejs 版 selenium )快速入门:https://blog.csdn.net/freeking101/article/details/91542887
爬虫界又出神器|一款比selenium更高效的利器:https://blog.csdn.net/chen801090/article/details/93216278
python爬虫利器 pyppeteer(模拟浏览器) 实战:https://blog.csdn.net/xiaoming0018/article/details/89841728</pre>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339