日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

第19讲:Pyppeteer 爬取实战

發布時間:2024/4/11 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 第19讲:Pyppeteer 爬取实战 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在上一課時我們了解了 Pyppeteer 的基本用法,確實我們可以發現其相比 Selenium 有很多方便之處。

本課時我們就來使用 Pyppeteer 針對之前的 Selenium 案例做一次改寫,來體會一下二者的不同之處,同時也加強一下對 Pyppeteer 的理解和掌握情況。

1.爬取目標

本課時我們要爬取的目標和之前是一樣的,還是 Selenium 的那個案例,地址為:https://dynamic2.scrape.cuiqingcai.com/,如下圖所示。

這個網站的每個詳情頁的 URL 都是帶有加密參數的,同時 Ajax 接口也都有加密參數和時效性。具體的介紹可以看下 Selenium 課時。

2.本節目標

爬取目標和那一節也是一樣的:

  • 遍歷每一頁列表頁,然后獲取每部電影詳情頁的 URL。
  • 爬取每部電影的詳情頁,然后提取其名稱、評分、類別、封面、簡介等信息。
  • 爬取到的數據存為 JSON 文件。

要求和之前也是一樣的,只不過我們這里的實現就全用 Pyppeteer 來做了。

3.準備工作

在本課時開始之前,我們需要做好如下準備工作:

  • 安裝好 Python (最低為 Python 3.6)版本,并能成功運行 Python 程序。
  • 安裝好 Pyppeteer 并能成功運行示例。

其他的瀏覽器、驅動配置就不需要了,這也是相比 Selenium 更加方便的地方。

頁面分析在這里就不多介紹了,還是列表頁 + 詳情頁的結構,具體可以參考 Selenium 那一課時的內容。

4.爬取列表頁

首先我們先做一些準備工作,定義一些基礎的配置,包括日志定義、變量等等并引入一些必要的包,代碼如下:

import logging logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(levelname)s: %(message)s') INDEX_URL = 'https://dynamic2.scrape.cuiqingcai.com/page/{page}' TIMEOUT = 10 TOTAL_PAGE = 10 WINDOW_WIDTH, WINDOW_HEIGHT = 1366, 768 HEADLESS = False

這里大多數的配置和之前是一樣的,不過這里我們額外定義了窗口的寬高信息,這里定義為 1366 x 768,你也可以隨意指定適合自己屏幕的寬高信息。另外這里定義了一個變量 HEADLESS,用來指定是否啟用 Pyppeteer 的無頭模式,如果為 False,那么啟動 Pyppeteer 的時候就會彈出一個 Chromium 瀏覽器窗口。

接著我們再定義一個初始化 Pyppeteer 的方法,包括啟動 Pyppeteer,新建一個頁面選項卡,設置窗口大小等操作,代碼實現如下:

from pyppeteer import launch browser, tab = None, None async def init():global browser, tabbrowser = await launch(headless=HEADLESS,args=['--disable-infobars',f'--window-size={WINDOW_WIDTH},{WINDOW_HEIGHT}'])tab = await browser.newPage()await tab.setViewport({'width': WINDOW_WIDTH, 'height': WINDOW_HEIGHT})

在這里我們先聲明了一個 browser 對象,代表 Pyppeteer 所用的瀏覽器對象,tab 代表新建的頁面選項卡,這里把兩項設置為全局變量,方便其他的方法調用。

另外定義了一個 init 方法,調用了 Pyppeteer 的 launch 方法,傳入了 headless 為 HEADLESS,將其設置為非無頭模式,另外還通過 args 指定了隱藏提示條并設定了窗口的寬高。

接下來我們像之前一樣,定義一個通用的爬取方法,代碼如下:

from pyppeteer.errors import TimeoutError async def scrape_page(url, selector):logging.info('scraping %s', url)try:await tab.goto(url)await tab.waitForSelector(selector, options={'timeout': TIMEOUT * 1000})except TimeoutError:logging.error('error occurred while scraping %s', url, exc_info=True)

這里我們定義了一個 scrape_page 方法,它接收兩個參數,一個是 url,代表要爬取的鏈接,使用 goto 方法調用即可;另外一個是 selector,即要等待渲染出的節點對應的 CSS 選擇器,這里我們使用 waitForSelector 方法并傳入了 selector,并通過 options 指定了最長等待時間。

這樣的話在運行時頁面會首先訪問這個 URL,然后等待某個符合 selector 的節點加載出來,最長等待 10 秒,如果 10 秒內加載出來了,那就接著往下執行,否則拋出異常,捕獲 TimeoutError 并輸出錯誤日志。

接下來,我們就實現一下爬取列表頁的方法,代碼實現如下:

async def scrape_index(page):url = INDEX_URL.format(page=page)await scrape_page(url, '.item .name')

這里我們定義了 scrape_index 方法來爬取頁面,其接受一個參數 page,代表要爬取的頁碼,這里我們首先通過 INDEX_URL 構造了列表頁的 URL,然后調用 scrape_page 方法傳入了 url 和要等待加載的選擇器。

這里的選擇器我們使用的是 .item .name,這就是列表頁中每部電影的名稱,如果這個加載出來了,那么就代表頁面加載成功了,如圖所示。

好,接下來我們可以再定義一個解析列表頁的方法,提取出每部電影的詳情頁 URL,定義如下:

async def parse_index():return await tab.querySelectorAllEval('.item .name', 'nodes => nodes.map(node => node.href)')

這里我們調用了 querySelectorAllEval 方法,它接收兩個參數,第一個參數是 selector,代表要選擇的節點對應的 CSS 選擇器;第二個參數是 pageFunction,代表的是要執行的 JavaScript 方法,這里需要傳入的是一段 JavaScript 字符串,整個方法的作用是選擇 selector 對應的節點,然后對這些節點通過 pageFunction 定義的邏輯抽取出對應的結果并返回。

所以這里第一個參數 selector 就傳入電影名稱對應的節點,其實是超鏈接 a 節點。由于提取結果有多個,所以這里 JavaScript 對應的 pageFunction 輸入參數就是 nodes,輸出結果是調用了 map 方法得到每個 node,然后調用 node 的 href 屬性即可。這樣返回結果就是當前列表頁的所有電影的詳情頁 URL 組成的列表了。

好,接下來我們來串聯調用一下看看,代碼實現如下:

import asyncio async def main():await init()try:for page in range(1, TOTAL_PAGE + 1):await scrape_index(page)detail_urls = await parse_index()logging.info('detail_urls %s', detail_urls)finally:await browser.close() if __name__ == '__main__':asyncio.get_event_loop().run_until_complete(main())

這里我們定義了一個 mian 方法,將前面定義的幾個方法串聯調用了一下。首先調用了 init 方法,然后循環遍歷頁碼,調用了 scrape_index 方法爬取了每一頁列表頁,接著我們調用了 parse_index 方法,從列表頁中提取出詳情頁的每個 URL,然后輸出結果。

運行結果如下:

2020-04-08 13:54:28,879 - INFO: scraping https://dynamic2.scrape.cuiqingcai.com/page/1 2020-04-08 13:54:31,411 - INFO: detail_urls ['https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx', ..., 'https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI5', 'https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIxMA=='] 2020-04-08 13:54:31,411 - INFO: scraping https://dynamic2.scrape.cuiqingcai.com/page/2

由于內容較多,這里省略了部分內容。

在這里可以看到,每一次的返回結果都會是當前列表頁提取出來的所有詳情頁 URL 組成的列表,我們下一步就可以用這些 URL 來接著爬取了。

5.爬取詳情頁

拿到詳情頁的 URL 之后,下一步就是爬取每一個詳情頁然后提取信息了,首先我們定義一個爬取詳情頁的方法,代碼如下:

async def scrape_detail(url):await scrape_page(url, 'h2')

代碼非常簡單,就是直接調用了 scrape_page 方法,然后傳入了要等待加載的節點的選擇器,這里我們就直接用了 h2 了,對應的就是詳情頁的電影名稱,如圖所示。

如果順利運行,那么當前 Pyppeteer 就已經成功加載出詳情頁了,下一步就是提取里面的信息了。

接下來我們再定義一個提取詳情信息的方法,代碼如下:

async def parse_detail():url = tab.urlname = await tab.querySelectorEval('h2', 'node => node.innerText')categories = await tab.querySelectorAllEval('.categories button span', 'nodes => nodes.map(node => node.innerText)')cover = await tab.querySelectorEval('.cover', 'node => node.src')score = await tab.querySelectorEval('.score', 'node => node.innerText')drama = await tab.querySelectorEval('.drama p', 'node => node.innerText')return {'url': url,'name': name,'categories': categories,'cover': cover,'score': score,'drama': drama}

這里我們定義了一個 parse_detail 方法,提取了 URL、名稱、類別、封面、分數、簡介等內容,提取方式如下:

  • URL:直接調用 tab 對象的 url 屬性即可獲取當前頁面的 URL。
  • 名稱:由于名稱只有一個節點,所以這里我們調用了 querySelectorEval 方法來提取,而不是querySelectorAllEval,第一個參數傳入 h2,提取到了名稱對應的節點,然后第二個參數傳入提取的 pageFunction,調用了 node 的 innerText 屬性提取了文本值,即電影名稱。
  • 類別:類別有多個,所以我們這里調用了 querySelectorAllEval 方法來提取,其對應的 CSS 選擇器為 .categories button span,可以選中多個類別節點。接下來還是像之前提取詳情頁 URL 一樣,pageFunction 使用 nodes 參數,然后調用 map 方法提取 node 的 innerText 就得到所有類別結果了。
  • 封面:同樣地,可以使用 CSS 選擇器 .cover 直接獲取封面對應的節點,但是由于其封面的 URL 對應的是 src 這個屬性,所以這里提取的是 src 屬性。
  • 分數:分數對應的 CSS 選擇器為 .score ,類似的原理,提取 node 的 innerText 即可。
  • 簡介:同樣可以使用 CSS 選擇器 .drama p 直接獲取簡介對應的節點,然后調用 innerText 屬性提取文本即可。

最后我們將提取結果匯總成一個字典然后返回即可。

接下來 main 方法里面,我們增加 scrape_detail 和 parse_detail 方法的調用,main 方法改寫如下:

async def main():await init()try:for page in range(1, TOTAL_PAGE + 1):await scrape_index(page)detail_urls = await parse_index()for detail_url in detail_urls:await scrape_detail(detail_url)detail_data = await parse_detail()logging.info('data %s', detail_data)finally:await browser.close()

重新看下運行結果,運行結果如下:

2020-04-08 14:12:39,564 - INFO: scraping https://dynamic2.scrape.cuiqingcai.com/page/1 2020-04-08 14:12:42,935 - INFO: scraping https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx 2020-04-08 14:12:45,781 - INFO: data {'url': 'https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx', 'name': '霸王別姬 - Farewell My Concubine', 'categories': ['劇情', '愛情'], 'cover': 'https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@464w_644h_1e_1c', 'score': '9.5', 'drama': '影片借一出《霸王別姬》的京戲,牽扯出三個人之間一段隨時代風云變幻的愛恨情仇。段小樓(張豐毅 飾)與程蝶衣(張國榮 飾)是一對打小一起長大的師兄弟,兩人一個演生,一個飾旦,一向配合天衣無縫,尤其一出《霸王別姬》,更是譽滿京城,為此,兩人約定合演一輩子《霸王別姬》。但兩人對戲劇與人生關系的理解有本質不同,段小樓深知戲非人生,程蝶衣則是人戲不分。段小樓在認為該成家立業之時迎娶了名妓菊仙(鞏俐 飾),致使程蝶衣認定菊仙是可恥的第三者,使段小樓做了叛徒,自此,三人圍繞一出《霸王別姬》生出的愛恨情仇戰開始隨著時代風云的變遷不斷升級,終釀成悲劇。'} 2020-04-08 14:12:45,782 - INFO: scraping https://dynamic2.scrape.cuiqingcai.com/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIy

這里可以看到,首先先爬取了列表頁,然后提取出了詳情頁之后接著開始爬詳情頁,然后提取出我們想要的電影信息之后,再接著去爬下一個詳情頁。

這樣,所有的詳情頁都會被我們爬取下來啦。

6.數據存儲

最后,我們再像之前一樣添加一個數據存儲的方法,為了方便,這里還是保存為 JSON 文本文件,實現如下:

import json from os import makedirs from os.path import exists RESULTS_DIR = 'results' exists(RESULTS_DIR) or makedirs(RESULTS_DIR) async def save_data(data):name = data.get('name')data_path = f'{RESULTS_DIR}/{name}.json'json.dump(data, open(data_path, 'w', encoding='utf-8'), ensure_ascii=False, indent=2)

這里原理和之前是完全相同的,但是由于這里我們使用的是 Pyppeteer,是異步調用,所以 save_data 方法前面需要加 async。

最后添加上 save_data 的調用,完整看下運行效果。

7.問題排查

在運行過程中,由于 Pyppeteer 本身實現的原因,可能連續運行 20 秒之后控制臺就會出現如下錯誤:

pyppeteer.errors.NetworkError: Protocol Error (Runtime.evaluate): Session closed. Most likely the page has been closed.

其原因是 Pyppeteer 內部使用了 Websocket,在 Websocket 客戶端發送 ping 信號 20 秒之后仍未收到 pong 應答,就會中斷連接。

問題的解決方法和詳情描述見 https://github.com/miyakogi/pyppeteer/issues/178,此時我們可以通過修改 Pyppeteer 源代碼來解決這個問題,對應的代碼修改見:https://github.com/miyakogi/pyppeteer/pull/160/files,即把 connect 方法添加 ping_interval=None, ping_timeout=None 兩個參數即可。

另外也可以復寫一下 Connection 的實現,其解決方案同樣可以在 https://github.com/miyakogi/pyppeteer/pull/160 找到,如 patch_pyppeteer 的定義。

8.無頭模式

最后如果代碼能穩定運行了,我們可以將其改為無頭模式,將 HEADLESS 修改為 True 即可,這樣在運行的時候就不會彈出瀏覽器窗口了。

9.總結

本課時我們通過實例來講解了 Pyppeteer 爬取一個完整網站的過程,從而對 Pyppeteer 的使用有進一步的掌握。
本節代碼:https://github.com/Python3WebSpider/ScrapeDynamic2。

超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生

總結

以上是生活随笔為你收集整理的第19讲:Pyppeteer 爬取实战的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。