Puppeteer實戰:教你如何自動在掘金上發布文章

      網友投稿 1433 2025-03-31

      前言


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

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

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

      自動化測試工具的原理

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

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

      Puppeteer 簡介

      這里摘抄 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 (通過開發工具協議); Puppeteer 默認的運行模式是無頭的,但是可以被配置成非無頭的模式。

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

      以下是 Puppeteer 可以做的事情:

      生成截圖和頁面 PDF ;

      抓取單頁應用,產生預渲染內容(即 SSR ,服務端渲染);

      自動化表單提交、 UI 測試、鍵盤輸入等等;

      創建一個最新的、自動化的測試環境;

      捕獲網站的時間線來幫助診斷性能問題;

      測試 Chrome 插件;

      ...

      前言

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

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

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

      自動化測試工具的原理

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

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

      Puppeteer 簡介

      這里摘抄 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 (通過開發工具協議); Puppeteer 默認的運行模式是無頭的,但是可以被配置成非無頭的模式。

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

      以下是 Puppeteer 可以做的事情:

      生成截圖和頁面 PDF ;

      抓取單頁應用,產生預渲染內容(即 SSR ,服務端渲染);

      自動化表單提交、 UI 測試、鍵盤輸入等等;

      創建一個最新的、自動化的測試環境;

      捕獲網站的時間線來幫助診斷性能問題;

      測試 Chrome 插件;

      ...

      Puppeteer 安裝

      安裝 Puppeteer 并不難,只需要保證你的環境上安裝了 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-resolver

      const?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)

      const?res?=?await?page.evaluate((arg1,?arg2,?arg3)?=>?{

      //?anything?frontend

      return?'frontend?awesome'

      },?arg1,?arg2,?arg3)

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

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

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

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

      Puppeteer 實戰:在掘金上自動發布文章

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

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

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

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

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

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

      我們不用理解所有的方法,只需要知道我們啟動的入口是 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()

      //?發布文章

      await?this.publish()

      //?關閉瀏覽器

      await?this.browser.close()

      }

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

      async?login()?{

      logger.info(`logging?in...?navigating?to?${this.urls.login}`)

      await?this.page.goto(this.urls.login)

      let?errNum?=?0

      while?(errNum?

      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,我們先將瀏覽器導航至這個地址。

      這里我們循環 10 次,嘗試輸入用戶名和密碼,如果 10 次都失敗了,就設置登錄狀態為 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)

      //?后續處理

      await?this.afterInputEditor()

      }

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

      下面我們詳細看看 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()?{

      //?點擊發布文章

      const?elPubBtn?=?await?this.page.$('.publish-popup')

      await?elPubBtn.click()

      Puppeteer實戰:教你如何自動在掘金上發布文章

      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)

      }

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

      async?publish()?{

      logger.info(`publishing?article`)

      //?發布文章

      const?elPub?=?await?this.page.$(this.editorSel.publish)

      await?elPub.click()

      await?this.page.waitFor(10000)

      //?后續處理

      await?this.afterPublish()

      }

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

      源碼

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

      總結

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

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

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

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

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

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

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

      Puppeteer

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:項目管理軟件,提高工作效率的神器
      下一篇:wps怎么把別的表格轉移到工作表上
      相關文章
      亚洲激情视频在线观看| 亚洲av中文无码字幕色不卡| 亚洲成人国产精品| 亚洲AV无码第一区二区三区| 亚洲中文字幕人成乱码| 亚洲精品国产精品乱码不卡| 亚洲中文字幕AV每天更新| 亚洲精品成人网久久久久久| 亚洲 无码 在线 专区| 亚洲视频免费播放| 久久乐国产综合亚洲精品| 亚洲色婷婷六月亚洲婷婷6月| 久久精品国产亚洲AV蜜臀色欲 | 亚洲国产精品综合一区在线| 亚洲欧洲中文日韩久久AV乱码| 国产成人亚洲综合一区| av在线亚洲欧洲日产一区二区| 亚洲卡一卡2卡三卡4麻豆| 久久久久久亚洲精品不卡| 亚洲日韩AV一区二区三区中文| 国产av天堂亚洲国产av天堂| 亚洲精品偷拍视频免费观看| 亚洲色精品VR一区区三区| 亚洲中文字幕在线乱码| 亚洲自偷自偷在线成人网站传媒 | 国产精品亚洲专一区二区三区| 亚洲国产亚洲综合在线尤物| 久久国产亚洲电影天堂| 亚洲AV无码专区电影在线观看 | 日韩亚洲变态另类中文| 亚洲日韩人妻第一页| 亚洲另类古典武侠| 久久精品国产亚洲AV蜜臀色欲| 亚洲电影唐人社一区二区| 亚洲欧洲日产国码在线观看| 亚洲最新在线视频| 亚洲精品伊人久久久久| 亚洲精品久久无码av片俺去也| 久久久久亚洲AV无码去区首| 亚洲av麻豆aⅴ无码电影| 亚洲午夜无码AV毛片久久|