網絡爬蟲之使用pyppeteer替代selenium完美繞過webdriver檢測

1引言

曾經使用模擬瀏覽器操做(selenium + webdriver)來寫爬蟲,可是稍微有點反爬的網站都會對selenium和webdriver進行識別,網站只須要在前端js添加一下判斷腳本,很容易就能夠判斷出是真人訪問仍是webdriver。雖然也能夠經過中間代理的方式進行js注入屏蔽webdriver檢測,可是webdriver對瀏覽器的模擬操做(輸入、點擊等等)都會留下webdriver的標記,一樣會被識別出來,要繞過這種檢測,只有從新編譯webdriver,麻煩自沒必要說,難度不是通常大。css

做爲selenium+webdriver的優秀替代,pyppeteer就是一個很好的選擇。html

2 手動安裝

經過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瀏覽器(有些慢),而後繼續往下看。

對於版本,沒什麼好說的,是用默認的就行了。可是,對於chromium的可執行文件路徑,默認是在C盤,對於有C盤潔癖的我,咋看咋不舒服,那就改了吧。從上面的分析中咱們能夠知道,C:\Users\Administrator\AppData\Local\pyppeteer\pyppeteer這一部分讀取的是環境變量或者默認值,因此,咱們能夠經過配置環境變量改這一部分(或者也能夠直接改源碼,讀取環境變量那一行,直接設爲固定值),經過os.environ添加PYPPETEER_HOME這一變量值,例如我想把個人chromium放在D盤的Program Files文件夾下:
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來開發爬蟲(別說維護世界和平,我不信),那麼接下來,重點來講說爬蟲中要用到的一些主要操做。

3 主要操做

3.1 打開瀏覽器

打開瀏覽器是經過pyppeteer.launcher.launch(options: dict = None, **kwargs) 方法,運行該函數後,會獲得一個pyppeteer.browser.Browser實例,也就是說瀏覽器對象實例。launch方法是必須使用的方法,因此,詳細學學它的參數,你也直接閱讀官方文檔,由於我也是直接翻譯的:

  • ignoreHTTPSErrors (bool): 是否HTTPS錯誤,某人狀況下爲False.
  • headless (bool): 是否以無頭模式(無界面模式)執行,默認爲True,爲True時是不會彈出可視界面的,因此,上面代碼運行時設置headless=False。注意,下面還有個devtools參數,表示是否出現打開調試窗口,若是devtools設置爲True,headless就算設置爲False也會彈出可視界面。
  • executablePath (str): Chromium或Chrome瀏覽器的可執行文件路徑,若是設置,則使用設置的這個路徑,不使用默認設置.
  • slowMo (int|float): 設置這個參數能夠延遲pyppeteer的操做,單位是毫秒.
  • args (List[str]): 要傳遞給瀏覽器進程的一些其餘參數.
  • ignoreDefaultArgs (bool): 若是有些參數你不想使用默認值,那麼,經過這個參數設置,不過,孩子,最好別用,有危險(電腦會爆炸).
  • handleSIGINT (bool): 是否響應 SIGINT 信號,是否容許經過快捷鍵Ctrl+C來終止瀏覽器進程,默認值爲True,也就是容許.
  • handleSIGTERM (bool): 是否響應 SIGTERM 信號,也就是說kill命令關閉瀏覽器,,默認值爲True,也就是容許.
  • handleSIGHUP (bool): 是否響應 SIGHUP 信號,即掛起信號,默認值爲True,也就是容許.
  • dumpio (bool): 是要將瀏覽器進程的輸出傳遞給process.stdout 和 process.stderr 對象,默認爲False不傳遞。
  • userDataDir (str): 用戶數據文件目錄.
  • env (dict): 以字典的形式傳遞給瀏覽器環境變量.
  • devtools (bool): 是否打開調試窗口,上面介紹headless參數是說過,默認值爲False不打開.
  • logLevel (int|str): 日誌級別,默認和 root logger 對象的級別相同.
  • autoClose (bool): 當全部操做都執行完後,是否自動關閉瀏覽器,默認True,自動關閉.
  • loop (asyncio.AbstractEventLoop): 時間循環。
  • appMode (bool): Deprecated.

打開瀏覽器操做簡單,看參數就行,很少介紹。

3.2 調整窗口大小

若是你運行了上面的代碼,你會發現,打開的頁面只在窗口左上角一小塊顯示,看着很彆扭,這是由於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())    

3.3 設置userAgent

常規操做,很少說,上代碼:

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

3.4 執行js腳本

有時候,爲了達成某些目的(例如屏蔽網站原有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。

3.5 模擬操做

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先定位,而後再模擬操做也是能夠的。下一小節中模擬登錄外賣平臺就是用這種方法,不過,這種方法要麻煩一些,不推薦。

 3.6 某電商平臺模擬登錄

我曾經用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的檢測,登陸成功,諸位能夠本身嘗試一下。

4 總結

當使用selenium+webdriver寫爬蟲被檢測到時,pyppeteer是你得不二選擇,幾乎全部能在人工操做瀏覽器進行的操做經過pyppeteer都能實現,且能完美避開官方對webdriver的檢測。pyppeteer涉及的使用方法還不少,本文只介紹了經常使用方法的很小很小一部分,須要一說的是,pyppeteer的中文資料真的不多,多看看官方文檔吧。

參考:

相關文章
相關標籤/搜索