基於 Electron 的爬蟲框架 Nightmare

来源:http://www.cnblogs.com/ikcamp/archive/2017/09/19/7553117.html
-Advertisement-
Play Games

Electron 可以讓你使用純 JavaScript 調用 Chrome 豐富的原生的介面來創造桌面應用。你可以把它看作一個專註於桌面應用的 Node.js 的變體,而不是 Web 伺服器。其基於瀏覽器的應用方式可以極方便的做各種響應式的交互,接下來介紹下關於 Electron 上衍生出的框架 N... ...


作者:William
本文為原創文章,轉載請註明作者及出處

Electron 可以讓你使用純 JavaScript 調用 Chrome 豐富的原生的介面來創造桌面應用。你可以把它看作一個專註於桌面應用的 Node.js 的變體,而不是 Web 伺服器。其基於瀏覽器的應用方式可以極方便的做各種響應式的交互,接下來介紹下關於 Electron 上衍生出的框架 Nightmare。

Nightmare 是一個基於 Electron 的框架,針對 Web 自動化測試和爬蟲(其實爬蟲這個是大家自己給這個框架加的功能XD),因為其具有跟 PlantomJS 一樣的自動化測試的功能可以在頁面上模擬用戶的行為觸發一些非同步數據載入,也可以跟 Request 庫一樣直接訪問 URL 來抓取數據,並且可以設置頁面的延遲時間,所以無論是手動觸發腳本還是行為觸發腳本都是輕而易舉的(這邊註意,如果事件具備 isTrusted 的檢查的話,就無法觸發了)。

使用 Nightmare

為了更快速使用 NPM 下載,可以使用淘寶的鏡像地址。直接 NPM 安裝Nightmare 就完成安裝了(二進位的 Electron 依賴有點大,安裝時間可能比較長)。

寫一個簡單的啟動 app.js;

const Nightmare = require('nightmare')
const nightmare = new Nightmare({
     show: true,
     openDevTools: {
         mode: 'detach'
     }
 })

 nightmare.goto('https://www.hujiang.com')
   .evaluate(function() {
       // 該環境中能使用瀏覽器中的任何對象window/document,並且返回一個promise
     console.log('hello nightmare')
     console.log('5 second close window')
   })
   .wait(5000)
   .end()
   .then(()=> {
     console.log('close nightmare')
   })

這個腳本會在打開的瀏覽器的調試控制臺中列印出 hello nightmare 並且在5秒後關閉,隨後在運行的該腳本的中輸出 close nightmare。

Nightmare原理

利用了 Electron 提供的 Browser 的環境,同時具備了 Node.js 的 I/O 能力,所以可以很方便實現一個爬蟲應用。Nightmare 的官網有更詳細的介紹:

大致操作:

  • 瀏覽器事件: goto,back,forward,refresh,
  • 用戶事件: click,mousedown,mouseup,mouseover,type,insert,select,check,uncheck,selectscrollTo
  • 向網頁註入腳本: .js .css的文件類型原理是跟油猴差不多,可以編寫自己的js代碼註入十分方便
  • wait 函數可以按照延遲時間或者一個 dom 元素的出現
  • evaluate 以瀏覽器的環境運行的腳本函數,然後返回一個 promise 函數

一個完整的nightmare爬蟲應用

我們以抓取知乎上的話題的為應用場景,需要的數據是知乎的話題信息 包含以下欄位 話題名稱/話題的圖片/關註者數量/話題數量/精華話題數量,但是因為後三者只能在其父親話題中包含,所以必須先抓父話題才能抓取子話題,而且這些子話題是以 hover 的形式在父話題中非同步載入的,如果用Request/Superagent 需要 HTTP 傳遞其解析過的id才能獲取,但是用Nightmare 可以直接調用其 hover 事件觸發數據的載入。

第一步獲取需要抓取的話題深度,預設的根是現在知乎的根話題;

/** 
* 抓取對應的話題頁面的url和對應的深度保存到指定的文件名中
* @param {string} rootUrl - 頂層的url 
* @param {int} deep - 抓取頁面的深度 
* @param {string} toFile - 保存的文件名
* @param {Function} cb - 完成後的回調 
*/
async function crawlerTopicsFromRoot (rootUrl, deep, toFile, cb) {
  rootUrl = rootUrl ||'https://www.zhihu.com/topic/19776749/hot'
  toFile = toFile || './topicsTree.json'
  console.time()
  const result = await interactive
      .iAllTopics(rootUrl, deep)
  console.timeEnd()
  util.writeJSONToFile(result['topics'], toFile, cb)
}

crawlerTopicsFromRoot('', 2, '', _ => {
  console.log('完成抓取')
})

然後進行交互函數的核心函數,註意在開始抓取前,要去看看知乎的 robots.txt 文件看看哪些能抓和抓取的間隔不然很容易 timeout 的錯誤。

// 獲取對應的話題的信息
const cntObj = queue.shift()
const url = `https://www.zhihu.com/topic/${cntObj['id']}/hot`
const topicOriginalInfo = await nightmare
  .goto(url)
  .wait('.zu-main-sidebar') // 等待該元素的出現
  .evaluate(function () {
   // 獲取這塊數據
      return document.querySelector('.zu-main-sidebar').innerHTML
  })
// .....若幹步的操作後
// 獲取其子話題的數值信息
const hoverElement = `a.zm-item-tag[href$='${childTopics[i]['id']}']`
const waitElement = `.avatar-link[href$='${childTopics[i]['id']}']`
const topicAttached = await nightmare
  .mouseover(hoverElement) // 觸發hover事件
  .wait(waitElement)
  .evaluate(function () {
      return document.querySelector('.zh-profile-card').innerHTML
  })
  .then(val => {
      return parseRule.crawlerTopicNumbericalAttr(val)
  })
  .catch(error => {
      console.error(error)
  })

cheerio 是一個 jQuery 的 selector 庫,可以應用於 HTML 片段並且獲得對應的DOM 元素,然後我們就可以進行對應的 DOM 操作->增刪改查都可以,這邊主要用來查詢 DOM 和獲取數據。

const $ = require('cheerio')
/** *抓取對應話題的問題數量/精華話題數量/關註者數量 */
const crawlerTopicNumbericalAttr = function (html) {
  const $ = cheerio.load(html)
  const keys = ['questions', 'top-answers', 'followers']
  const obj = {}
  obj['avatar'] = $('.Avatar.Avatar--xs').attr('src')
  keys.forEach(key => {
      obj[key] = ($(`div.meta a.item[href$=${key}] .value`).text() || '').trim()
  })
  return obj
}
/** * 抓取話題的信息 */
const crawlerTopics = function (html) {
  const $ = cheerio.load(html)
  const  obj = {}
  const childTopics = crawlerAttachTopic($, '.child-topic')  
  obj['desc'] = $('div.zm-editable-content').text() || ''
  if (childTopics.length > 0) {
      obj['childTopics'] = childTopics
  }
  return obj
}

/** * 抓取子話題的信息id/名稱 */
const crawlerAttachTopic = function ($, selector) {
  const topicsSet = []
  $(selector).find('.zm-item-tag').each((index, elm) => {
      const self = $(elm)
      const topic = {}
      topic['id'] = self.attr('data-token')
      topic['value'] = self.text().trim()
      topicsSet.push(topic)
  })
  return topicsSet
}

然後一個簡單的爬蟲就完成了,最終獲得部分數據格式如何:

{
  "value": "rootValue",
  "id": "19776749",
  "fatherId": "-1",
  "desc": "知乎的全部話題通過父子關係構成一個有根無迴圈的有向圖。「根話題」即為所有話題的最上層的父話題。話題精華即為知乎的 Top1000 高票回答。請不要在問題上直接綁定「根話題」。這樣會使問題話題過於寬泛。",
  "cids": [
      "19778317",
      "19776751",
      "19778298",
      "19618774",
      "19778287",
      "19560891"
  ]
},
{
  "id": "19778317",
  "value": "生活、藝術、文化與活動",
  "avatar": "https://pic4.zhimg.com/6df49c633_xs.jpg",
  "questions": "3.7M",
  "top-answers": "1000",
  "followers": "91K",
  "fid": "19776749",
  "desc": "以人類集體行為和人類社會文明為主體的話題,其內容主要包含生活、藝術、文化、活動四個方面。",
  "cids": [
      "19551147",
      "19554825",
      "19550453",
      "19552706",
      "19551077",
      "19550434",
      "19552266",
      "19554791",
      "19553622",
      "19553632"
  ]
},

總結

Nightmare 作為爬蟲的最大優勢是只需要知道數據所在頁面的 URL 就可以獲取對應的同步/非同步數據,並不需要詳細的分析 HTTP 需要傳遞的參數。只需要知道進行哪些操作能使得網頁頁面數據更新,就能通過獲取更新後的 HTML 片段獲得對應的數據,在 Demo 中的 Nightmare 是打開了 chrome-dev 進行操作的,但是實際運行的時候是可以關閉的,關閉了之後其操作的速度會有一定的上升。下麵的項目中還包含了另外一個爬取的知乎的動態。

Demo源碼地址: https://github.com/williamstar/nightmare-demo

iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、噹噹開售。

>> 滬江Web前端上海團隊招聘【Web前端架構師】,有意者簡歷至:[email protected] <<


*報名地址:http://www.huodongxing.com/event/4404706591400*


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這兩種寫法。這兩種寫法到底有什麼不同呢?用哪種來寫更加規範呢? 將href="#"是指聯接到當前頁面,其實是無意義的,頁面也不會刷新。這是一個錨鏈接。 在製作網頁時html語言里的參數,用於指定鏈接的url ####就是本頁鏈接,href="地址"就是鏈接到地址 鏈接本頁面 預設本頁,不彈出新視窗, ...
  • <!DOCTYPE html><html><head lang="zh-cn"><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"><m ...
  • function getobj(objs, key, value) { for (var i in objs) { var obj = $(objs[i]); if (obj.attr(key) == value) { return obj[0]; } } return null; }; ...
  • 首先瞭解下CSS的渲染邏輯,它是從標記的最後一位開始搜索的,例如:.myclass li a,首選它會遍歷所有的<a>,然後看哪些<a>之前有<li>,然後再看哪些<li>之前有.myclass。 所以:1、層級太多會增加CSS渲染的工作量。 如下: 除此之外,還有哪些可以優化的呢? 2、圖中樣式的 ...
  • 接著上篇vue-cli腳手架構建項目結構建好項目之後,就開始寫個“hello world!”吧~~~ vue玩的都是組件,所以開發的也是組件。 1.新建helloworld.vue。(刪除Hello.vue)代碼如下: 一個簡單的組件就完成了。 2.我們打開入口組件App.vue並把裡面的代碼替換成 ...
  • 使用 react已經有不短的時間了,最近看到關於 react高階組件的一篇文章,看了之後頓時眼前一亮,對於我這種還在新手村晃蕩、一切朝著打怪升級看齊的小嘍啰來說,像這種難度不是太高同時門檻也不是那麼低的東西如今可不多見了啊,是個不可多得的 zhuangbility的利器,自然不可輕易錯過,遂深入瞭解 ...
  • 簡介 在SF上看到這樣一個提問: 如題,因為不得已的原因,需要寫若幹個全局函數。但又不想這樣: 題主問有什麼好的寫法? 解答: 如果你用 jQuery,你可以這樣寫 如果你不用 jQuery,可以直接實現類似的 extend: 在JavaScript中,命名空間可以幫助我們防止與全局命名空間下的其他 ...
  • 當我們在Spoil打算推出我們自己的移動端應用時,頭一個需要作出的決定就是:我們應該使用哪種編程語言?經過一番討論,我們最終做出的決定是:React-Native。學習一門新的“語言”或者框架並不是個大問題,但是老兄我得告訴你,React-Native和Redux確確實實是塊難啃的骨頭。這篇文章沒有... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...