<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          不僅僅可以用來做爬蟲,Puppeteer 還可以干這個!

          共 8319字,需瀏覽 17分鐘

           ·

          2019-10-10 23:20

          Python 自動化測試工具大家可能知道 Pyppeteer,其實它就是 Puppeteer 的 Python 版本的實現,二者功能類似。但其實 Puppeteer 和 Pyppeteer 不僅僅可以用來做爬蟲,還能干很多其他的事情,今天就來介紹用 Puppeteer 搞的一個騷操作——自動發(fā)文。

          閱讀本文大概需要 6 分鐘。


          d3d50b1eb5f11fef6676c7d46879a9f3.webp


          前言

          自動化測試對于軟件開發(fā)來說是一個很重要也很方便的東西,但是自動化測試工具除了能用來做測試以外,還能被用來做一些模擬人類操作的事情,所以一些 E2E 自動化測試工具(例如:Selenium、Puppeteer、Appium)因為其強大的模擬功能,經常還被爬蟲工程師們用來抓取數據。

          網上有很多將自動化測試工具作為爬蟲的抓取教程,不過僅僅都限于如何獲取數據,而我們知道這些基于瀏覽器的解決方案都有較大的性能開銷,而且效率不高,并不是爬蟲的最佳選擇。

          本篇文章將介紹自動化測試工具的另一種用法,也就是用來自動化一些人工操作。我們使用的工具是谷歌開發(fā)并開源的測試框架 Puppeteer ,它會操作 Chromium (谷歌開發(fā)的開源瀏覽器)來完成自動化。我們將一步一步介紹如何利用 Puppeteer 在掘金上自動發(fā)布文章。

          自動化測試工具的原理

          自動化測試工具的原理是通過程式化地操作瀏覽器,與其進行模擬交互(例如點擊、打字、導航等等)來控制要抓取的網頁。自動化測試工具通常也能獲取網頁的 DOM 或 HTML,因此也可以輕松的獲取網頁數據。

          此外,對于一些動態(tài)網站來說,JS 動態(tài)渲染的數據通常不能輕松獲取,而自動化測試工具則可以輕松的做到,因為它是將 HTML 輸入瀏覽器里運行的。

          Puppeteer 簡介

          aac693dfebed21b92bc96fb2e77bea3c.webp

          這里摘抄 Puppeteer 的 Github 主頁上的定義(英文)。

          Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.

          翻譯過來大致是:Puppeteer 是一個 Node.js 庫,提供了高級 API 來控制 Chrome 或 Chromium (通過開發(fā)工具協(xié)議);Puppeteer 默認的運行模式是無頭的,但是可以被配置成非無頭的模式。

          Loco注:無頭指的是不顯示瀏覽器的GUI,是為了提升性能而設計的,因為渲染圖像是一件很消耗資源的事情。

          以下是 Puppeteer 可以做的事情:

          ?生成截圖和頁面 PDF ;?抓取單頁應用,產生預渲染內容(即 SSR ,服務端渲染);?自動化表單提交、 UI 測試、鍵盤輸入等等;?創(chuàng)建一個最新的、自動化的測試環(huán)境;?捕獲網站的時間線來幫助診斷性能問題;?測試 Chrome 插件;?...

          Puppeteer 安裝

          安裝 Puppeteer 并不難,只需要保證你的環(huán)境上安裝了 Node.js 以及能夠運行 NPM。

          由于官方的安裝教程沒有考慮到已經安裝了 Chromium 的情況,我們這里使用一個第三方庫?puppeteer-chromium-resolver,它能夠自定義化 Puppeteer 以及管理 Chromium 的下載情況。

          運行以下命令安裝 Puppeteer:

          npm install puppeteer-chromium-resolver --save

          puppeteer-chromium-resolver?的詳細用法請參照官網:https://www.npmjs.com/package/puppeteer-chromium-resolver。

          Puppeteer 常用命令

          Puppeteer 的官方API文檔是 https://pptr.dev/ ,文檔里有詳細的 Puppeteer 的開放接口,可以進行參考,這里我們只列出一些常用的接口命令。

          生成/關閉瀏覽器

          // 引入puppeteer-chromium-resolverconst PCR = require('puppeteer-chromium-resolver')
          // 生成PCR實例const pcr = await PCR({ revision: '', detectionPath: '', folderName: '.chromium-browser-snapshots', hosts: ['https://storage.googleapis.com', 'https://npm.taobao.org/mirrors'], retry: 3, silent: false})
          // 生成瀏覽器const browser = await pcr.puppeteer.launch({...})
          // 關閉瀏覽器await browser.close()

          生成頁面

          const page = await browser.newPage()

          導航

          await page.goto('https://baidu.com')

          等待

          await page.waitFor(3000)
          await page.goto('https://baidu.com')

          獲取頁面元素

          const el = await page.$(selector)

          點擊元素

          await el.click()

          輸入內容

          await el.type(text)

          執(zhí)行Console代碼(重點)

          const res = await page.evaluate((arg1, arg2, arg3) => {// anything frontend    return 'frontend awesome'}, arg1, arg2, arg3)

          這應該是 Puppeteer 中最強大的 API 了。任何熟悉前端技術的開發(fā)者都應該了解 Chrome 開發(fā)者工具中的 Console,任何 JS 的代碼都可以在這里被運行,其中包括點擊事件、獲取元素、增刪改元素等等。我們的自動發(fā)文程序將大量用到這個 API 。

          可以看到?evaluate?方法可以接受一些參數,并作為回調函數中的參數作用在前端代碼中。這讓我們可以將后端的任何數據注入到前端 DOM 中,例如文章標題和文章內容等等。

          另外,回調函數中的返回值可以作為?evaluate?的返回值,賦值給?res,這經常被用作數據抓取。

          注意,上面的這些代碼都用了?await?這個關鍵字,這其實是 ES7 中的?async/await?新語法,是 ES6 的?Promise?的語法糖,讓異步代碼更容易閱讀和理解。如果對?async/await?不理解的同學,可以參考這篇文章:https://juejin.im/post/596e142d5188254b532ce2da。

          Puppeteer 實戰(zhàn):在掘金上自動發(fā)布文章

          常言說:Talk is cheap, show me the code。

          下面,我們將用一個自動發(fā)文章的例子來展示 Puppeteer 的功能。本文中用來作為示例的平臺是掘金。

          為什么選擇掘金呢?這是因為掘金的登錄并不像其他某些網站(例如 CSDN )要求輸入驗證碼(這會增大復雜度),只要求輸入賬戶名和密碼就可以登錄了。

          為了方便新手理解,我們將從爬蟲基本結構開始講解。(限于篇幅考慮,我們將略過瀏覽器和頁面的初始化,只挑重點講解)

          基礎結構

          為了讓爬蟲顯得不那么亂七八糟,我們將發(fā)布文章的各個步驟抽離了出來,形成了一個基類(因為我們可能不止掘金一個平臺要抓取,使用面向對象的思想編寫代碼的話,其他平臺只需要繼承基類就可以了)。

          這個爬蟲基類大致的結構如下:

          f9b57336c5fe8b63fc3e00f969821dd0.webp

          我們不用理解所有的方法,只需要知道我們啟動的入口是?run?這個方法就好了。

          所有方法都加上了?async,表示這個方法將返回?Promise,如果需要以同步的形式調用,必須加上?await?這個關鍵字。

          run?方法的內容如下:

            async run() {// 初始化    await this.init()
          if (this.task.authType === constants.authType.LOGIN) {// 登陸 await this.login() } else { // 使用Cookie await this.setCookies() }
          // 導航至編輯器 await this.goToEditor()
          // 輸入編輯器內容 await this.inputEditor()
          // 發(fā)布文章 await this.publish()
          // 關閉瀏覽器 await this.browser.close() }

          可以看到,爬蟲將首先初始化,完成一些基礎配置;然后根據任務的驗證類別(authType?)來決定是否采用登錄或 Cookie 的方式來通過網站驗證(本文只考慮登錄驗證的情況);接下來就是導航至編輯器,然后輸入編輯器內容;接著,發(fā)布文章;最后關閉瀏覽器,發(fā)布任務完成。

          登錄

            async login() {    logger.info(`logging in... navigating to ${this.urls.login}`)    await this.page.goto(this.urls.login)let errNum = 0while (errNum < 10) {try {        await this.page.waitFor(1000)const elUsername = await this.page.$(this.loginSel.username)const elPassword = await this.page.$(this.loginSel.password)const elSubmit = await this.page.$(this.loginSel.submit)        await elUsername.type(this.platform.username)        await elPassword.type(this.platform.password)        await elSubmit.click()        await this.page.waitFor(3000)break      } catch (e) {        errNum++      }    }
          // 查看是否登陸成功 this.status.loggedIn = errNum !== 10
          if (this.status.loggedIn) { logger.info('Logged in') } }

          掘金的登錄地址是 https://juejin.im/login,我們先將瀏覽器導航至這個地址。

          這里我們循環(huán) 10 次,嘗試輸入用戶名和密碼,如果 10 次都失敗了,就設置登錄狀態(tài)為?false;反之,則設置為?true

          接著,我們用到了?page.$(selector)?和?el.type(text)?這兩個 API ,分別用于獲取元素和輸入內容。而最后的?elSubmit.click()?是提交表單的操作。

          編輯文章

          這里我們略過了跳轉到文章編輯器的步驟,因為這個很簡單,只需要調用?page.goto(url)?就可以了,后面會貼出源碼地址供大家參考。

          輸入編輯器的代碼如下:

            async inputEditor() {    logger.info(`input editor title and content`)// 輸入標題    await this.page.evaluate(this.inputTitle, this.article, this.editorSel, this.task)    await this.page.waitFor(3000)
          // 輸入內容 await this.page.evaluate(this.inputContent, this.article, this.editorSel) await this.page.waitFor(3000)
          // 輸入腳注 await this.page.evaluate(this.inputFooter, this.article, this.editorSel) await this.page.waitFor(3000)
          await this.page.waitFor(10000)
          // 后續(xù)處理 await this.afterInputEditor() }

          首先輸入標題,調用了?page.evaluate?這個前端執(zhí)行函數,傳入?this.inputTitle?輸入標題這個回調函數,以及其他參數;接著同樣的原理,調用輸入內容回調函數;然后是輸入腳注;最后,調用后續(xù)處理函數。


          下面我們詳細看看?this.inputTitle?這個函數:

            async inputTitle(article, editorSel, task) {const el = document.querySelector(editorSel.title)    el.focus()    el.select()    document.execCommand('delete', false)    document.execCommand('insertText', false, task.title || article.title)  }

          我們首先通過前端的公開接口?document.querySelector(selector)?獲取標題的元素,為了防止標題有 placeholder,我們用?el.focus()(獲取焦點)、el.select()(全選)、document.execCommand('delete', false)(刪除)來刪除已有的 placeholder。然后我們通過?document.execCommand('insertText', false, text)來輸入標題內容。

          接下來,是輸入內容,代碼如下(它的原理與輸入標題類似):

            async inputContent(article, editorSel) {const el = document.querySelector(editorSel.content)    el.focus()    el.select()    document.execCommand('delete', false)    document.execCommand('insertText', false, article.content)  }

          有人可能會問,為什么不用?el.type(text)?來輸入內容,反而要大費周章的用?document.execCommand?來實現輸入呢?

          這里我們不用前者的原因,是因為它是完全模擬人的敲打鍵盤操作的,這樣會破壞已有的內容格式。而如果用后者的話,可以一次性的將內容輸入進來。

          我們在基類?BaseSpider?中預留了一個方法來完成選擇分類、標簽等操作,在繼承后的類?JuejinSpider?中是這樣的:

              async afterInputEditor() {// 點擊發(fā)布文章        const elPubBtn = await this.page.$('.publish-popup')        await elPubBtn.click()        await this.page.waitFor(5000)
          // 選擇類別 await this.page.evaluate((task) => { document.querySelectorAll('.category-list > .item').forEach(el => { if (el.textContent === task.category) { el.click() } }) }, this.task) await this.page.waitFor(5000)
          // 選擇標簽 const elTagInput = await this.page.$('.tag-input > input') await elTagInput.type(this.task.tag) await this.page.waitFor(5000) await this.page.evaluate(() => { document.querySelector('.suggested-tag-list > .tag:nth-child(1)').click() }) await this.page.waitFor(5000) }

          發(fā)布

          發(fā)布操作相對來說比較簡單了,只需要點擊發(fā)布的那個按鈕就可以了。代碼如下:

            async publish() {    logger.info(`publishing article`)// 發(fā)布文章    const elPub = await this.page.$(this.editorSel.publish)    await elPub.click()    await this.page.waitFor(10000)
          // 后續(xù)處理 await this.afterPublish() }

          this.afterPublish?是用來處理驗證發(fā)文狀態(tài)和獲取發(fā)布 URL 的,這里限于篇幅不詳細介紹了。

          源碼

          當然,本篇文章由于篇幅原因,介紹的并不是所有的自動發(fā)文功能,如果你想了解更多,可以發(fā)送消息【掘金自動發(fā)文】到微信公眾號【NightTeam】獲取源碼地址,注意是【NightTeam】,不是本號。

          總結

          本篇文章介紹了如何使用 Puppeteer 來操作 Chromium 瀏覽器在掘金上發(fā)布文章。

          很多人用 Puppeteer 來抓取數據,但我們認為這種效率較低,而且開銷較大,不適合大規(guī)模抓取。

          相反, Puppeteer 更適合做一些自動化的工作,例如操作瀏覽器發(fā)布文章、發(fā)布帖子、提交表單等等。

          Puppeteer 自動化工具很類似 RPA(Robotic Process Automation),都是自動化一些繁瑣的、重復性的工作,只不過后者不僅限于瀏覽器,其范圍(Scope)是基于整個操作系統(tǒng)的,功能更強大,但是開銷也更大。

          Puppeteer 作為相對輕量級的自動化工具,很適合用來做一些網頁自動化操作作業(yè)。本文介紹的 Puppeteer 實戰(zhàn)內容也是開源一文多發(fā)平臺項目?ArtiPub?的一部分,有興趣的同學可以去嘗試一下。

          文章作者:「夜幕團隊 NightTeam」 - 張冶青

          潤色、校對:「夜幕團隊 NightTeam」 - Loco

          夜幕團隊成立于 2019 年,團隊包括崔慶才、周子淇、陳祥安、唐軼飛、馮威、蔡晉、戴煌金、張冶青和韋世東。

          涉獵的編程語言包括但不限于 Python、Rust、C++、Go,領域涵蓋爬蟲、深度學習、服務研發(fā)、對象存儲等。團隊非正亦非邪,只做認為對的事情,請大家小心。

          感興趣可以關注夜幕團隊的公眾號【NightTeam】

          崔慶才

          靜覓博客博主,《Python3網絡爬蟲開發(fā)實戰(zhàn)》作者

          隱形字

          個人公眾號:進擊的Coder

          621190c511c6b43028eb575b31433a6c.webp64342ed9a4560a6802f2d531c07d07ef.webp

          長按識別二維碼關注

          好文和朋友一起看~
          瀏覽 142
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  无码一区二区三区四区五 | 麻豆成人av影院 漫画视频搞黄网站 | 日韩精品一区二区三区在线观看 | 免费视频三区 | 大香蕉久久依人网站 |