Pyppeteer:比selenium更高效的爬蟲界的新神器

Pyppeteer:比selenium更高效的爬蟲界的新神器

當今大數據的時代,網絡爬蟲已經成為了獲取數據的一個重要手段。

隨著互聯網的發展,前端技術也在不斷變化,數據的加載方式也不再是單純的服務端渲染了。現在你可以看到很多網站的數據可能都是通過接口的形式傳輸的,或者即使不是接口那也是一些 JSON 的數據,然後經過 JavaScript 渲染得出來的。

這時,如果你還用 requests 來爬取內容,那就不管用了。因為 requests 爬取下來的只能是服務器端網頁的源碼,這和瀏覽器渲染以後的頁面內容是不一樣的。因為,真正的數據是經過 JavaScript 執行後,渲染出來的,數據來源可能是 Ajax,也可能是頁面裡的某些 Data,或者是一些 ifame 頁面等。不過,大多數情況下極有可能是 Ajax 接口獲取的。

所以,很多情況我們需要分析 Ajax請求,分析這些接口的調用方式,通過抓包工具或者瀏覽器的“開發者工具”,找到數據的請求鏈接,然後再用程序來模擬。但是,抓包分析流的方式,也存在一定的缺點。

Pyppeteer:比selenium更高效的爬蟲界的新神器

一是:因為有些接口帶著加密參數,比如 token、sign 等等,模擬難度較大;

二是:抓包的方式只適合量小的情況。如果有一百、一千個,甚至五千、一萬個網站要處理時,該如何處理?還一個一個分析數據流?一個一個去抓包嗎?

基於以上的兩個嚴重的缺點,那有沒有一種簡單粗暴的方法,既不需要分析數據流,不需要抓包,又適合大批量的網站採集呢?這時 Puppeteer、Pyppeteer、Selenium、Splash 等自動化框架出現了。使用這些框架獲取HTML源碼,這樣我們爬取到的源代碼就是JavaScript 渲染以後的真正的網頁代碼,數據自然就好提取了。同時,也就繞過分析 Ajax 和一些 JavaScript 邏輯的過程。這種方式就做到了可見即可爬,難度也不大,同時適合大批量的採集。由於是模擬瀏覽器,一些法律方面的問題可以繞過。畢竟,爬蟲有風險啊! 哈哈....

Selenium,作為一款知名的Web自動化測試框架,支持大部分主流瀏覽器,提供了功能豐富的API接口,常常被我們用作爬蟲工具來使用。然而selenium的缺點也很明顯,比如速度太慢、對版本配置要求嚴苛,最麻煩是經常要更新對應的驅動

Pyppeteer:比selenium更高效的爬蟲界的新神器

由於Selenium流行已久,現在稍微有點反爬的網站都會對selenium和webdriver進行識別,網站只需要在前端js添加一下判斷腳本,很容易就可以判斷出是真人訪問還是webdriver。雖然也可以通過中間代理的方式進行js注入屏蔽webdriver檢測,但是webdriver對瀏覽器的模擬操作(輸入、點擊等等)都會留下webdriver的標記,同樣會被識別出來,要繞過這種檢測,只有重新編譯webdriver,麻煩自不必說,難度不是一般大。

由於Selenium具有這些嚴重的缺點。pyperteer成為了爬蟲界的又一新星。相比於selenium具有異步加載、速度快、具備有界面/無界面模式、偽裝性更強不易被識別為機器人,同時可以偽裝手機平板等終端;雖然支持的瀏覽器比較單一,但在安裝配置的便利性和運行效率方面都要遠勝selenium。

Pyppeteer:比selenium更高效的爬蟲界的新神器pyppeteer無疑為防爬牆撕開了一道大口子,針對selenium的淘寶、美團、文書網等網站,目前可通過該庫使用selenium的思路繼續突破,毫不費勁。

01.Pyppeteer簡介

Pyppeteer其實是Puppeteer的Python版本,下面簡單介紹下Pyppeteer的兩大特點,chromium瀏覽器和asyncio框架:

1).chromium

Chromium是一款獨立的瀏覽器,是Google為發展自家的瀏覽器Google Chrome而開啟的計劃,相當於Chrome的實驗版,Chromium的穩定性不如Chrome但是功能更加豐富,而且更新速度很快,通常每隔數小時就有新的開發版本發佈

2).asyncio

syncio是Python的一個異步協程庫,自3.4版本引入的標準庫,直接內置了對異步IO的支持,號稱是Python最有野心的庫,官網上有非常詳細的介紹:

02.安裝與使用

1).極簡安裝

使用pip install pyppeteer命令就能完成pyppeteer庫的安裝,至於chromium瀏覽器,只需要一條pyppeteer-install命令就會自動下載對應的最新版本chromium瀏覽器到pyppeteer的默認位置。

如果不運行pyppeteer-install命令,在第一次使用pyppeteer的時候也會自動下載並安裝chromium瀏覽器,效果是一樣的。總的來說,pyppeteer比起selenium省去了driver配置的環節。

當然,出於某種原因,也可能會出現chromium自動安裝無法順利完成的情況,這時可以考慮手動安裝:首先,從下列網址中找到自己系統的對應版本,下載chromium壓縮包;

然後,將壓縮包放到pyppeteer的指定目錄下解壓縮,windows系統的默認目錄。其他系統下的默認目錄可以參照下面這幅圖:

Pyppeteer:比selenium更高效的爬蟲界的新神器

2).使用

安裝完後就來試試效果。一起來看下面這段代碼,在main函數中,先是建立一個瀏覽器對象,然後打開新的標籤頁,訪問百度主頁,對當前頁面截圖並保存為“example.png”,最後關閉瀏覽器。前文也提到過,pyppeteer是基於asyncio構建的,所以在使用的時候需要用到async/await結構

Pyppeteer:比selenium更高效的爬蟲界的新神器

現在網站或系統的開發,逐漸趨於前後端分離,這樣數據的傳入就需要通過接口的方式進行傳輸。所以Ajax、動態渲染數據採集逐漸成為常態,Pyppeteer的使用會越來越多。基於方便、便與管理的考量,需要整理Pyppeteer的工具類,提供給團隊使用,下面是我在工作中整理的一個簡單的工具類,共大家參考,由於內容有點多,大家可以去我WX(crawler-small-gun),那裡有完整的工具類。

一部分工具類代碼

import asyncio, tkinter, traceback

import base64, time, random

from pyppeteer import launch

from com.fy.utils.http.UserAgentUtils import UserAgentUtils

from com.fy.utils.hash.HashUtils import Hash_Utils

from com.fy.utils.file.FileUtils import File_Utils

class PyppeteerBrowser:

def __init__(self):

self.hash = Hash_Utils()

self.url = None

self.ua = UserAgentUtils()

#"""使用tkinter獲取屏幕大小""")--OK--

def screen_size(self):

tk = tkinter.Tk()

width = tk.winfo_screenwidth()

height = tk.winfo_screenheight()

tk.quit()

return width, height

#構造一個瀏覽器對象; --OK--; 如果需要每次初始化新的瀏覽器對象,則userDataDir路徑必須不同,否則,始終是在第一次初始化的瀏覽器對象上進行操作,且容易出異常;

async def getbrowser(self, headless=False, userDataDir=None):

'''

參數:

•ignoreHTTPSErrors(bool):是否忽略 HTTPS 錯誤。默認為 False

•headless(bool):是否在無頭模式下運行瀏覽器。默認為 True除非appMode或devtools選項True

•executablePath (str):運行 Chromium 或 Chrome 可執行文件的路徑,而不是默認捆綁的 Chromium。如果指定之後就不需要使用默認的 Chromium 了,可以指定為已有的 Chrome 或 Chromium。

•slowMo (int | float):通過傳入指定的時間,可以減緩 Pyppeteer 的一些模擬操作。 (按指定的毫秒數減慢 pyppeteer 操作。)

•args (List [str]):傳遞給瀏覽器進程的附加參數(標誌)。

•dumpio(bool):是否管道瀏覽器進程 stdout 和 stderr 進入process.stdout和process.stderr。默認為False。為 True時,可以解決chromium瀏覽器多開頁面卡死問題。

•userDataDir (str):用戶數據目錄的路徑。即用戶數據文件夾,即可以保留一些個性化配置和操作記錄。(比如登錄信息等;可以在以後打開時自動登錄;)

•env(dict):指定瀏覽器可見的環境變量。默認與 python 進程相同。

•devtools(bool):是否為每個選項卡自動打開 DevTools 面板。如果是此選項True,headless則將設置該選項 False。

•logLevel(int | str):用於打印日誌的日誌級別。默認值與根記錄器相同。

•autoClose(bool):腳本完成時自動關閉瀏覽器進程。默認為True。

•loop(asyncio.AbstractEventLoop):事件循環(實驗)。

•args:常用的有['--no-sandbox','--disable-gpu', '--disable-setuid-sandbox','--window-size=1440x900']

•dumpio: 不知道為什麼,如果不加 dumpio=True 有時會出現瀏覽器卡頓

•autoClose:默認就好,不過如果你需要保持瀏覽器狀態,可以不關閉,下次直接連接這個已存在的瀏覽器

ignoreDefaultArgs (bool): 不使用 Pyppeteer 的默認參數,如果使用了這個參數,那麼最好通過 args 參數來設定一些參數,否則可能會出現一些意想不到的問題。這個參數相對比較危險,慎用。

handleSIGINT (bool): 是否響應 SIGINT 信號,也就是可以使用 Ctrl + C 來終止瀏覽器程序,默認是 True。

handleSIGTERM (bool): 是否響應 SIGTERM 信號,一般是 kill 命令,默認是 True。

handleSIGHUP (bool): 是否響應 SIGHUP 信號,即掛起信號,比如終端退出操作,默認是 True。

launch_kwargs = {

# 控制是否為無頭模式

"headless": False,

# chrome啟動命令行參數

"args": [

# 瀏覽器代理 配合某些中間人代理使用

"--proxy-server=http://127.0.0.1:8008",

# 最大化窗口

"--start-maximized",

# 取消沙盒模式 沙盒模式下權限太小

"--no-sandbox",

# 不顯示信息欄 比如 chrome正在受到自動測試軟件的控制 ...

"--disable-infobars",

# log等級設置 在某些不是那麼完整的系統裡 如果使用默認的日誌等級 可能會出現一大堆的warning信息

"--log-level=3",

# 設置UA

"--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",

],

# 用戶數據保存目錄 這個最好也自己指定一個目錄

# 如果不指定的話,chrome會自動新建一個臨時目錄使用,在瀏覽器退出的時候會自動刪除臨時目錄

# 在刪除的時候可能會刪除失敗(不知道為什麼會出現權限問題,我用的windows) 導致瀏覽器退出失敗

# 然後chrome進程就會一直沒有退出 CPU就會狂飆到99%

"userDataDir": "",

}

'''

print("構造瀏覽器對象開始...")

args = [ "--start-maximized", '--no-sandbox', "--disable-infobars" , "--log-level=3"]

parameters = {}

if userDataDir == None:

parameters = {'headless': headless, #是否打開瀏覽器;False:打開瀏覽器;True:進程中運行;

'args': args,

'dumpio': True #'dumpio': True:解決chromium瀏覽器多開頁面卡死問題。

}

else:

parameters = {'headless': headless, #是否打開瀏覽器;False:打開瀏覽器;True:進程中運行;

'args': args,

"userDataDir": userDataDir,

'dumpio': True #'dumpio': True:解決chromium瀏覽器多開頁面卡死問題。

}

#注意:同一個用戶目錄(userDataDir)不能被兩個chrome進程使用,如果你要多開,記得分別指定用戶目錄。否則會報編碼錯誤。

self.browser = await launch(parameters)

self.page = await self.browser.newPage()#在此瀏覽器上創建新頁面並返回其對象

width, height = self.screen_size()

# 設置網頁可視區域大小

await self.page.setViewport({

"width": width,

"height": height

})


# 是否啟用JS,enabled設為False,則無渲染效果

await self.page.setJavaScriptEnabled(enabled=True)


#設置請求頭userAgent

await self.page.setUserAgent(self.ua.getheaders())

await self.preventCheckWebdriver(self.page)

print("構造瀏覽器對象完畢....", self.page)

#獲取當前操作的界面

async def getPage(self):

return self.page


#獲取當前page對象的鏈接;

async def getCurUrl(self, page):

if page == None:

page = self.page

return page.url


#打開一個新的界面;)--OK--

async def getnewpage(self):

return await self.browser.newPage()


#獲取當前操作的界面重新加載

async def reload(self):

await self.page.reload()


#當前操作界面返回

async def goBack(self):

await self.page.goBack()


#獲取當前操作的界面的URL

async def getPageUrl(self):

await self.page.url()


#打開連接;--OK--

async def open(self, url, timeout=60):

try:

if url == None:

print("當前傳入的【url】不能為空,參數錯誤!!")

self.url = url

print("打開網頁:" + (url))

self.res = await self.page.goto(url, options={'timeout':int(timeout * 1000)})#打開連接;

await asyncio.sleep(1)#強行等待3秒

status = self.res.status

curUrl = self.page.url

await self.preventCheckWebdriver(self.page)

return status, curUrl

except:return 404, None



分享到:


相關文章: