爬蟲利器Pyppeteer的介紹和使用 爬取京東商城書籍資訊

qwer1030274531發表於2020-09-22

提起 selenium 想必大家都不陌生,作為一款知名的 Web 自動化測試框架,selenium 支援多款主流瀏覽器,提供了功能豐富的API 介面,經常被我們用作爬蟲工具來使用。但是 selenium 的缺點也很明顯,比如速度太慢、對版本配置要求嚴苛,最麻煩是經常要更新對應的驅動。還有些網頁是可以檢測到是否是使用了selenium 。並且selenium 所謂的保護機制不允許跨域 cookies 儲存以及登入的時候必須先開啟網頁然後後載入 cookies 再重新整理的方式很不友好。

今天給大家介紹另一款 web 自動化測試工具 Pyppeteer,雖然支援的瀏覽器比較單一,但在安裝配置的便利性和執行效率上相比selenium要好。介紹 Pyppeteer 之前先說一下 Puppeteer,Puppeteer 是 Google 基於 Node.js 開發的一個工具,主要是用來操縱 Chrome 瀏覽器的 API,透過 Javascript 程式碼來操縱 Chrome 瀏覽器的一些操作,用作網路爬蟲進行資料爬取、Web 程式自動測試等任務。其 API 極其完善,功能非常強大。 而 Pyppeteer 實際上是 Puppeteer 的 Python 版本的實現,但他不是 Google 開發的,是一位來自於日本的工程師依據 Puppeteer 的一些功能開發出來的非官方版本。

1. Pyppeteer介紹

Puppeteer 是 Google 基於 Node.js 開發的一個工具,有了它我們可以透過 JavaScript 來控制 Chrome 瀏覽器的一些操作,當然也可以用作網路爬蟲上,其 API 極其完善,功能非常強大,Selenium 當然同樣可以做到。而 Pyppeteer 又是什麼呢?它實際上是 Puppeteer 的 Python 版本的實現,但它不是 Google 開發的,是一位來自於日本的工程師依據 Puppeteer 的一些功能開發出來的非官方版本。在 Pyppetter 中,實際上它背後也是有一個類似 Chrome 瀏覽器的 Chromium 瀏覽器在執行一些動作進行網頁渲染,首先說下 Chrome 瀏覽器和 Chromium 瀏覽器的淵源。Chromium 是谷歌為了研發 Chrome 而啟動的專案,是完全開源的。二者基於相同的原始碼構建,Chrome 所有的新功能都會先在 Chromium 上實現,待驗證穩定後才會移植,因此 Chromium 的版本更新頻率更高,也會包含很多新的功能,但作為一款獨立的瀏覽器,Chromium 的使用者群體要小眾得多。兩款瀏覽器“同根同源”,它們有著同樣的 Logo,但配色不同,Chrome 由藍紅綠黃四種顏色組成,而 Chromium 由不同深度的藍色構成。

在這裡插入圖片描述
總而言之,兩款瀏覽器的核心是一樣的,實現方式也是一樣的,可以認為是開發版和正式版的區別,功能上基本是沒有太大區別的。

Pyppeteer 就是依賴於 Chromium 這個瀏覽器來執行的。在有了 Pyppeteer 之後,就可以免去那些煩瑣的環境配置等問題。第一次執行的時候,如果Chromium 瀏覽器沒有安裝,那麼程式會幫我們自動安裝和配置,就免去了煩瑣的環境配置等工作。另外 Pyppeteer 是基於Python 的新特性 async 實現的,所以它的一些執行也支援非同步操作,效率相對於 Selenium 來說也提高了。

2. Pyppeteer的安裝與使用

安裝:

由於 Pyppeteer 採用了 Python 的 async 機制,所以其執行要求的 Python 版本為 3.5 及以上。安裝方式很簡單,命令列pip安裝即可。

pip3 install pyppeteer1

安裝完成之後在命令列測試:

import pyppeteer1

如果沒有報錯,那就證明安裝成功了。

使用:

Pyppeteer 是一款非常高效的 web 自動化測試工具,由於 Pyppeteer 是基於 asyncio 構建的,它的所有屬性和方法幾乎都是coroutine (協程) 物件,因此在構建非同步程式的時候非常方便,天生就支援非同步執行。

測試程式碼如下:

import asynciofrom pyppeteer import launchimport randomdef screen_size():
    # 使用tkinter獲取螢幕大小
    import tkinter
    tk = tkinter.Tk()
    width = tk.winfo_screenwidth()
    height = tk.winfo_screenheight()
    tk.quit()
    return width, heightasync def main():
    # 建立一個瀏覽器物件
    browser = await launch(headless=False)
    # 開啟新的標籤頁
    page = await browser.newPage()
    # 設定網頁檢視大小
    width, height = screen_size()
    await page.setViewport(viewport={'width': width, 'height': height})
    # 訪問目標url網頁
    await page.goto('{'timeout': 5 * 1000})
    # 休眠
    await asyncio.sleep(10)
    # 對當前頁面截圖並儲存為example1.png
    await page.screenshot({'path': 'example1.png'})
    # 搜尋框輸入  python Pyppeteer爬蟲
    await page.type('#kw', 'python Pyppeteer爬蟲')
    # 點選百度一下
    await page.click('#su')
    # 休眠
    await asyncio.sleep(random.randint(1, 3))
    # 對當前頁面截圖並儲存為example2.png
    await page.screenshot({'path': 'example2.png'})
    # 關閉瀏覽器
    await browser.close()asyncio.get_event_loop().run_until_complete(main())12345678910111213141516171819202122232425262728293031323334353637383940414243

第一次使用 pyppeteer 的時候會自動下載並安裝 chromium 瀏覽器

在這裡插入圖片描述

執行效果如下: 在這裡插入圖片描述

頁面截圖:
在這裡插入圖片描述
在這裡插入圖片描述

程式成功執行,在main函式中進行的操作有,初始化一個瀏覽器物件,然後開啟新的標籤頁,設定頁面檢視大小,訪問百度主頁,對當前頁面截圖並儲存為example1.png,然後模擬在搜尋框中輸入’python Pyppeteer爬蟲’,模擬點選百度一下,跳轉到搜尋結果網頁,再對當前頁面截圖並儲存為example2.png,最後關閉瀏覽器。pyppeteer是基於asyncio 構建的,所以在使用的時候要用到 async/await 結構。

用Pyppeteer啟動瀏覽器,呼叫  launch 方法即可實現。

pyppeteer.launcher.launch(options: dict = None, **kwargs) → pyppeteer.browser.Browser1

可以看到它處於 launcher 模組中,引數沒有在宣告中特別指定,返回型別是 browser 模組中的 Browser 物件,另外檢視其原始碼發現這是一個 async 修飾的方法,所以呼叫它的時候需要使用 await。

常用引數: jiaozuo/

  • headless (bool):是否啟用 Headless 模式,即無介面模式,如果 devtools 這個引數是 True 的話,那麼該引數就會被設定為 False,否則為 True, 即預設是開啟無介面模式的
  • devtools (bool):**是否為每一個頁面自動開啟除錯工具,**預設是 False。如果這個引數設定為 True,那麼 headless 引數就會無效,會被強制設定為 False。
  • args (List[str]):在執行過程中可以傳入的額外引數。
  • userDataDir (str):即使用者資料資料夾,即可以保留一些個性化配置和操作記錄。
  • loop (asyncio.AbstractEventLoop):事件迴圈物件。
  • executablePath (str):可執行檔案的路徑,如果指定之後就不需要使用預設的 Chromium 了,可以指定為已有的 Chrome 或 Chromium。
  • env (dict):環境變數,可以透過字典形式傳入。

禁用提示條
在之前執行效果圖中,我們可以看到上面的一條提示:“Chrome 正受到自動測試軟體的控制”,不喜歡這個提示出現的話,我們可以利用 args 引數將其禁用,禁用操作如下:

browser = await launch(headless=False, args=['--disable-infobars'])1

修改網站檢測瀏覽器特徵值

只是把提示關閉了,有些網站還是會檢測到是 WebDriver,測試如下:

import asynciofrom pyppeteer import launchasync def main():
    browser = await launch(headless=False, args=['--disable-infobars'])
    page = await browser.newPage()
    await page.setUserAgent("Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5")
    await page.setViewport(viewport={'width': 1536, 'height': 768})
    await page.goto('https://intoli.com/blog/not-possible-to-block-chrome-headless/chrome-headless-test.html')
    await asyncio.sleep(25)
    await browser.close()asyncio.get_event_loop().run_until_complete(main())123456789101112131415

執行效果如下:

在這裡插入圖片描述

這說明 Pyppeteer 開啟 Chromium 照樣還是能被檢測到 WebDriver 的存在。
無論是selenium的execute_script()方法,還是pyppeteer的evaluate()方法執行下面程式碼都能臨時修改瀏覽器屬性中的webdriver屬性,當頁面重新整理或者跳轉之後該值就原形畢露。

() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }1

但是pyppeteer的最底層是封裝的puppeteer,是js庫,是和網站原始碼互動最深的方式。
在pyppeteer中提供了一個方法:evaluateOnNewDocument(),該方法是將一段js程式碼載入到頁面文件中,當發生頁面導航、頁面內嵌框架導航的時候載入的js程式碼會自動執行,那麼當頁面重新整理的時候該js也會執行,這樣就保證了修改網站的屬性持久化的目的。

await page.evaluateOnNewDocument('() =>{ Object.defineProperties(navigator,'
                                     '{ webdriver:{ get: () => false } }) }')  12

程式碼改寫如下:

import asynciofrom pyppeteer import launchasync def main():
    browser = await launch(headless=False, args=['--disable-infobars'])
    page = await browser.newPage()
    await page.setUserAgent("Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5")
    await page.evaluateOnNewDocument('() =>{ Object.defineProperties(navigator,'
                                       '{ webdriver:{ get: () => false } }) }')  
    await page.setViewport(viewport={'width': 1536, 'height': 768})
    await page.goto('https://intoli.com/blog/not-possible-to-block-chrome-headless/chrome-headless-test.html')
    await asyncio.sleep(25)
    await browser.close()asyncio.get_event_loop().run_until_complete(main())1234567891011121314151617

執行效果如下:

在這裡插入圖片描述
可以看到,繞過了 WebDriver 的檢測。

開啟無痕模式
Chrome 瀏覽器是可以開無痕模式的,它的好處就是環境比較乾淨,不與其他的瀏覽器示例共享 Cache、Cookies 等內容,其開啟方式可以透過 createIncognitoBrowserContext 方法,程式碼如下:

import asynciofrom pyppeteer import launch
width, height = 1536, 768async def main():
    browser = await launch(headless=False,
                           args=['--disable-infobars', f'--window-size={width},{height}'])
    context = await browser.createIncognitoBrowserContext()
    page = await context.newPage()
    await page.setViewport({'width': width, 'height': height})
    await page.goto(')
    await asyncio.sleep(5)
    await browser.close()asyncio.get_event_loop().run_until_complete(main())123456789101112131415161718

更多詳細使用可以參考如下文件

3. Pyppeteer爬蟲實戰 非同步爬取京東商城書籍資訊

有些網站的頁面是 JavaScript 渲染而成的,我們所看到的內容都是網頁載入後又執行了JavaScript程式碼之後才呈現出來的,因此這些資料並不存在於原始 HTML 程式碼中,而 requests 僅僅抓取的是原始 HTML 程式碼。
抓取這種型別網站的頁面資料,解決方案如下:

  • 分析網頁原始碼資料,如果資料是隱藏在 HTML 中的其他地方,以 JavaScript 變數的形式存在,直接提取就好了。
  • 分析 Ajax,很多資料可能是經過 Ajax 請求時候獲取的,所以可以分析其介面。
  • 模擬JavaScript渲染過程,直接抓取渲染後的結果。

Pyppeteer爬蟲就是用的第三種方法

import asynciofrom pyppeteer import launchimport randomimport loggingimport openpyxlimport datetime
wb = openpyxl.Workbook()sheet = wb.active
sheet.append(['book_info', 'price', 'comment', 'shop_name', 'link'])logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')start = datetime.datetime.now()semaphore = asyncio.Semaphore(5)   # 設定訊號量   防止一下開啟過多瀏覽器async def scrape_info(page):
    # 滑動到頁面底部   不然剛進入頁面只渲染出了30條資訊  一半
    await page.evaluate('window.scrollBy(0, document.body.scrollHeight)')
    # 休眠   防止爬取過快被返回首頁
    await asyncio.sleep(random.randint(2, 4))
    items = await page.xpath('//*[@id="J_goodsList"]/ul/li')
    for item in items:
        # 捕捉異常   有些沒提取到資料   報list index out of range
        try:
            book_info = await item.xpath('.//div[@class="p-name p-name-type-2"]/a/em')
            book_info = await (await book_info[0].getProperty('textContent')).jsonValue()
            price = await item.xpath('.//div[@class="p-price"]/strong/i')
            price = await (await price[0].getProperty('textContent')).jsonValue()
            comment = await item.xpath('.//div[@class="p-commit"]/strong/a')
            comment = await (await comment[0].getProperty('textContent')).jsonValue()
            shop_name = await item.xpath('.//span[@class="J_im_icon"]/a')
            shop_name = await (await shop_name[0].getProperty('textContent')).jsonValue()
            link = await item.xpath('.//div[@class="p-img"]/a')
            link = await (await link[0].getProperty('href')).jsonValue()
            logging.info({'book_info': book_info, 'price': price, 'comment': comment, 'shop_name': shop_name, 'link': link})
            sheet.append([book_info, price, comment, shop_name, link])
        except Exception as e:
            logging.info(e)async def main(i):
    # 除錯好後   headless設定為True  執行不彈出瀏覽器介面
    async with semaphore:
        browser = await launch(
            {'headless': True}
        )
        url = f'%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&wq=python%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&page={pages[i]}&s={size[i]}&click=0'
        page = await browser.newPage()
        # 設定頁面檢視大小
        await page.setViewport(viewport={'width': 1366, 'height': 768})
        # 設定請求頭
        await page.setUserAgent(
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1")
        # 超時時間  設定為6s
        await page.goto(url, options={'timeout': 6 * 1000})
        await asyncio.sleep(random.randint(1, 3))
        # 呼叫函式抓取資料
        await scrape_info(page)
        # 關閉瀏覽器
        await browser.close()if __name__ == '__main__':
    # 構造引數
    pages = [i for i in range(1, 200) if i % 2 != 0]
    size = [i * 60 + 1 for i in range(100)]
    scrape_index_tasks = [asyncio.ensure_future(main(index)) for index in range(0, 100)]
    loop = asyncio.get_event_loop()
    tasks = asyncio.gather(*scrape_index_tasks)
    loop.run_until_complete(tasks)
    wb.save('book_info.xlsx')
    delta = (datetime.datetime.now() - start).total_seconds()
    print("用時:{:.3f}s".format(delta))12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273

執行效果如下:

在這裡插入圖片描述
在這裡插入圖片描述

成功實現利用Pyppeteer爬蟲非同步爬取100頁的書籍資訊儲存到Excel,用時249.160s。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30239065/viewspace-2723031/,如需轉載,請註明出處,否則將追究法律責任。

相關文章