若是你們對 Python 爬蟲有所瞭解的話,想必你應該據說過 Selenium 這個庫,這其實是一個自動化測試工具,如今已經被普遍用於網絡爬蟲中來應對 JavaScript 渲染的頁面的抓取。html
但 Selenium 用的時候有個麻煩事,就是環境的相關配置,得安裝好相關瀏覽器,好比 Chrome、Firefox 等等,而後還要到官方網站去下載對應的驅動,最重要的還須要安裝對應的 Python Selenium 庫,確實是否是很方便,另外若是要作大規模部署的話,環境配置的一些問題也是個頭疼的事情。git
那麼本節就介紹另外一個相似的替代品,叫作 Pyppeteer。注意,是叫作 Pyppeteer,不是 Puppeteer。Puppeteer 是 Google 基於 Node.js 開發的一個工具,有了它咱們能夠經過 JavaScript 來控制 Chrome 瀏覽器的一些操做,固然也能夠用做網絡爬蟲上,其 API 極其完善,功能很是強大。而 Pyppeteer 又是什麼呢?它其實是 Puppeteer 的 Python 版本的實現,但他不是 Google 開發的,是一位來自於日本的工程師依據 Puppeteer 的一些功能開發出來的非官方版本。github
在 Pyppetter 中,實際上它背後也是有一個相似 Chrome 瀏覽器的 Chromium 瀏覽器在執行一些動做進行網頁渲染,首先說下 Chrome 瀏覽器和 Chromium 瀏覽器的淵源。web
Chromium 是谷歌爲了研發 Chrome 而啓動的項目,是徹底開源的。兩者基於相同的源代碼構建,Chrome 全部的新功能都會先在 Chromium 上實現,待驗證穩定後纔會移植,所以 Chromium 的版本更新頻率更高,也會包含不少新的功能,但做爲一款獨立的瀏覽器,Chromium 的用戶羣體要小衆得多。兩款瀏覽器「同根同源」,它們有着一樣的 Logo,但配色不一樣,Chrome 由藍紅綠黃四種顏色組成,而 Chromium 由不一樣深度的藍色構成。
總的來講,兩款瀏覽器的內核是同樣的,實現方式也是同樣的,能夠認爲是開發版和正式版的區別,功能上基本是沒有太大區別的。瀏覽器
Pyppeteer 就是依賴於 Chromium 這個瀏覽器來運行的。那麼有了 Pyppeteer 以後,咱們就能夠免去那些繁瑣的環境配置等問題。若是第一次運行的時候,Chromium 瀏覽器沒有安全,那麼程序會幫咱們自動安裝和配置,就免去了繁瑣的環境配置等工做。另外 Pyppeteer 是基於 Python 的新特性 async 實現的,因此它的一些執行也支持異步操做,效率相對於 Selenium 來講也提升了。安全
那麼下面就讓咱們來一塊兒瞭解下 Pyppeteer 的相關用法吧。網絡
首先就是安裝問題了,因爲 Pyppeteer 採用了 Python 的 async 機制,因此其運行要求的 Python 版本爲 3.5 及以上。less
安裝方式很是簡單:異步
pip3 install pyppeteer
好了,安裝完成以後咱們命令行下測試下:async
>>> import pyppeteer
若是沒有報錯,那麼就證實安裝成功了。
接下來咱們測試下基本的頁面渲染操做,這裏咱們選用的網址爲:http://quotes.toscrape.com/js/,這個頁面是 JavaScript 渲染而成的,用基本的 requests 庫請求獲得的 HTML 結果裏面是不包含頁面中所見的條目內容的。
爲了證實 requests 沒法完成正常的抓取,咱們能夠先用以下代碼來測試一下:
import requests from pyquery import PyQuery as pq url = 'http://quotes.toscrape.com/js/' response = requests.get(url) doc = pq(response.text) print('Quotes:', doc('.quote').length)
這裏首先使用 requests 來請求網頁內容,而後使用 pyquery 來解析頁面中的每個條目。觀察源碼以後咱們發現每一個條目的 class 名爲 quote,因此這裏選用了 .quote 這個 CSS 選擇器來選擇,最後輸出條目數量。
運行結果:
Quotes: 0
結果是 0,這就證實使用 requests 是沒法正常抓取到相關數據的。由於什麼?由於這個頁面是 JavaScript 渲染而成的,咱們所看到的內容都是網頁加載後又執行了 JavaScript 以後才呈現出來的,所以這些條目數據並不存在於原始 HTML 代碼中,而 requests 僅僅抓取的是原始 HTML 代碼。
好的,因此遇到這種類型的網站咱們應該怎麼辦呢?
其實答案有不少:
而 Pyppeteer 和 Selenium 就是用的第三種方法,下面咱們再用 Pyppeteer 來試試,若是用 Pyppeteer 實現如上頁面的抓取的話,代碼就能夠寫爲以下形式:
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())
運行結果:
Quotes: 10
看運行結果,這說明咱們就成功匹配出來了 class 爲 quote 的條目,總數爲 10 條,具體的內容能夠進一步使用 pyquery 解析查看。
那麼這裏面的過程發生了什麼?
實際上,Pyppeteer 整個流程就完成了瀏覽器的開啓、新建頁面、頁面加載等操做。另外 Pyppeteer 裏面進行了異步操做,因此須要配合 async/await 關鍵詞來實現。
首先, launch 方法會新建一個 Browser 對象,而後賦值給 browser,而後調用 newPage 方法至關於瀏覽器中新建了一個選項卡,同時新建了一個 Page 對象。而後 Page 對象調用了 goto 方法就至關於在瀏覽器中輸入了這個 URL,瀏覽器跳轉到了對應的頁面進行加載,加載完成以後再調用 content 方法,返回當前瀏覽器頁面的源代碼。而後進一步地,咱們用 pyquery 進行一樣地解析,就能夠獲得 JavaScript 渲染的結果了。
另外其餘的一些方法如調用 asyncio 的 get_event_loop 等方法的相關操做則屬於 Python 異步 async 相關的內容了,你們若是不熟悉能夠了解下 Python 的 async/await 的相關知識。
好,經過上面的代碼,咱們就能夠完成 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 渲染後的頁面。
pdf 方法也是相似的,只不過頁面保存格式不同,最後獲得一個多頁的 pdf 文件,樣例以下:
可見其內容也是 JavaScript 渲染後的內容,另外這個方法還能夠指定放縮大小 scale、頁碼範圍 pageRanges、寬高 width 和 height、方向 landscape 等等參數,導出定製化的 pdf 用這個方法就十分方便。
最後咱們又調用了 evaluate 方法執行了一些 JavaScript,JavaScript 傳入的是一個函數,使用 return 方法返回了網頁的寬高、像素大小比率三個值,最後獲得的是一個 JSON 格式的對象,內容以下:
{'width': 800, 'height': 600, 'deviceScaleFactor': 1}
OK,實例就先感覺到這裏,還有太多太多的功能還沒說起。
總之利用 Pyppeteer 咱們能夠控制瀏覽器執行幾乎全部動做,想要的操做和功能基本均可以實現,用它來自由地控制爬蟲固然就不在話下了。
瞭解了基本的實例以後,咱們再來梳理一下 Pyppeteer 的一些基本和經常使用操做。Pyppeteer 的幾乎全部功能都能在其官方文檔的 API Reference 裏面找到,連接爲:https://miyakogi.github.io/py...,用到哪一個方法就來這裏查詢就行了,參數沒必要死記硬背,即用即查就好。
使用 Pyppeteer 的第一步即是啓動瀏覽器,首先咱們看下怎樣啓動一個瀏覽器,其實就至關於咱們點擊桌面上的瀏覽器圖標同樣,把它開起來。用 Pyppeteer 完成一樣的操做,只須要調用 launch 方法便可。
咱們先看下 launch 方法的 API,連接爲:
https://miyakogi.github.io/py...,
其方法定義以下:
pyppeteer.launcher.launch(options: dict = None, **kwargs) → pyppeteer.browser.Browser
能夠看到它處於 launcher 模塊中,參數沒有在聲明中特別指定,返回類型是 browser 模塊中的 Browser 對象,另外觀察源碼發現這是一個 async 修飾的方法,因此調用它的時候須要使用 await。
接下來看看它的參數:
好了,知道這些參數以後,咱們能夠先試試看。
首先能夠試用下最經常使用的參數 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 界面了:
關閉 Headless 模式以後的界面
可是能夠看到這就是一個光禿禿的瀏覽器而已,看一下相關信息:
看到了,這就是 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'])
這裏就再也不寫完整代碼了,就是在 launch 方法中,args 參數經過 list 形式傳入便可,這裏使用的是 --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())
運行時候進行一下登陸,而後就會彈出滑塊,本身手動拖動一下,而後就報錯了,界面以下:
淘寶登陸失敗<
爬蟲的時候看到這界面是很讓人崩潰的吧,並且這時候咱們還發現了頁面的 bug,整個瀏覽器窗口比顯示的內容窗口要大,這個是某些頁面會出現的狀況,讓人看起來很不爽。
咱們能夠先解決一下這個顯示的 bug,須要設置下 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())
這樣整個界面就正常了:
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em; overflow-wrap: break-word !important;">正常的界面</figcaption>
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())
這裏沒加輸入用戶名密碼的代碼,固然後面能夠自行添加,下面打開以後,咱們點擊輸入用戶名密碼,而後這時候會出現一個滑動條,這裏滑動的話,就能夠經過了,如圖所示:
<figcaption style="margin: 10px 0px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; line-height: inherit; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em; overflow-wrap: break-word !important;">淘寶滑動條驗證經過</figcaption>
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 的文件夾,裏面的結構是這樣子的:
用戶文件夾
再次運行上面的代碼,這時候能夠發現如今就已是登陸狀態了,不須要再次登陸了,這樣就成功跳過了登陸的流程。固然可能時間過久了,Cookies 都過時了,那仍是須要登陸的。