pyppeteer:比 selenium 更高效的爬蟲利器


pyppeteer

 

API 接口文檔:API Reference:https://miyakogi.github.io/pyppeteer/reference.html

pyppeteer github 地址:https://github.com/miyakogi/pyppeteer

pyppeteer  英文文檔地址:https://miyakogi.github.io/pyppeteer/
pyppeteer 官方文檔 API Reference :https://miyakogi.github.io/pyppeteer/reference.html

puppeteer( Nodejs 版 selenium )快速入門:https://blog.csdn.net/freeking101/article/details/91542887

python爬蟲利器 pyppeteer(模擬瀏覽器) 實戰:https://blog.csdn.net/xiaoming0018/article/details/89841728

重點:pyppeteer使用遇到的 bug 及解決方法http://www.sanfenzui.com/pyppeteer-bug-collection.html

pyppeteer 進階技巧 ( Xvfb 配合實現 headless 效果 ):https://www.cnblogs.com/dyfblog/p/10887940.html

Python 爬蟲之pyppeteer 的使用(爬蟲、獲取cookie、截屏插件、防爬繞過):https://mohen.blog.csdn.net/article/details/107312709

爬蟲神器 Pyppeteer 的使用:https://blog.csdn.net/weixin_38819889/article/details/108684254

 

 

Pyppeteer 簡介

 

提起 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 的一些功能開發出來的非官方版本。

Pyppeteer 其實是 Puppeteer 的 Python 版本。pyppeteer 模塊看不懂就去看puppeteer文檔,pyppeteer 只是在 puppeteer之上稍微包裝了下而已 。

注意:本來 chrome 就問題多多,puppeteer 也是各種坑,加上 pyppeteer 是基於前者的改編 python 版本,也就是產生了只要前兩個有一個有 bug,那么 pyppeteer 就會原封不動的繼承下來,本來這沒什么,但是現在遇到的問題就是 pyppeteer 這個項目從2018年9月份之后幾乎沒更新過,前兩者都在不斷的更新迭代,而 pyppeteer 一直不更新,導致很多 bug 根本沒人修復。

 

下面簡單介紹下 Pyppeteer 的兩大特點:chromium 瀏覽器 和 asyncio框架:

 

1).chromium

Chromium 是一款獨立的瀏覽器,是 Google 為發展自家的瀏覽器 Google Chrome 而開啟的計划,相當於 Chrome的實驗版,且 Chromium 是完全開源的。二者基於相同的源代碼構建,Chrome 所有的新功能都會先在 Chromium 上實現,待驗證穩定后才會移植,因此 Chromium 的版本更新頻率更高,也會包含很多新的功能,但作為一款獨立的瀏覽器,Chromium 的用戶群體要小眾得多。兩款瀏覽器“同根同源”,它們有着同樣的 Logo,但配色不同,Chrome 由藍紅綠黃四種顏色組成,而 Chromium 由不同深度的藍色構成。

Pyppeteer 的 web 自動化是基於 chromium 來實現的,由於 chromium 中某些特性的關系,Pyppeteer 的安裝配置非常簡單,關於這一點稍后我們會詳細介紹。

 

2).asyncio

asyncio 是 Python 的一個異步協程庫,自3.4版本引入的標准庫,直接內置了對異步IO的支持,號稱是Python最有野心的庫,官網上有非常詳細的介紹:https://docs.python.org/3/library/asyncio.html

 

 

安裝與使用

 

由於 Pyppeteer 采用了 Python 的 async 機制,所以其運行要求的 Python 版本為 3.5 及以上

 

1).極簡安裝

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

window 下 安裝完 pyppeteer ,會在 python 安裝目錄下的 Scripts 目錄下 有 pyppeteer-install.exe 和 pyppeteer-install-script.py 兩個文件,執行 任意一個都可以安裝 chromium 瀏覽器到 pyppeteer 的默認位置。

運行 pyppeteer-install.exe :

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

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

'linux': 'https://storage.googleapis.com/chromium-browser-snapshots/Linux_x64/575458/chrome-linux.zip'
'mac': 'https://storage.googleapis.com/chromium-browser-snapshots/Mac/575458/chrome-mac.zip'
'win32': 'https://storage.googleapis.com/chromium-browser-snapshots/Win/575458/chrome-win32.zip'
'win64': 'https://storage.googleapis.com/chromium-browser-snapshots/Win_x64/575458/chrome-win32.zip'

然后,將壓縮包放到pyppeteer的指定目錄下解壓縮,windows系統的默認目錄。

其他系統下的默認目錄可以參照下面:

  • Windows: C:\Users\<username>\AppData\Local\pyppeteer
  • OS X: /Users/<username>/Library/Application Support/pyppeteer
  • Linux: /home/<username>/.local/share/pyppeteer
    • or in $XDG_DATA_HOME/pyppeteer if $XDG_DATA_HOME is defined.

Details see appdirs’s user_data_dir.

好了,安裝完成之后我們命令行下測試下:
>>> import pyppeteer
如果沒有報錯,那么就證明安裝成功了。

 

2).使用

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

程序構建的基本思路是新建 一個 browser 瀏覽器一個 頁面 page

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

  1.  
    import asyncio
  2.  
    from pyppeteer import launch
  3.  
     
  4.  
     
  5.  
    async def main():
  6.  
    browser = await launch()
  7.  
    page = await browser.newPage()
  8.  
    await page.goto( 'http://baidu.com')
  9.  
    await page.screenshot({ 'path': 'example.png'})
  10.  
    await browser.close()
  11.  
     
  12.  
     
  13.  
    asyncio.get_event_loop().run_until_complete(main())

運行上面這段代碼會發現並沒有瀏覽器彈出運行,這是因為 Pyppeteer 默認使用的是無頭瀏覽器,如果想要瀏覽器顯示,需要在launch 函數中設置參數 “headless =False”,程序運行結束后在同一目錄下會出現截取到的網頁圖片:

 

遇到的錯誤

 

  • 1)pyppeteer.errors.NetworkError: Protocol error Network.getCookies: Target close

控制訪問指定 url 之后 await page.goto(url),會遇到上面的錯誤,如果這時候使用了 sleep 之類的延時也會出現這個錯誤或者類似的 time out。 
這個問題是 puppeteer 的 bug,但是對方已經修復了,而 pyppeteer 遲遲沒更新,就只能靠自己了,搜了很多人的文章,例如:https://github.com/miyakogi/pyppeteer/issues/171 ,但是我按照這個並沒有成功。也有人增加一個函數,但調用這個參數依然沒解決問題。

  1.  
    async def scroll_page( page):
  2.  
    cur_dist = 0
  3.  
    height = await page.evaluate( "() => document.body.scrollHeight")
  4.  
    while True:
  5.  
    if cur_dist < height:
  6.  
    await page.evaluate( "window.scrollBy(0, 500);")
  7.  
    await asyncio.sleep( 0.1)
  8.  
    cur_dist += 500
  9.  
    else:
  10.  
    break

可以把 python 第三方庫 websockets 版本 7.0 改為 6.0 就可以了,親測可用。

  1.  
    pip uninstall websockets #卸載websockets
  2.  
     
  3.  
    pip install websockets== 6.0
  4.  
    或者
  5.  
    pip install websockets== 6.0 --force-reinstall #指定安裝6.0版本

 

  • 2)chromium瀏覽器多開頁面卡死問題。

解決這個問題的方法就是瀏覽器初始化的時候添加'dumpio':True。

  1.  
    # 啟動 pyppeteer 屬於內存中實現交互的模擬器
  2.  
    browser = await launch({ 'headless': False, 'args': [ '--no-sandbox'], 'dumpio': True})
  • 3)瀏覽器窗口很大,內容顯示很小。

需要設置瀏覽器顯示大小,默認就是無法正常顯示。可以看到頁面左側右側都是空白,網站內容並沒有完整鋪滿chrome.

  1.  
    # Pyppeteer 支持字典 和 關鍵字傳參,Puppeteer 只支持字典傳參。
  2.  
    # 這里使用字典傳參
  3.  
    browser = await launch(
  4.  
    {
  5.  
    'headless': False,
  6.  
    'dumpio': True,
  7.  
    'autoClose': False,
  8.  
    'args': [
  9.  
    '--no-sandbox',
  10.  
    '--window-size=1366,850'
  11.  
    ]
  12.  
    }
  13.  
    )
  14.  
    await page.setViewport({ 'width': 1366, 'height': 768})

通過上面設置Windows-size和Viewport大小來實現網頁完整顯示。

但是對於那種向下無限加載的長網頁這種情況如果瀏覽器是可見狀態會顯示不全,針對這種情況的解決方法就是復制當前網頁新開一個標簽頁粘貼進去就正常了

 

Pyppeteer 和 Puppeteer 的 不同點

  • Pyppeteer支持字典和關鍵字傳參,Puppeteer只支持字典傳參
  1.  
    # Puppeteer只支持字典傳參
  2.  
    browser = await launch({ 'headless': True})
  1.  
    # Pyppeteer支持字典和關鍵字傳參
  2.  
    browser = await launch({ 'headless': True})
  3.  
    browser = await launch(headless= True)
  • 元素選擇器方法名 $變為querySelector
  1.  
    # Puppeteer使用$符
  2.  
    Page.$()/ Page.$$()/ Page.$x()
  1.  
    # Pyppeteer使用Python風格的函數名
  2.  
    Page.querySelector()/Page.querySelectorAll()/Page.xpath()
  3.  
    # 簡寫方式為:
  4.  
    Page.J(), Page.JJ(), and Page.Jx()
  • Page.evaluate() 和 Page.querySelectorEval()的參數

Puppeteer的evaluate()方法使用JavaScript原生函數或JavaScript表達式字符串。Pyppeteer的evaluate()方法只使用JavaScript字符串,該字符串可以是函數也可以是表達式,Pyppeteer會進行自動判斷。但有時會判斷錯誤,如果字符串被判斷成了函數,並且報錯,可以添加選項force_expr=True,強制Pyppeteer作為表達式處理。

獲取頁面內容:

content = await page.evaluate('document.body.textContent', force_expr=True)
 

獲取元素的內部文字:

  1.  
    element = await page.querySelector( 'h1')
  2.  
    title = await page.evaluate( '(element) => element.textContent', element)

 

基礎用法

 

抓取內容  可以使用 xpath 表達式
"""
# Pyppeteer 三種解析方式
    Page.querySelector()      # 選擇器
    Page.querySelectorAll()
    Page.xpath()                   # xpath  表達式
# 簡寫方式為:
    Page.J(), Page.JJ(), and Page.Jx()
"""

示例 1 :

  1.  
    import asyncio
  2.  
    from pyppeteer import launch
  3.  
     
  4.  
     
  5.  
    async def main():
  6.  
    # headless參數設為False,則變成有頭模式
  7.  
    # Pyppeteer支持字典和關鍵字傳參,Puppeteer只支持字典傳參
  8.  
     
  9.  
    # 指定引擎路徑
  10.  
    # exepath = r'C:\Users\Administrator\AppData\Local\pyppeteer\pyppeteer\local-chromium\575458\chrome-win32/chrome.exe'
  11.  
    # browser = await launch({'executablePath': exepath, 'headless': False, 'slowMo': 30})
  12.  
     
  13.  
    browser = await launch(
  14.  
    # headless=False,
  15.  
    { 'headless': False}
  16.  
    )
  17.  
     
  18.  
    page = await browser.newPage()
  19.  
     
  20.  
    # 設置頁面視圖大小
  21.  
    await page.setViewport(viewport={ 'width': 1280, 'height': 800})
  22.  
     
  23.  
    # 是否啟用JS,enabled設為False,則無渲染效果
  24.  
    await page.setJavaScriptEnabled(enabled= True)
  25.  
    # 超時間見 1000 毫秒
  26.  
    res = await page.goto( 'https://www.toutiao.com/', options={ 'timeout': 1000})
  27.  
    resp_headers = res.headers # 響應頭
  28.  
    resp_status = res.status # 響應狀態
  29.  
     
  30.  
    # 等待
  31.  
    await asyncio.sleep( 2)
  32.  
    # 第二種方法,在while循環里強行查詢某元素進行等待
  33.  
    while not await page.querySelector( '.t'):
  34.  
    pass
  35.  
    # 滾動到頁面底部
  36.  
    await page.evaluate( 'window.scrollBy(0, document.body.scrollHeight)')
  37.  
     
  38.  
    await asyncio.sleep( 2)
  39.  
    # 截圖 保存圖片
  40.  
    await page.screenshot({ 'path': 'toutiao.png'})
  41.  
     
  42.  
    # 打印頁面cookies
  43.  
    print( await page.cookies())
  44.  
     
  45.  
    """ 打印頁面文本 """
  46.  
    # 獲取所有 html 內容
  47.  
    print( await page.content())
  48.  
     
  49.  
    # 在網頁上執行js 腳本
  50.  
    dimensions = await page.evaluate(pageFunction= '''() => {
  51.  
    return {
  52.  
    width: document.documentElement.clientWidth, // 頁面寬度
  53.  
    height: document.documentElement.clientHeight, // 頁面高度
  54.  
    deviceScaleFactor: window.devicePixelRatio, // 像素比 1.0000000149011612
  55.  
    }
  56.  
    }''', force_expr= False) # force_expr=False 執行的是函數
  57.  
    print(dimensions)
  58.  
     
  59.  
    # 只獲取文本 執行 js 腳本 force_expr 為 True 則執行的是表達式
  60.  
    content = await page.evaluate(pageFunction= 'document.body.textContent', force_expr= True)
  61.  
    print(content)
  62.  
     
  63.  
    # 打印當前頁標題
  64.  
    print( await page.title())
  65.  
     
  66.  
    # 抓取新聞內容 可以使用 xpath 表達式
  67.  
    """
  68.  
    # Pyppeteer 三種解析方式
  69.  
    Page.querySelector() # 選擇器
  70.  
    Page.querySelectorAll()
  71.  
    Page.xpath() # xpath 表達式
  72.  
    # 簡寫方式為:
  73.  
    Page.J(), Page.JJ(), and Page.Jx()
  74.  
    """
  75.  
    element = await page.querySelector( ".feed-infinite-wrapper > ul>li") # 紙抓取一個
  76.  
    print(element)
  77.  
    # 獲取所有文本內容 執行 js
  78.  
    content = await page.evaluate( '(element) => element.textContent', element)
  79.  
    print(content)
  80.  
     
  81.  
    # elements = await page.xpath('//div[@class="title-box"]/a')
  82.  
    elements = await page.querySelectorAll( ".title-box a")
  83.  
    for item in elements:
  84.  
    print( await item.getProperty( 'textContent'))
  85.  
    # <pyppeteer.execution_context.JSHandle object at 0x000002220E7FE518>
  86.  
     
  87.  
    # 獲取文本
  88.  
    title_str = await ( await item.getProperty( 'textContent')).jsonValue()
  89.  
     
  90.  
    # 獲取鏈接
  91.  
    title_link = await ( await item.getProperty( 'href')).jsonValue()
  92.  
    print(title_str)
  93.  
    print(title_link)
  94.  
     
  95.  
    # 關閉瀏覽器
  96.  
    await browser.close()
  97.  
     
  98.  
     
  99.  
    asyncio.get_event_loop().run_until_complete(main())

示例 2 :

  1.  
    import asyncio
  2.  
    import pyppeteer
  3.  
    from collections import namedtuple
  4.  
     
  5.  
    headers = {
  6.  
    'date': 'Sun, 28 Apr 2019 06:50:20 GMT',
  7.  
    'server': 'Cmcc',
  8.  
    'x-frame-options': 'SAMEORIGIN\nSAMEORIGIN',
  9.  
    'last-modified': 'Fri, 26 Apr 2019 09:58:09 GMT',
  10.  
    'accept-ranges': 'bytes',
  11.  
    'cache-control': 'max-age=43200',
  12.  
    'expires': 'Sun, 28 Apr 2019 18:50:20 GMT',
  13.  
    'vary': 'Accept-Encoding,User-Agent',
  14.  
    'content-encoding': 'gzip',
  15.  
    'content-length': '19823',
  16.  
    'content-type': 'text/html',
  17.  
    'connection': 'Keep-alive',
  18.  
    'via': '1.1 ID-0314217270751344 uproxy-17'
  19.  
    }
  20.  
     
  21.  
    Response = namedtuple( "rs", "title url html cookies headers history status")
  22.  
     
  23.  
     
  24.  
    async def get_html( url):
  25.  
    browser = await pyppeteer.launch(headless= True, args=[ '--no-sandbox'])
  26.  
    page = await browser.newPage()
  27.  
    res = await page.goto(url, options={ 'timeout': 10000})
  28.  
    data = await page.content()
  29.  
    title = await page.title()
  30.  
    resp_cookies = await page.cookies() # cookie
  31.  
    resp_headers = res.headers # 響應頭
  32.  
    resp_status = res.status # 響應狀態
  33.  
    print(data)
  34.  
    print(title)
  35.  
    print(resp_headers)
  36.  
    print(resp_status)
  37.  
    return title
  38.  
     
  39.  
     
  40.  
    if __name__ == '__main__':
  41.  
    url_list = [
  42.  
    "https://www.toutiao.com",
  43.  
    "http://jandan.net/ooxx/page-8#comments",
  44.  
    "https://www.12306.cn/index"
  45.  
    ]
  46.  
    task = [get_html(url) for url in url_list]
  47.  
     
  48.  
    loop = asyncio.get_event_loop()
  49.  
    results = loop.run_until_complete(asyncio.gather(*task))
  50.  
    for res in results:
  51.  
    print(res)

 

模擬輸入

模擬輸入文本:

  1.  
    # 模擬輸入 賬號密碼 {'delay': rand_int()} 為輸入時間
  2.  
    await page. type( '#TPL_username_1', "sadfasdfasdf")
  3.  
    await page. type( '#TPL_password_1', "123456789", )
  4.  
     
  5.  
    await page.waitFor( 1000)
  6.  
    await page.click( "#J_SubmitStatic")

使用 tkinter 獲取頁面高度 寬度

  1.  
    def screen_size():
  2.  
    """使用tkinter獲取屏幕大小"""
  3.  
    import tkinter
  4.  
    tk = tkinter.Tk()
  5.  
    width = tk.winfo_screenwidth()
  6.  
    height = tk.winfo_screenheight()
  7.  
    tk.quit()
  8.  
    return width, height

 

爬取京東商城

示例代碼:

  1.  
    import requests
  2.  
    from bs4 import BeautifulSoup
  3.  
    from pyppeteer import launch
  4.  
    import asyncio
  5.  
     
  6.  
     
  7.  
    def screen_size():
  8.  
    """使用tkinter獲取屏幕大小"""
  9.  
    import tkinter
  10.  
    tk = tkinter.Tk()
  11.  
    width = tk.winfo_screenwidth()
  12.  
    height = tk.winfo_screenheight()
  13.  
    tk.quit()
  14.  
    return width, height
  15.  
     
  16.  
     
  17.  
    async def main( url):
  18.  
    # browser = await launch({'headless': False, 'args': ['--no-sandbox'], })
  19.  
    browser = await launch({ 'args': [ '--no-sandbox'], })
  20.  
    page = await browser.newPage()
  21.  
    width, height = screen_size()
  22.  
    await page.setViewport(viewport={ "width": width, "height": height})
  23.  
    await page.setJavaScriptEnabled(enabled= True)
  24.  
    await page.setUserAgent(
  25.  
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
  26.  
    '(KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299'
  27.  
    )
  28.  
    await page.goto(url)
  29.  
     
  30.  
    # await asyncio.sleep(2)
  31.  
     
  32.  
    await page.evaluate( 'window.scrollBy(0, document.body.scrollHeight)')
  33.  
     
  34.  
    await asyncio.sleep( 1)
  35.  
     
  36.  
    # content = await page.content()
  37.  
    li_list = await page.xpath( '//*[@id="J_goodsList"]/ul/li')
  38.  
     
  39.  
    # print(li_list)
  40.  
    item_list = []
  41.  
    for li in li_list:
  42.  
    a = await li.xpath( './/div[@class="p-img"]/a')
  43.  
    detail_url = await ( await a[ 0].getProperty( "href")).jsonValue()
  44.  
    promo_words = await ( await a[ 0].getProperty( "title")).jsonValue()
  45.  
    a_ = await li.xpath( './/div[@class="p-commit"]/strong/a')
  46.  
    p_commit = await ( await a_[ 0].getProperty( "textContent")).jsonValue()
  47.  
    i = await li.xpath( './div/div[3]/strong/i')
  48.  
    price = await ( await i[ 0].getProperty( "textContent")).jsonValue()
  49.  
    em = await li.xpath( './div/div[4]/a/em')
  50.  
    title = await ( await em[ 0].getProperty( "textContent")).jsonValue()
  51.  
    item = {
  52.  
    "title": title,
  53.  
    "detail_url": detail_url,
  54.  
    "promo_words": promo_words,
  55.  
    'p_commit': p_commit,
  56.  
    'price': price
  57.  
    }
  58.  
    item_list.append(item)
  59.  
    # print(item)
  60.  
    # break
  61.  
    # print(content)
  62.  
     
  63.  
    await page_close(browser)
  64.  
    return item_list
  65.  
     
  66.  
     
  67.  
    async def page_close( browser):
  68.  
    for _page in await browser.pages():
  69.  
    await _page.close()
  70.  
    await browser.close()
  71.  
     
  72.  
     
  73.  
    msg = "手機"
  74.  
    url = "https://search.jd.com/Search?keyword={}&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq={}&cid2=653&cid3=655&page={}"
  75.  
     
  76.  
    task_list = []
  77.  
    for i in range( 1, 6):
  78.  
    page = i * 2 - 1
  79.  
    url = url. format(msg, msg, page)
  80.  
    task_list.append(main(url))
  81.  
     
  82.  
    loop = asyncio.get_event_loop()
  83.  
    results = loop.run_until_complete(asyncio.gather(*task_list))
  84.  
    # print(results, len(results))
  85.  
    for i in results:
  86.  
    print(i, len(i))
  87.  
     
  88.  
    print( '*' * 100)
  89.  
    # soup = BeautifulSoup(content, 'lxml')
  90.  
    # div = soup.find('div', id='J_goodsList')
  91.  
    # for i, li in enumerate(div.find_all('li', class_='gl-item')):
  92.  
    # if li.select('.p-img a'):
  93.  
    # print(li.select('.p-img a')[0]['href'], i)
  94.  
    # print(li.select('.p-price i')[0].get_text(), i)
  95.  
    # print(li.select('.p-name em')[0].text, i)
  96.  
    # else:
  97.  
    # print("#" * 200)
  98.  
    # print(li)

 

抓取淘寶

示例代碼:

  1.  
    # -*- coding: utf-8 -*-
  2.  
     
  3.  
    import time
  4.  
    import random
  5.  
    import asyncio
  6.  
    from retrying import retry # 錯誤自動重試
  7.  
    from pyppeteer.launcher import launch
  8.  
     
  9.  
     
  10.  
    js1 = '''() =>{Object.defineProperties(navigator,{ webdriver:{ get: () => false}})}'''
  11.  
    js2 = '''() => {alert(window.navigator.webdriver)}'''
  12.  
    js3 = '''() => {window.navigator.chrome = {runtime: {}, }; }'''
  13.  
    js4 = '''() =>{Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});}'''
  14.  
    js5 = '''() =>{Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5,6],});}'''
  15.  
     
  16.  
     
  17.  
    def retry_if_result_none( result):
  18.  
    return result is None
  19.  
     
  20.  
     
  21.  
    @retry(retry_on_result=retry_if_result_none, )
  22.  
    async def mouse_slide( page=None):
  23.  
    await asyncio.sleep( 3)
  24.  
    try:
  25.  
    await page.hover( '#nc_1_n1z')
  26.  
    await page.mouse.down()
  27.  
    await page.mouse.move( 2000, 0, { 'delay': random.randint( 1000, 2000)})
  28.  
    await page.mouse.up()
  29.  
     
  30.  
    except Exception as e:
  31.  
    print(e, ' :slide login False')
  32.  
    return None
  33.  
    else:
  34.  
    await asyncio.sleep( 3)
  35.  
    slider_again = await page.Jeval( '.nc-lang-cnt', 'node => node.textContent')
  36.  
    if slider_again != '驗證通過':
  37.  
    return None
  38.  
    else:
  39.  
    await page.screenshot({ 'path': './headless-slide-result.png'})
  40.  
    print( '驗證通過')
  41.  
    return 1
  42.  
     
  43.  
     
  44.  
    def input_time_random():
  45.  
    return random.randint( 100, 151)
  46.  
     
  47.  
     
  48.  
    def screen_size():
  49.  
    """使用tkinter獲取屏幕大小"""
  50.  
    import tkinter
  51.  
    tk = tkinter.Tk()
  52.  
    width = tk.winfo_screenwidth()
  53.  
    height = tk.winfo_screenheight()
  54.  
    tk.quit()
  55.  
    return width, height
  56.  
     
  57.  
     
  58.  
    async def main( username, pwd, url):
  59.  
    browser = await launch(
  60.  
    { 'headless': False, 'args': [ '--no-sandbox'], },
  61.  
    userDataDir= './userdata',
  62.  
    args=[ '--window-size=1366,768']
  63.  
    )
  64.  
    page = await browser.newPage()
  65.  
    width, height = screen_size()
  66.  
    await page.setViewport(viewport={ "width": width, "height": height})
  67.  
    await page.setUserAgent(
  68.  
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
  69.  
    '(KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299'
  70.  
    )
  71.  
     
  72.  
    await page.goto(url)
  73.  
    await page.evaluate(js1)
  74.  
    await page.evaluate(js3)
  75.  
    await page.evaluate(js4)
  76.  
    await page.evaluate(js5)
  77.  
     
  78.  
    pwd_login = await page.querySelector( '.J_Quick2Static')
  79.  
    # print(await (await pwd_login.getProperty('textContent')).jsonValue())
  80.  
    await pwd_login.click()
  81.  
     
  82.  
    await page. type( '#TPL_username_1', username, { 'delay': input_time_random() - 50})
  83.  
    await page. type( '#TPL_password_1', pwd, { 'delay': input_time_random()})
  84.  
     
  85.  
    await page.screenshot({ 'path': './headless-test-result.png'})
  86.  
    time.sleep( 2)
  87.  
     
  88.  
    slider = await page.Jeval( '#nocaptcha', 'node => node.style') # 是否有滑塊
  89.  
     
  90.  
    if slider:
  91.  
    print( '出現滑塊情況判定')
  92.  
    await page.screenshot({ 'path': './headless-login-slide.png'})
  93.  
    flag = await mouse_slide(page=page)
  94.  
    if flag:
  95.  
    print(page.url)
  96.  
    await page.keyboard.press( 'Enter')
  97.  
    await get_cookie(page)
  98.  
    else:
  99.  
    await page.keyboard.press( 'Enter')
  100.  
    await page.waitFor( 20)
  101.  
    await page.waitForNavigation()
  102.  
    try:
  103.  
    global error
  104.  
    error = await page.Jeval( '.error', 'node => node.textContent')
  105.  
    except Exception as e:
  106.  
    error = None
  107.  
    print(e, "錯啦")
  108.  
    finally:
  109.  
    if error:
  110.  
    print( '確保賬戶安全重新入輸入')
  111.  
    else:
  112.  
    print(page.url)
  113.  
    # 可繼續網頁跳轉 已經攜帶 cookie
  114.  
    # await get_search(page)
  115.  
    await get_cookie(page)
  116.  
    await page_close(browser)
  117.  
     
  118.  
     
  119.  
    async def page_close( browser):
  120.  
    for _page in await browser.pages():
  121.  
    await _page.close()
  122.  
    await browser.close()
  123.  
     
  124.  
     
  125.  
    async def get_search( page):
  126.  
    # https://s.taobao.com/search?q={查詢的條件}&p4ppushleft=1%2C48&s={每頁 44 條 第一頁 0 第二頁 44}&sort=sale-desc
  127.  
    await page.goto( "https://s.taobao.com/search?q=氣球")
  128.  
     
  129.  
    await asyncio.sleep( 5)
  130.  
    # print(await page.content())
  131.  
     
  132.  
     
  133.  
    # 獲取登錄后cookie
  134.  
    async def get_cookie( page):
  135.  
    res = await page.content()
  136.  
    cookies_list = await page.cookies()
  137.  
    cookies = ''
  138.  
    for cookie in cookies_list:
  139.  
    str_cookie = '{0}={1};'
  140.  
    str_cookie = str_cookie. format(cookie.get( 'name'), cookie.get( 'value'))
  141.  
    cookies += str_cookie
  142.  
    print(cookies)
  143.  
    # 將cookie 放入 cookie 池 以便多次請求 封賬號 利用cookie 對搜索內容進行爬取
  144.  
    return cookies
  145.  
     
  146.  
     
  147.  
    if __name__ == '__main__':
  148.  
    tb_username = '淘寶用戶名'
  149.  
    tb_pwd = '淘寶密碼'
  150.  
    tb_url = "https://login.taobao.com/member/login.jhtml"
  151.  
     
  152.  
    loop = asyncio.get_event_loop()
  153.  
    loop.run_until_complete(main(tb_username, tb_pwd, tb_url))

 

利用 上面 獲取到的 cookie 爬取搜索內容

示例代碼:

  1.  
    import json
  2.  
    import requests
  3.  
    import re
  4.  
     
  5.  
    # 設置 cookie 池 隨機發送請求 通過 pyppeteer 獲取 cookie
  6.  
    cookie = '_tb_token_=edd7e354dee53;t=fed8f4ca1946ca1e73223cfae04bc589;sg=20f;cna=2uJSFdQGmDMCAbfFWXWAC4Jv;cookie2=1db6cd63ad358170ea13319f7a862c33;_l_g_=Ug%3D%3D;v=0;unb=3150916610;skt=49cbfd5e01d1b550;cookie1=BxVRmD3sh19TaAU6lH88bHw5oq%2BgcAGcRe229Hj5DTA%3D;csg=cf45a9e2;uc3=vt3=F8dByEazRMnQZDe%2F9qI%3D&id2=UNGTqfZ61Z3rsA%3D%3D&nk2=oicxO%2BHX4Pg%3D&lg2=U%2BGCWk%2F75gdr5Q%3D%3D;existShop=MTU1Njg3MDM3MA%3D%3D;tracknick=%5Cu7433150322;lgc=%5Cu7433150322;_cc_=V32FPkk%2Fhw%3D%3D;mt=ci=86_1;dnk=%5Cu7433150322;_nk_=%5Cu7433150322;cookie17=UNGTqfZ61Z3rsA%3D%3D;tg=0;enc=tThHs6Sn3BAl8v1fu3J4tMpgzA1n%2BLzxjib0vDAtGsXJCb4hqQZ7Z9fHIzsN0WghdcKEsoeKz6mBwPUpyzLOZw%3D%3D;JSESSIONID=B3F383B3467EC60F8CA425935232D395;l=bBMspAhrveV5732DBOCanurza77OSIRYYuPzaNbMi_5pm6T_G4QOlC03xF96VjfRswYBqh6Mygv9-etuZ;hng=CN%7Czh-CN%7CCNY%7C156;isg=BLi41Q8PENDal3xUVsA-aPbfiWaKiRzB6vcTu_IpBPOmDVj3mjHsO86vxUQYW9SD;uc1=cookie16=W5iHLLyFPlMGbLDwA%2BdvAGZqLg%3D%3D&cookie21=W5iHLLyFeYZ1WM9hVnmS&cookie15=UIHiLt3xD8xYTw%3D%3D&existShop=false&pas=0&cookie14=UoTZ4ttqLhxJww%3D%3D&tag=8&lng=zh_CN;thw=cn;x=e%3D1%26p%3D*%26s%3D0%26c%3D0%26f%3D0%26g%3D0%26t%3D0;swfstore=34617;'
  7.  
     
  8.  
    headers = {
  9.  
    'cookie': cookie,
  10.  
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"
  11.  
    }
  12.  
     
  13.  
    rep = requests.get( 'https://s.taobao.com/search?q=手機&p4ppushleft=1%2C48&s=0&sort=sale-desc ', headers=headers)
  14.  
    rep.encoding = 'utf-8'
  15.  
    res = rep.text
  16.  
    print(res)
  17.  
     
  18.  
    r = re. compile( r'g_page_config = (.*?)g_srp_loadCss', re.S)
  19.  
    res = r.findall(res)
  20.  
     
  21.  
    data = res[ 0].strip().rstrip( ';')
  22.  
    dic_data = json.loads(data)
  23.  
    auctions = dic_data.get( 'mods')[ 'itemlist'][ 'data'][ 'auctions']
  24.  
     
  25.  
    # print(auctions,len(auctions))
  26.  
    for item in auctions[ 1:]:
  27.  
    print(item)
  28.  
    break

 

針對iframe 的操作

  • page.frames 獲取所有的 iframe 列表 需要判斷操作的是哪一個 iframe 跟操作 page 一樣操作
  1.  
    from pyppeteer import launch
  2.  
    import asyncio
  3.  
     
  4.  
     
  5.  
    async def main( url):
  6.  
    w = await launch({ 'headless': False, 'args': [ '--no-sandbox'], })
  7.  
     
  8.  
    page = await w.newPage()
  9.  
    await page.setViewport({ "width": 1366, 'height': 800})
  10.  
    await page.goto(url)
  11.  
    try:
  12.  
    await asyncio.sleep( 1)
  13.  
     
  14.  
    frame = page.frames
  15.  
    print(frame) # 需要找到是哪一個 frame
  16.  
    title = await frame[ 1].title()
  17.  
    print(title)
  18.  
    await asyncio.sleep( 1)
  19.  
    login = await frame[ 1].querySelector( '#switcher_plogin')
  20.  
    print(login)
  21.  
    await login.click()
  22.  
     
  23.  
    await asyncio.sleep( 20)
  24.  
    except Exception as e:
  25.  
    print(e, "EEEEEEEEE")
  26.  
     
  27.  
    for _page in await w.pages():
  28.  
    await _page.close()
  29.  
    await w.close()
  30.  
     
  31.  
     
  32.  
    asyncio.get_event_loop().run_until_complete(main( "https://i.qq.com/?rd=1"))
  33.  
    # asyncio.get_event_loop().run_until_complete(main("https://www.gushici.com/"))

 

與 scrapy 的整合

加入downloadmiddleware

  1.  
    from scrapy import signals
  2.  
    from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
  3.  
    import random
  4.  
    import pyppeteer
  5.  
    import asyncio
  6.  
    import os
  7.  
    from scrapy.http import HtmlResponse
  8.  
     
  9.  
    pyppeteer.DEBUG = False
  10.  
     
  11.  
    class FundscrapyDownloaderMiddleware( object):
  12.  
    # Not all methods need to be defined. If a method is not defined,
  13.  
    # scrapy acts as if the downloader middleware does not modify the
  14.  
    # passed objects.
  15.  
    def __init__(self) :
  16.  
    print( "Init downloaderMiddleware use pypputeer.")
  17.  
    os.environ[ 'PYPPETEER_CHROMIUM_REVISION'] = '588429'
  18.  
    # pyppeteer.DEBUG = False
  19.  
    print(os.environ.get( 'PYPPETEER_CHROMIUM_REVISION'))
  20.  
    loop = asyncio.get_event_loop()
  21.  
    task = asyncio.ensure_future(self.getbrowser())
  22.  
    loop.run_until_complete(task)
  23.  
     
  24.  
    #self.browser = task.result()
  25.  
    print(self.browser)
  26.  
    print(self.page)
  27.  
    # self.page = await browser.newPage()
  28.  
    async def getbrowser( self):
  29.  
    self.browser = await pyppeteer.launch()
  30.  
    self.page = await self.browser.newPage()
  31.  
    # return await pyppeteer.launch()
  32.  
    async def getnewpage( self):
  33.  
    return await self.browser.newPage()
  34.  
     
  35.  
    @classmethod
  36.  
    def from_crawler( cls, crawler):
  37.  
    # This method is used by Scrapy to create your spiders.
  38.  
    s = cls()
  39.  
    crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
  40.  
    return s
  41.  
     
  42.  
    def process_request( self, request, spider):
  43.  
    # Called for each request that goes through the downloader
  44.  
    # middleware.
  45.  
     
  46.  
    # Must either:
  47.  
    # - return None: continue processing this request
  48.  
    # - or return a Response object
  49.  
    # - or return a Request object
  50.  
    # - or raise IgnoreRequest: process_exception() methods of
  51.  
    # installed downloader middleware will be called
  52.  
    loop = asyncio.get_event_loop()
  53.  
    task = asyncio.ensure_future(self.usePypuppeteer(request))
  54.  
    loop.run_until_complete(task)
  55.  
    # return task.result()
  56.  
    return HtmlResponse(url=request.url, body=task.result(), encoding= "utf-8",request=request)
  57.  
     
  58.  
    async def usePypuppeteer( self, request):
  59.  
    print(request.url)
  60.  
    # page = await self.browser.newPage()
  61.  
    await self.page.goto(request.url)
  62.  
    content = await self.page.content()
  63.  
    return content
  64.  
     
  65.  
    def process_response( self, request, response, spider):
  66.  
    # Called with the response returned from the downloader.
  67.  
     
  68.  
    # Must either;
  69.  
    # - return a Response object
  70.  
    # - return a Request object
  71.  
    # - or raise IgnoreRequest
  72.  
    return response
  73.  
     
  74.  
    def process_exception( self, request, exception, spider):
  75.  
    # Called when a download handler or a process_request()
  76.  
    # (from other downloader middleware) raises an exception.
  77.  
     
  78.  
    # Must either:
  79.  
    # - return None: continue processing this exception
  80.  
    # - return a Response object: stops process_exception() chain
  81.  
    # - return a Request object: stops process_exception() chain
  82.  
    pass
  83.  
     
  84.  
    def spider_opened( self, spider):
  85.  
    spider.logger.info( 'Spider opened: %s' % spider.name)

 

 

實戰 異步爬取

 

示例 1 :快速上手

接下來我們測試下基本的頁面渲染操作,這里我們選用的網址為:http://quotes.toscrape.com/js/,這個頁面是 JavaScript 渲染而成的,用基本的 requests 庫請求得到的 HTML 結果里面是不包含頁面中所見的條目內容的。

為了證明 requests 無法完成正常的抓取,我們可以先用如下代碼來測試一下:

  1.  
    import requests
  2.  
    from pyquery import PyQuery as pq
  3.  
     
  4.  
    url = 'http://quotes.toscrape.com/js/'
  5.  
    response = requests.get(url=url)
  6.  
    doc = pq(response.text)
  7.  
    print( 'Quotes : {0}'. format(doc( '.quote').length))
  8.  
     
  9.  
    # 結果
  10.  
    # Quotes : 0

這里首先使用 requests 來請求網頁內容,然后使用 pyquery 來解析頁面中的每一個條目。觀察源碼之后我們發現每個條目的 class 名為 quote,所以這里選用了 .quote 這個 CSS 選擇器來選擇,最后輸出條目數量。

運行結果:Quotes: 0

結果是 0,這就證明使用 requests 是無法正常抓取到相關數據的。

為什么?

因為這個頁面是 JavaScript 渲染而成的,我們所看到的內容都是網頁加載后又執行了 JavaScript 之后才呈現出來的,因此這些條目數據並不存在於原始 HTML 代碼中,而 requests 僅僅抓取的是原始 HTML 代碼。

好的,所以遇到這種類型的網站我們應該怎么辦呢?

其實答案有很多:

  1. 分析網頁源代碼數據,如果數據是隱藏在 HTML 中的其他地方,以 JavaScript 變量的形式存在,直接提取就好了。
  2. 分析 Ajax,很多數據可能是經過 Ajax 請求時候獲取的,所以可以分析其接口。
  3. 模擬 JavaScript 渲染過程,直接抓取渲染后的結果。

而 Pyppeteer 和 Selenium 就是用的第三種方法,下面我們再用 Pyppeteer 來試試,如果用 Pyppeteer 實現如上頁面的抓取的話,代碼就可以寫為如下形式:

  1.  
    import asyncio
  2.  
    from pyppeteer import launch
  3.  
    from pyquery import PyQuery as pq
  4.  
     
  5.  
     
  6.  
    async def main():
  7.  
    browser = await launch()
  8.  
    page = await browser.newPage()
  9.  
    url = 'http://quotes.toscrape.com/js/'
  10.  
    await page.goto(url=url)
  11.  
    doc = pq( await page.content())
  12.  
    print( 'Quotes : {0}'. format(doc( '.quote').length))
  13.  
    await browser.close()
  14.  
     
  15.  
    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 渲染頁面的爬取了。

 

模擬網頁截圖,保存 PDF,執行自定義的 JavaScript 獲得特定的內容

接下來我們再看看另外一個例子,這個例子可以模擬網頁截圖,保存 PDF,另外還可以執行自定義的 JavaScript 獲得特定的內容,代碼如下:

  1.  
    import asyncio
  2.  
    from pyppeteer import launch
  3.  
     
  4.  
     
  5.  
    async def main():
  6.  
    browser = await launch()
  7.  
    page = await browser.newPage()
  8.  
    url = 'http://quotes.toscrape.com/js/'
  9.  
    await page.goto(url=url)
  10.  
    await page.screenshot(path= 'test_screenshot.png')
  11.  
    await page.pdf(path= 'test_pdf.pdf')
  12.  
     
  13.  
    # 在網頁上執行js 腳本
  14.  
    dimensions = await page.evaluate(pageFunction= '''() => {
  15.  
    return {
  16.  
    width: document.documentElement.clientWidth, // 頁面寬度
  17.  
    height: document.documentElement.clientHeight, // 頁面高度
  18.  
    deviceScaleFactor: window.devicePixelRatio, // 像素比 1.0000000149011612
  19.  
    }
  20.  
    }''', force_expr= False) # force_expr=False 執行的是函數
  21.  
     
  22.  
    print(dimensions)
  23.  
    await browser.close()
  24.  
     
  25.  
     
  26.  
    asyncio.get_event_loop().run_until_complete(main())
  27.  
     
  28.  
    # 結果
  29.  
    # {'width': 800, 'height': 600, 'deviceScaleFactor': 1}

這里我們又用到了幾個新的 API,完成了網頁截圖保存、網頁導出 PDF 保存、執行 JavaScript 並返回對應數據。

 evaluate 方法執行了一些 JavaScript,JavaScript 傳入的是一個函數,使用 return 方法返回了網頁的寬高、像素大小比率三個值,最后得到的是一個 JSON 格式的對象。

總之利用 Pyppeteer 我們可以控制瀏覽器執行幾乎所有動作,想要的操作和功能基本都可以實現,用它來自由地控制爬蟲當然就不在話下了。

了解了基本的實例之后,我們再來梳理一下 Pyppeteer 的一些基本和常用操作。Pyppeteer 的幾乎所有功能都能在其官方文檔的 API Reference 里面找到,鏈接為:https://miyakogi.github.io/pyppeteer/reference.html,用到哪個方法就來這里查詢就好了,參數不必死記硬背,即用即查就好。

 

登錄淘寶 (打開網頁后,手動輸入用戶名和密碼,可以看到正常跳轉到登錄后的頁面):

  1.  
    import asyncio
  2.  
    from pyppeteer import launch
  3.  
     
  4.  
    width, height = 1366, 768
  5.  
     
  6.  
     
  7.  
    js1 = '''() =>{Object.defineProperties(navigator,{ webdriver:{ get: () => false}})}'''
  8.  
    js2 = '''() => {alert(window.navigator.webdriver)}'''
  9.  
    js3 = '''() => {window.navigator.chrome = {runtime: {}, }; }'''
  10.  
    js4 = '''() =>{Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});}'''
  11.  
    js5 = '''() =>{Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5,6],});}'''
  12.  
     
  13.  
     
  14.  
    async def page_evaluate( page):
  15.  
    # 替換淘寶在檢測瀏覽時采集的一些參數
  16.  
    # 需要注意,在測試的過程中發現登陸成功后頁面的該屬性又會變成True
  17.  
    # 所以在每次重新加載頁面后要重新設置該屬性的值。
  18.  
    await page.evaluate( '''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')
  19.  
    await page.evaluate( '''() =>{ window.navigator.chrome = { runtime: {}, }; }''')
  20.  
    await page.evaluate( '''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }''')
  21.  
    await page.evaluate( '''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''')
  22.  
     
  23.  
     
  24.  
    async def main():
  25.  
    browser = await launch(
  26.  
    headless= False,
  27.  
    # userDataDir='./userdata',
  28.  
    args=[ '--disable-infobars', f'--window-size={width},{height}', '--no-sandbox']
  29.  
    )
  30.  
    page = await browser.newPage()
  31.  
     
  32.  
    await page.setViewport(
  33.  
    {
  34.  
    "width": width,
  35.  
    "height": height
  36.  
    }
  37.  
    )
  38.  
    url = 'https://www.taobao.com'
  39.  
    await page.goto(url=url)
  40.  
     
  41.  
    # await page.evaluate(js1)
  42.  
    # await page.evaluate(js3)
  43.  
    # await page.evaluate(js4)
  44.  
    # await page.evaluate(js5)
  45.  
     
  46.  
    await page_evaluate(page)
  47.  
     
  48.  
    await asyncio.sleep( 100)
  49.  
    # await browser.close()
  50.  
     
  51.  
    asyncio.get_event_loop().run_until_complete(main())

如果把上面 js 去掉,發現淘寶可以檢測出來, 跳轉不到登錄后的頁面。

window.navigator 對象包含有關訪問者瀏覽器的信息:https://www.runoob.com/js/js-window-navigator.html

js 主要需要修改瀏覽器的 window.navigator.webdriver、window.navigator.languages等值。

打開正常的瀏覽器可以看到:

window.navigator.webdriver的值為undefined,而通過pyppeteer控制打開的瀏覽器該值為True,當被檢測到該值為True的時候,則滑動會一直失敗,所以我們需要修改該屬性。需要注意,在測試的過程中發現登陸成功后頁面的該屬性又會變成True,所以在每次重新加載頁面后要重新設置該屬性的值。

  1.  
    async def page_evaluate( page):
  2.  
    # 替換淘寶在檢測瀏覽時采集的一些參數
  3.  
    await page.evaluate( '''() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => false } }) }''')
  4.  
    await page.evaluate( '''() =>{ window.navigator.chrome = { runtime: {}, }; }''')
  5.  
    await page.evaluate( '''() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }''')
  6.  
    await page.evaluate( '''() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }''')

 

另一種方法可以進一步免去淘寶登錄的煩惱,那就是設置用戶目錄。

平時我們已經注意到,當我們登錄淘寶之后,如果下次再次打開瀏覽器發現還是登錄的狀態。這是因為淘寶的一些關鍵 Cookies 已經保存到本地了,下次登錄的時候可以直接讀取並保持登錄狀態。

那么這些信息保存在哪里了呢?其實就是保存在用戶目錄下了,里面不僅包含了瀏覽器的基本配置信息,還有一些 Cache、Cookies 等各種信息都在里面,如果我們能在瀏覽器啟動的時候讀取這些信息,那么啟動的時候就可以恢復一些歷史記錄甚至一些登錄狀態信息了。

這也就解決了一個問題:很多朋友在每次啟動 Selenium 或 Pyppeteer 的時候總是是一個全新的瀏覽器,那就是沒有設置用戶目錄,如果設置了它,每次打開就不再是一個全新的瀏覽器了,它可以恢復之前的歷史記錄,也可以恢復很多網站的登錄信息。

當然可能時間太久了,Cookies 都過期了,那還是需要登錄的。

那么這個怎么來做呢?很簡單,在啟動的時候設置 userDataDir 就好了,示例如下:

  1.  
    browser = await launch(
  2.  
    headless= False,
  3.  
    userDataDir= './userdata',
  4.  
    args=[ '--disable-infobars', f'--window-size={width},{height}']
  5.  
    )

好,這里就是加了一個 userDataDir 的屬性,值為 userdata,即當前目錄的 userdata 文件夾。我們可以首先運行一下,然后登錄一次淘寶,這時候我們同時可以觀察到在當前運行目錄下又多了一個 userdata 的文件夾:

用戶文件夾

具體的介紹可以看官方的一些說明,如:https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md 這里面介紹了 userdatadir 的相關內容。

 

命令行啟動 chrome 並進入指定的 URL:chrome.exe --disable-infobars --user-data-dir="./userdatadir" --new-window https://login.taobao.com/member/login.jhtml

執行完后會打開 淘寶的登錄頁面,登錄淘寶,然后保存用戶名密碼,這樣登錄信息就保存在 userdatadir 目錄下了

在執行 chrome.exe --disable-infobars --user-data-dir="./userdatadir" --new-window https://www.taobao.com

可以看到已經時登錄狀態了。

 

 

 

示例 2 :爬取今日頭條

  1.  
    # -*- coding: utf-8 -*-
  2.  
    # @Author :
  3.  
    # @File : toutiao.py
  4.  
    # @Software: PyCharm
  5.  
    # @description : XXX
  6.  
     
  7.  
     
  8.  
    import asyncio
  9.  
    from pyppeteer import launch
  10.  
    from pyquery import PyQuery as pq
  11.  
     
  12.  
     
  13.  
    def screen_size():
  14.  
    """使用tkinter獲取屏幕大小"""
  15.  
    import tkinter
  16.  
    tk = tkinter.Tk()
  17.  
    width = tk.winfo_screenwidth()
  18.  
    height = tk.winfo_screenheight()
  19.  
    tk.quit()
  20.  
    return width, height
  21.  
     
  22.  
     
  23.  
    async def main():
  24.  
    width, height = screen_size()
  25.  
    print( f'screen : [ width:{width} , height:{height} ]')
  26.  
     
  27.  
    browser = await launch(headless= False, args=[ f'--window-size={width},{height}'])
  28.  
    page = await browser.newPage()
  29.  
    await page.setViewport({ 'width': width, 'height': height})
  30.  
     
  31.  
    # 是否啟用JS,enabled設為False,則無渲染效果
  32.  
    await page.setJavaScriptEnabled(enabled= True)
  33.  
     
  34.  
    await page.goto( 'https://www.toutiao.com')
  35.  
    await asyncio.sleep( 5)
  36.  
     
  37.  
    print( await page.cookies()) # 打印頁面cookies
  38.  
    print( await page.content()) # 打印頁面文本
  39.  
    print( await page.title()) # 打印當前頁標題
  40.  
     
  41.  
    # 抓取新聞標題
  42.  
    title_elements = await page.xpath( '//div[@class="title-box"]/a')
  43.  
    for item in title_elements:
  44.  
    # 獲取文本
  45.  
    title_str = await ( await item.getProperty( 'textContent')).jsonValue()
  46.  
    print( await item.getProperty( 'textContent'))
  47.  
    # 獲取鏈接
  48.  
    title_link = await ( await item.getProperty( 'href')).jsonValue()
  49.  
    print(title_str)
  50.  
    print(title_link)
  51.  
     
  52.  
    # 在搜索框中輸入python
  53.  
    await page. type( 'input.tt-input__inner', 'python')
  54.  
     
  55.  
    # 點擊搜索按鈕
  56.  
    await page.click( 'button.tt-button')
  57.  
    await asyncio.sleep( 5)
  58.  
     
  59.  
    # print(page.url)
  60.  
    # 今日頭條點擊后新開一個頁面, 通過打印url可以看出page還停留在原頁面
  61.  
    # 以下用於切換至新頁面
  62.  
    pages = await browser.pages()
  63.  
    page = pages[- 1]
  64.  
    # print(page.url)
  65.  
     
  66.  
    page_source = await page.content()
  67.  
    text = pq(page_source)
  68.  
    await page.goto(
  69.  
    url= "https://www.toutiao.com/api/search/content/?"
  70.  
    "aid=24&app_name=web_search&offset=60&format=json"
  71.  
    "&keyword=python&autoload=true&count=20&en_qc=1"
  72.  
    "&cur_tab=1&from=search_tab&pd=synthesis&timestamp=1555589585193"
  73.  
    )
  74.  
    for i in range( 1, 10):
  75.  
    print(text( "#J_section_{} > div > div > div.normal.rbox > div > div.title-box > a > span". format(i)).text())
  76.  
     
  77.  
    # 關閉瀏覽器
  78.  
    await browser.close()
  79.  
     
  80.  
    asyncio.get_event_loop().run_until_complete(main())
  81.  
     

示例代碼2:

  1.  
    import asyncio
  2.  
    from pyppeteer import launch
  3.  
     
  4.  
     
  5.  
    async def main():
  6.  
    # headless參數設為False,則變成有頭模式
  7.  
    browser = await launch(
  8.  
    # headless=False
  9.  
    )
  10.  
     
  11.  
    page = await browser.newPage()
  12.  
     
  13.  
    # 設置頁面視圖大小
  14.  
    await page.setViewport(viewport={ 'width': 1280, 'height': 800})
  15.  
     
  16.  
    # 是否啟用JS,enabled設為False,則無渲染效果
  17.  
    await page.setJavaScriptEnabled(enabled= True)
  18.  
     
  19.  
    await page.goto( 'https://www.toutiao.com/')
  20.  
     
  21.  
    # 打印頁面cookies
  22.  
    print( await page.cookies())
  23.  
     
  24.  
    # 打印頁面文本
  25.  
    print( await page.content())
  26.  
     
  27.  
    # 打印當前頁標題
  28.  
    print( await page.title())
  29.  
     
  30.  
    # 抓取新聞標題
  31.  
    title_elements = await page.xpath( '//div[@class="title-box"]/a')
  32.  
    for item in title_elements:
  33.  
    # 獲取文本
  34.  
    title_str = await ( await item.getProperty( 'textContent')).jsonValue()
  35.  
    print( await item.getProperty( 'textContent'))
  36.  
    # 獲取鏈接
  37.  
    title_link = await ( await item.getProperty( 'href')).jsonValue()
  38.  
    print(title_str)
  39.  
    print(title_link)
  40.  
     
  41.  
    # 關閉瀏覽器
  42.  
    await browser.close()
  43.  
     
  44.  
     
  45.  
    asyncio.get_event_loop().run_until_complete(main())

 

 百度首頁交互

示例代碼:

  1.  
    import time
  2.  
    import asyncio
  3.  
    from pyppeteer import launch
  4.  
     
  5.  
    async def main():
  6.  
    browser = await launch(headless= False)
  7.  
    page = await browser.newPage()
  8.  
    await page.setViewport({ 'width': 1200, 'height': 800})
  9.  
    await page.goto( 'https://www.baidu.com')
  10.  
    # 在搜索框中輸入python
  11.  
    await page. type( 'input#kw.s_ipt', 'python')
  12.  
    # 點擊搜索按鈕
  13.  
    await page.click( 'input#su')
  14.  
     
  15.  
    # 等待元素加載,第一種方法,強行等待5秒
  16.  
    # await asyncio.sleep(5)
  17.  
     
  18.  
    # 第二種方法,在while循環里強行查詢某元素進行等待
  19.  
    while not await page.querySelector( '.t'):
  20.  
    pass
  21.  
     
  22.  
    # 滾動到頁面底部
  23.  
    await page.evaluate( 'window.scrollBy(0, window.innerHeight)')
  24.  
     
  25.  
    # 這些等待方法都不好用
  26.  
    # await page.waitForXPath('h3', timeout=300)
  27.  
    # await page.waitForNavigation(waitUntil="networkidle0")
  28.  
    # await page.waitForFunction('document.getElementByTag("h3")')
  29.  
    # await page.waitForSelector('.t')
  30.  
    # await page.waitFor('document.querySelector("#t")')
  31.  
    # await page.waitForNavigation(waitUntil='networkidle0')
  32.  
    # await page.waitForFunction('document.querySelector("").inner‌​Text.length == 7')
  33.  
     
  34.  
    title_elements = await page.xpath( '//h3[contains(@class,"t")]/a')
  35.  
    for item in title_elements:
  36.  
    title_str = await ( await item.getProperty( 'textContent')).jsonValue()
  37.  
    print(title_str)
  38.  
    await browser.close()
  39.  
     
  40.  
    asyncio.get_event_loop().run_until_complete(main())

 

示例:

  1.  
    import asyncio
  2.  
    import pyppeteer
  3.  
    from collections import namedtuple
  4.  
     
  5.  
    Response = namedtuple( "rs", "title url html cookies headers history status")
  6.  
     
  7.  
     
  8.  
    async def get_html( url, timeout=30):
  9.  
    # 默認30s
  10.  
    browser = await pyppeteer.launch(headless= True, args=[ '--no-sandbox'])
  11.  
    page = await browser.newPage()
  12.  
    res = await page.goto(url, options={ 'timeout': int(timeout * 1000)})
  13.  
    data = await page.content()
  14.  
    title = await page.title()
  15.  
    resp_cookies = await page.cookies()
  16.  
    resp_headers = res.headers
  17.  
    resp_history = None
  18.  
    resp_status = res.status
  19.  
    response = Response(
  20.  
    title=title,
  21.  
    url=url,
  22.  
    html=data,
  23.  
    cookies=resp_cookies,
  24.  
    headers=resp_headers,
  25.  
    history=resp_history,
  26.  
    status=resp_status
  27.  
    )
  28.  
    return response
  29.  
     
  30.  
     
  31.  
    if __name__ == '__main__':
  32.  
    url_list = [
  33.  
    "http://www.10086.cn/index/tj/index_220_220.html",
  34.  
    "http://www.10010.com/net5/011/",
  35.  
    # "http://python.jobbole.com/87541/"
  36.  
    ]
  37.  
    task = (get_html(url) for url in url_list)
  38.  
     
  39.  
    loop = asyncio.get_event_loop()
  40.  
    results = loop.run_until_complete(asyncio.gather(*task))
  41.  
    for res in results:
  42.  
    print(res.title)

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM