曾经使用模拟浏览器操做(selenium + webdriver)来写爬虫,可是稍微有点反爬的网站都会对selenium和webdriver进行识别,网站只须要在前端js添加一下判断脚本,很容易就能够判断出是真人访问仍是webdriver。虽然也能够经过中间代理的方式进行js注入屏蔽webdriver检测,可是webdriver对浏览器的模拟操做(输入、点击等等)都会留下webdriver的标记,一样会被识别出来,要绕过这种检测,只有从新编译webdriver,麻烦自没必要说,难度不是通常大。css
做为selenium+webdriver的优秀替代,pyppeteer就是一个很好的选择。html
经过pip使用豆瓣源加速安装pyppeteer:前端
pip install -i https://pypi.douban.com/simple pypeteer
按照官方手册,先来感觉一下:linux
import asyncio from pyppeteer import launch async def main(): browser = await launch(headless=False) page = await browser.newPage() await page.goto('http://www.baidu.com/') await asyncio.sleep(100) await browser.close() asyncio.get_event_loop().run_until_complete(main())
pyppeteer第一次运行时,会自动下载chromium浏览器,时间可能会有些长。不过,我第一次运行时,直接报错:git
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1056)
尝试多种方法无果,无奈只能手动下载,但手动下载的方法网上资料也几乎没有,让我来作这个先行者吧。github
上面代码运行虽然报错,可是控制台前两行却提供了颇有用的信息:web
[W:pyppeteer.chromium_downloader] start chromium download.
Download may take a few minutes.
能够看到,下载功能是由pyppeteer.chromium_downloader模块完成的,那么咱们进入这个模块查看源码。chrome
在这个模块源码中,咱们能够看到downloadURLs、chromiumExecutable等变量,很明显指的就是下载连接和chromium的可执行文件路径。咱们重点关注一下可执行文件路径windows
chromiumExecutable: chromiumExecutable = { 'linux': DOWNLOADS_FOLDER / REVISION / 'chrome-linux' / 'chrome', 'mac': (DOWNLOADS_FOLDER / REVISION / 'chrome-mac' / 'Chromium.app' / 'Contents' / 'MacOS' / 'Chromium'), 'win32': DOWNLOADS_FOLDER / REVISION / 'chrome-win32' / 'chrome.exe', 'win64': DOWNLOADS_FOLDER / REVISION / 'chrome-win32' / 'chrome.exe', }
可见,不管在哪一个平台下,chromiumExecutable都是由是4个部分组成,其中 DOWNLOADS_FOLDER 和 REVISION是定义好的变量:api
DOWNLOADS_FOLDER = Path(__pyppeteer_home__) / 'local-chromium'
进一步查看能够发现:
__pyppeteer_home__ = os.environ.get('PYPPETEER_HOME', AppDirs('pyppeteer').user_data_dir) REVISION = os.environ.get('PYPPETEER_CHROMIUM_REVISION', __chromium_revision__)
因此,DOWNLOADS_FOLDER 和 REVISION都是读取对应环境变量设置好的值,若是没有设置,就使用默认值。咱们来输出一下,看看默认值:
import pyppeteer.chromium_downloader print('默认版本是:{}'.format(pyppeteer.__chromium_revision__)) print('可执行文件默认路径:{}'.format(pyppeteer.chromium_downloader.chromiumExecutable.get('win64'))) print('win64平台下载连接为:{}'.format(pyppeteer.chromium_downloader.downloadURLs.get('win64')))
输出结果以下:
默认版本是:575458 可执行文件默认路径:C:\Users\Administrator\AppData\Local\pyppeteer\pyppeteer\local-chromium\575458\chrome-win32\chrome.exe win64平台下载连接为:https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/575458/chrome-win32.zip
在使用上面代码的时候,你能够将win64换成你的平台就行了,有了上面的下载连接,这个时候就能够先开始下载着chromium浏览器(有些慢),而后继续往下看。
import os os.environ['PYPPETEER_HOME'] = 'D:\Program Files' import pyppeteer.chromium_downloader print('默认版本是:{}'.format(pyppeteer.__chromium_revision__)) print('可执行文件默认路径:{}'.format(pyppeteer.chromium_downloader.chromiumExecutable.get('win64'))) print('win64平台下载连接为:{}'.format(pyppeteer.chromium_downloader.downloadURLs.get('win64')))
输出以下:
默认版本是:575458 可执行文件默认路径:D:\Program Files\local-chromium\575458\chrome-win32\chrome.exe win64平台下载连接为:https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/575458/chrome-win32.zip
特别提醒:上面设置环境变量的那一行,必须在导入pyppeteer这一行公里,不然设置无效。
上面这种方法你须要在每次使用pypeeteer以前经过这行代码设置一下,实在麻烦,因此,我仍是更愿意直接在windows系统里面添加这个变量:
虽然咱们把环境变量设置为D:\Program Files,可是层层文件夹以后,才到真正的可执行文件chrome.exe,下载好的压缩包解压后,全部文件都在名为chrome-win的文件夹中,因此,咱们须要在D:\Program Files建立local-chromium\575458这两个文件夹(575458是上面的版本号,记得修改成你的版本号),而后将解压获得的chrome-win文件夹,重命名为chrome-win32,而后直接拷贝进去就好,整个安装过程就完成了。
再来试试最初(最上面)的代码,你会看到,已经能够成功运行。
我相信,大多数阅读这篇博文的读者都是用pyppeteer来开发爬虫(别说维护世界和平,我不信),那么接下来,重点来讲说爬虫中要用到的一些主要操做。
打开浏览器是经过pyppeteer.launcher.launch(options: dict = None, **kwargs) 方法,运行该函数后,会获得一个pyppeteer.browser.Browser实例,也就是说浏览器对象实例。launch方法是必须使用的方法,因此,详细学学它的参数,你也直接阅读官方文档,由于我也是直接翻译的:
打开浏览器操做简单,看参数就行,很少介绍。
若是你运行了上面的代码,你会发现,打开的页面只在窗口左上角一小块显示,看着很别扭,这是由于pyppeteer默认窗口大小是800*600,因此,调整一下吧。调整窗口大小经过方法实现,看下面代码,最大化窗口:
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(): browser = await launch(headless=False) page = await browser.newPage() width, height = screen_size() await page.setViewport({ # 最大化窗口 "width": width, "height": height }) await page.goto('http://www.baidu.com/') await asyncio.sleep(100) await browser.close() asyncio.get_event_loop().run_until_complete(main())
常规操做,很少说,上代码:
import asyncio from pyppeteer import launch async def main(): browser = await launch(headless=False) page = await browser.newPage() # 设置请求头userAgent await page.setUserAgent('Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Mobile Safari/537.36') await page.goto('http://www.baidu.com/') await asyncio.sleep(100) await browser.close() asyncio.get_event_loop().run_until_complete(main())
有时候,为了达成某些目的(例如屏蔽网站原有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。
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先定位,而后再模拟操做也是能够的。下一小节中模拟登录外卖平台就是用这种方法,不过,这种方法要麻烦一些,不推荐。
我曾经用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的检测,登陆成功,诸位能够本身尝试一下。
当使用selenium+webdriver写爬虫被检测到时,pyppeteer是你得不二选择,几乎全部能在人工操做浏览器进行的操做经过pyppeteer都能实现,且能完美避开官方对webdriver的检测。pyppeteer涉及的使用方法还不少,本文只介绍了经常使用方法的很小很小一部分,须要一说的是,pyppeteer的中文资料真的不多,多看看官方文档吧。
参考: