一、常规操作
点击
点击用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'))
#
# 获取文本
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("").innerText.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选择器定位
问题2:找各个标题
.t就是这些标题所在
例五:
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,其方法定义如下:
pyppeteer.launcher.launch(options: dict = None, **kwargs) → pyppeteer.browser.Browser
可以看到它处于 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 界面了。但是可以看到这就是一个光秃秃的浏览器而已,看一下相关信息
看到了,这就是 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())
运行时候进行一下登录,然后就会弹出滑块,自己手动拖动一下,然后就报错了,界面如下:
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())
这样整个界面就正常了:
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())
输出:
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对象]
'''
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)
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
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