亚洲国产精品乱码一区二区,美景房屋2免费观看,哎呀哎呀在线观看视频高清国语,从镜子里看我是怎么C哭你

Article / 文章中心

網(wǎng)絡(luò)爬蟲之使用pyppeteer替代selenium完美繞過webdriver檢測

發(fā)布時(shí)間:2021-01-17 點(diǎn)擊數(shù):3298

1引言

曾經(jīng)使用模擬瀏覽器操作(selenium + webdriver)來寫爬蟲,但是稍微有點(diǎn)反爬的網(wǎng)站都會(huì)對(duì)selenium和webdriver進(jìn)行識(shí)別,網(wǎng)站只需要在前端js添加一下判斷腳本,很容易就可以判斷出是真人訪問還是webdriver。雖然也可以通過中間代理的方式進(jìn)行js注入屏蔽webdriver檢測,但是webdriver對(duì)瀏覽器的模擬操作(輸入、點(diǎn)擊等等)都會(huì)留下webdriver的標(biāo)記,同樣會(huì)被識(shí)別出來,要繞過這種檢測,只有重新編譯webdriver,麻煩自不必說,難度不是一般大。

作為selenium+webdriver的優(yōu)秀替代,pyppeteer就是一個(gè)很好的選擇。

2 手動(dòng)安裝

通過pip使用豆瓣源加速安裝pyppeteer:

pip install -i https://pypi.douban.com/simple pypeteer

按照官方手冊(cè),先來感受一下:

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第一次運(yùn)行時(shí),會(huì)自動(dòng)下載chromium瀏覽器,時(shí)間可能會(huì)有些長。不過,我第一次運(yùn)行時(shí),直接報(bào)錯(cuò):

ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1056)

嘗試多種方法無果,無奈只能手動(dòng)下載,但手動(dòng)下載的方法網(wǎng)上資料也幾乎沒有,讓我來做這個(gè)先行者吧。

上面代碼運(yùn)行雖然報(bào)錯(cuò),但是控制臺(tái)前兩行卻提供了很有用的信息:

[W:pyppeteer.chromium_downloader] start chromium download.
Download may take a few minutes.

可以看到,下載功能是由pyppeteer.chromium_downloader模塊完成的,那么我們進(jìn)入這個(gè)模塊查看源碼。

在這個(gè)模塊源碼中,我們可以看到downloadURLs、chromiumExecutable等變量,很明顯指的就是下載鏈接和chromium的可執(zhí)行文件路徑。我們重點(diǎn)關(guān)注一下可執(zhí)行文件路徑

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',
}

可見,無論在哪個(gè)平臺(tái)下,chromiumExecutable都是由是4個(gè)部分組成,其中 DOWNLOADS_FOLDER 和 REVISION是定義好的變量:

DOWNLOADS_FOLDER = Path(__pyppeteer_home__) / 'local-chromium'

進(jìn)一步查看可以發(fā)現(xiàn):

__pyppeteer_home__ = os.environ.get('PYPPETEER_HOME', AppDirs('pyppeteer').user_data_dir)
REVISION = os.environ.get('PYPPETEER_CHROMIUM_REVISION', __chromium_revision__)

所以,DOWNLOADS_FOLDER 和 REVISION都是讀取對(duì)應(yīng)環(huán)境變量設(shè)置好的值,如果沒有設(shè)置,就使用默認(rèn)值。我們來輸出一下,看看默認(rèn)值:

import pyppeteer.chromium_downloader
print('默認(rèn)版本是:{}'.format(pyppeteer.__chromium_revision__))
print('可執(zhí)行文件默認(rèn)路徑:{}'.format(pyppeteer.chromium_downloader.chromiumExecutable.get('win64')))
print('win64平臺(tái)下載鏈接為:{}'.format(pyppeteer.chromium_downloader.downloadURLs.get('win64')))

輸出結(jié)果如下:

1
2
3
默認(rèn)版本是:575458
可執(zhí)行文件默認(rèn)路徑:C:\Users\Administrator\AppData\Local\pyppeteer\pyppeteer\local-chromium\575458\chrome-win32\chrome.exe
win64平臺(tái)下載鏈接為:https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/575458/chrome-win32.zip

 在使用上面代碼的時(shí)候,你可以將win64換成你的平臺(tái)就好了,有了上面的下載鏈接,這個(gè)時(shí)候就可以先開始下載著chromium瀏覽器(有些慢),然后繼續(xù)往下看。

對(duì)于版本,沒什么好說的,是用默認(rèn)的就好了。但是,對(duì)于chromium的可執(zhí)行文件路徑,默認(rèn)是在C盤,對(duì)于有C盤潔癖的我,咋看咋不舒服,那就改了吧。從上面的分析中我們可以知道,C:\Users\Administrator\AppData\Local\pyppeteer\pyppeteer這一部分讀取的是環(huán)境變量或者默認(rèn)值,所以,我們可以通過配置環(huán)境變量改這一部分(或者也可以直接改源碼,讀取環(huán)境變量那一行,直接設(shè)為固定值),通過os.environ添加PYPPETEER_HOME這一變量值,例如我想把我的chromium放在D盤的Program Files文件夾下:
import os
os.environ['PYPPETEER_HOME'] = 'D:\Program Files'
import pyppeteer.chromium_downloader
print('默認(rèn)版本是:{}'.format(pyppeteer.__chromium_revision__))
print('可執(zhí)行文件默認(rèn)路徑:{}'.format(pyppeteer.chromium_downloader.chromiumExecutable.get('win64')))
print('win64平臺(tái)下載鏈接為:{}'.format(pyppeteer.chromium_downloader.downloadURLs.get('win64')))

輸出如下:

1
2
3
默認(rèn)版本是:575458
可執(zhí)行文件默認(rèn)路徑:D:\Program Files\local-chromium\575458\chrome-win32\chrome.exe
win64平臺(tái)下載鏈接為:https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/575458/chrome-win32.zip

 特別提醒:上面設(shè)置環(huán)境變量的那一行,必須在導(dǎo)入pyppeteer這一行上面,否則設(shè)置無效。

上面這種方法你需要在每次使用pypeeteer之前通過這行代碼設(shè)置一下,實(shí)在麻煩,所以,我還是更愿意直接在windows系統(tǒng)里面添加這個(gè)變量:

雖然我們把環(huán)境變量設(shè)置為D:\Program Files,但是層層文件夾之后,才到真正的可執(zhí)行文件chrome.exe,下載好的壓縮包解壓后,所有文件都在名為chrome-win的文件夾中,所以,我們需要在D:\Program Files創(chuàng)建local-chromium\575458這兩個(gè)文件夾(575458是上面的版本號(hào),記得修改為你的版本號(hào)),然后將解壓得到的chrome-win文件夾,重命名為chrome-win32,然后直接拷貝進(jìn)去就好,整個(gè)安裝過程就完成了。

再來試試最初(最上面)的代碼,你會(huì)看到,已經(jīng)可以成功運(yùn)行。

我相信,大多數(shù)閱讀這篇博文的讀者都是用pyppeteer來開發(fā)爬蟲(別說維護(hù)世界和平,我不信),那么接下來,重點(diǎn)來說說爬蟲中要用到的一些主要操作。

3 主要操作

3.1 打開瀏覽器

打開瀏覽器是通過pyppeteer.launcher.launch(options: dict = None, **kwargs) 方法,運(yùn)行該函數(shù)后,會(huì)得到一個(gè)pyppeteer.browser.Browser實(shí)例,也就是說瀏覽器對(duì)象實(shí)例。launch方法是必須使用的方法,所以,詳細(xì)學(xué)學(xué)它的參數(shù),你也直接閱讀官方文檔,因?yàn)槲乙彩侵苯臃g的:

  • ignoreHTTPSErrors (bool): 是否HTTPS錯(cuò)誤,某人情況下為False.
  • headless (bool): 是否以無頭模式(無界面模式)執(zhí)行,默認(rèn)為True,為True時(shí)是不會(huì)彈出可視界面的,所以,上面代碼運(yùn)行時(shí)設(shè)置headless=False。注意,下面還有個(gè)devtools參數(shù),表示是否出現(xiàn)打開調(diào)試窗口,如果devtools設(shè)置為True,headless就算設(shè)置為False也會(huì)彈出可視界面。
  • executablePath (str): Chromium或Chrome瀏覽器的可執(zhí)行文件路徑,如果設(shè)置,則使用設(shè)置的這個(gè)路徑,不使用默認(rèn)設(shè)置.
  • slowMo (int|float): 設(shè)置這個(gè)參數(shù)可以延遲pyppeteer的操作,單位是毫秒.
  • args (List[str]): 要傳遞給瀏覽器進(jìn)程的一些其他參數(shù).
  • ignoreDefaultArgs (bool): 如果有些參數(shù)你不想使用默認(rèn)值,那么,通過這個(gè)參數(shù)設(shè)置,不過,孩子,最好別用,有危險(xiǎn)(電腦會(huì)爆炸).
  • handleSIGINT (bool): 是否響應(yīng) SIGINT 信號(hào),是否允許通過快捷鍵Ctrl+C來終止瀏覽器進(jìn)程,默認(rèn)值為True,也就是允許.
  • handleSIGTERM (bool): 是否響應(yīng) SIGTERM 信號(hào),也就是說kill命令關(guān)閉瀏覽器,,默認(rèn)值為True,也就是允許.
  • handleSIGHUP (bool): 是否響應(yīng) SIGHUP 信號(hào),即掛起信號(hào),默認(rèn)值為True,也就是允許.
  • dumpio (bool): 是要將瀏覽器進(jìn)程的輸出傳遞給process.stdout 和 process.stderr 對(duì)象,默認(rèn)為False不傳遞。
  • userDataDir (str): 用戶數(shù)據(jù)文件目錄.
  • env (dict): 以字典的形式傳遞給瀏覽器環(huán)境變量.
  • devtools (bool): 是否打開調(diào)試窗口,上面介紹headless參數(shù)是說過,默認(rèn)值為False不打開.
  • logLevel (int|str): 日志級(jí)別,默認(rèn)和 root logger 對(duì)象的級(jí)別相同.
  • autoClose (bool): 當(dāng)所有操作都執(zhí)行完后,是否自動(dòng)關(guān)閉瀏覽器,默認(rèn)True,自動(dòng)關(guān)閉.
  • loop (asyncio.AbstractEventLoop): 時(shí)間循環(huán)。
  • appMode (bool): Deprecated.

打開瀏覽器操作簡單,看參數(shù)就行,不多介紹。

3.2 調(diào)整窗口大小

如果你運(yùn)行了上面的代碼,你會(huì)發(fā)現(xiàn),打開的頁面只在窗口左上角一小塊顯示,看著很別扭,這是因?yàn)閜yppeteer默認(rèn)窗口大小是800*600,所以,調(diào)整一下吧。調(diào)整窗口大小通過方法實(shí)現(xiàn),看下面代碼,最大化窗口:

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())    

3.3 設(shè)置userAgent

常規(guī)操作,不多說,上代碼:

import asyncio
from pyppeteer import launch
 
async def main():
browser = await launch(headless=False)
page = await browser.newPage()
# 設(shè)置請(qǐng)求頭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())

3.4 執(zhí)行js腳本

有時(shí)候,為了達(dá)成某些目的(例如屏蔽網(wǎng)站原有js),我們不可避免得需要執(zhí)行一些js腳本。執(zhí)行js腳本通過evaluate方法。如下所示,我們通過js來修改window.navigator.webdriver屬性的值,由此繞過網(wǎng)站對(duì)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方法執(zhí)行了兩段js腳本,第一段腳本將webdriver的屬性值設(shè)為false,第二段代碼在此讀取 webdriver屬性值,輸出為false。

3.5 模擬操作

pyppeteer提供了Keyboard和Mouse兩個(gè)類來實(shí)現(xiàn)模擬操作,前者是用來實(shí)現(xiàn)鍵盤模擬,后者實(shí)現(xiàn)鼠標(biāo)模擬(還有其他觸屏之類的就不說了)。

主要來說說輸入和點(diǎn)擊:

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') # 模擬鍵盤輸入手機(jī)號(hào)
await page.click('form section button') # 模擬鼠標(biāo)點(diǎn)擊獲取驗(yàn)證碼
await asyncio.sleep(200)
await browser.close()
 
asyncio.get_event_loop().run_until_complete(main())

上面的模擬操作中,無論是模擬鍵盤輸入還是鼠標(biāo)點(diǎn)擊定位都是通過css選擇器,似乎pyppeteer的type和click直接模擬操作定位都只能通過css選擇器(或者是我在官方文檔中沒找到方法),當(dāng)然,要間接通過xpath先定位,然后再模擬操作也是可以的。下一小節(jié)中模擬登陸外賣平臺(tái)就是用這種方法,不過,這種方法要麻煩一些,不推薦。

 3.6 某電商平臺(tái)模擬登陸

我曾經(jīng)用selenium + chrome 實(shí)現(xiàn)了模擬登陸這個(gè)電商平臺(tái),但是實(shí)在是有些麻煩,繞過對(duì)webdriver的檢測不難,但是,通過webdriver對(duì)瀏覽器的每一步操作都會(huì)留下特殊的痕跡,會(huì)被平臺(tái)識(shí)別,這個(gè)必須通過重新編譯chrome的webdriver才能實(shí)現(xiàn),麻煩得讓人想哭。不說了,都是淚,下面直接上用pyppeteer實(shí)現(xiàn)的代碼:

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('*****手機(jī)號(hào)********')
    await click_yzm[0].click()
    ya = input('請(qǐng)輸入驗(yàn)證碼:')
    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())

登錄時(shí),由于等待時(shí)間過長(我猜的)導(dǎo)致出現(xiàn)以下錯(cuò)誤:

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)

再次運(yùn)行就沒問題了。可以成功繞過官方對(duì)webdriver的檢測,登錄成功,諸位可以自己嘗試一下。