整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Node.js 學習筆記:構建 Web 服務

          例3. 構建 Web 服務器

          這部分示例將致力于用 Node.js 模擬一個類似于 Apache 的 Web 服務器,處理瀏覽器端的請求,將相關的頁面響應給瀏覽器。首先,我們要在code目錄下執行mkdir 03_webSever命令來創建用于存放這一組示例的目錄。然后執行以下步驟:

          1. 在code/03_webSever目錄下執行mkdir www命令,創建網站目錄,然后在其中創建index.htm和login.htm兩個 HTML 文件以及一個名為style.css的 CSS 文件:

          1、index.htm:

          <!DOCTYPE html>
          <html lang="zh-cn">
          <head>
          <meta charset="utf-8" />
          <link rel="stylesheet" type="text/css" href="style.css" />
          <title>首頁</title>
          </head>
          <body>
          <h1>你好,nodejs!</h1>
          <p> <a href="login.htm">請登錄!</a> </p>
          </body>
          </html>

          2、login.htm:

          <!DOCTYPE html>
          <html lang="zh-cn">
          <head>
          <meta charset="utf-8" />
          <link rel="stylesheet" type="text/css" href="style.css" />
          <title>登錄頁面</title>
          </head>
          <body>
          <h1>你已經登錄。。。</h1>
          <p> <a href="index.htm">回首頁!</a> </p>
          </body>
          </html>

          3、style.css:

          body { 
          background: gray; 
          }

          2、在code/03_webSever目錄下執行touch 03-webServer.js命令,創建腳本文件,并輸入如下代碼:

          const http = require('http')
          const fs = require('fs')
          const server = http.createServer()
          
          server.on('request', function(req, res) {
          const webRoot = './www'
          const url = req.url
          if ( url === '/' ) {
          url = '/index.htm'
          }
          
          fs.readFile(webRoot+url, function(err, data) {
          if ( err !== null ) {
          console.error('錯誤信息:' + err.message)
          return res.end('<h1>404 頁面沒找到!</h1>')
          }
          res.end(data)
          })
          })
          
          server.listen(8080, function(){
          console.log('請訪問http://localhost:8080/,按Ctrl+C終止服務!')
          })

          3、保存所有文件后,在code/03_webSever目錄下執行node 03-webServer.js命令,然后打開瀏覽器并訪問http://localhost:8080/,就會看到如下頁面:

          示例4. 使用art-template模版引擎生成網頁

          這一部分本示例將以生成個人信息頁面為例,演示在服務器端基于 Node.js 使用art-template模板引擎來生成網頁。為此,我們需要在code目錄下執行mkdir 04_templatingEngine命令來創建用于存放這一組示例的目錄。

          1. 單模版渲染

          首先來示范一下如何使用art-template模版引擎的渲染單一模版文件,請跟著以下步驟來構建示例:

          1. 在code/04_templatingEngine目錄下執行npm install art-template --save命令,安裝將art-template包安裝到當前示例項目中。
          2. 在code/04_templatingEngine目錄下執行touch singleTpl.htm命令,創建一個模版文件,并在其中輸入以下代碼:
          <!DOCTYPE html>
          <html lang="zh-cn">
          <head>
          <meta charset="utf-8" />
          <link rel="stylesheet" type="text/css" href="style.css" />
          <title>{{ name }}的個人信息</title>
          </head>
          <body>
          <h1>{{ name }}的個人信息</h1>
          <table>
          <tr><td>姓名:</td><td>{{ name }}</td></tr>
          <tr><td>年齡:</td><td>{{ age }}</td></tr>
          <tr><td>性別:</td><td>{{ sex }}</td></tr>
          <tr>
          <td>愛好:</td>
          <td>{{ each items }} {{ $value }} {{ /each }}</td>
          </tr>
          </table>
          </body>
          </html>

          3、在code/04_templatingEngine目錄下執行touch 04-useTemplating_engine.js命令,創建一個腳本文件,具體如下:

          const http = require('http')
          const fs = require('fs')
          const template = require('art-template')
          
          class human {
          constructor(name, age, sex, items=[])
          {
          this.name = name
          this.age = age
          this.sex = sex
          this.items = items
          }
          }
          
          const server = http.createServer()
          
          server.on('request', function(req, res){
          const url = req.url
          let boy = null
          if ( url === '/' ) {
          boy = new human('凌杰', '37', '男', ['看書', '看電影','旅游'])
          } else if ( url === '/wang' ) {
          boy = new human('蔓兒', '25', '女', ['看書', '看電影','寫作'])
          }
          
          if ( boy === null ) {
          return res.end('<h1>404 頁面沒找到!</h1>')
          }
          
          fs.readFile('./singleTpl.htm', function(err, data){
          if ( err !== null ) {
          return res.end('<h1>404 沒找到模版文件!</h1>')
          }
          
          const strHtml = template.render(data.toString(), {
          name : boy.name,
          age : boy.age,
          sex : boy.sex,
          items: boy.items
          })
          
          res.end(strHtml)
          })
          })
          
          server.listen(8080, function(){
          console.log('請訪問http://localhost:8080/,按Ctrl+C終止服務!')
          })

          4、保存所有文件后,在code/04_templatingEngine目錄下執行node 04-useTemplating_engine.js命令,然后打開瀏覽器并訪問http://localhost:8080/wang,就會看到如下頁面:

          2. 多模版組合渲染

          在同一 Web 應用中,所有的頁面通常都由相同的頭部和底部元素,所以為了減少代碼的冗余,提高重用率,開發者們通常會考慮將重復的部分獨立成一個單獨的模版文件,然后用相互包含的方式組合成頁面。下面就繼續以art-template模板引擎為例來演示一下如何將多個模版組合渲染成單一的 HTML 頁面,其具體步驟如下:

          1. 在code/04_templatingEngine目錄下執行touch tpl1.art tpl2.art命令,創建兩個模版文件,然后在這兩個文件中分別輸入以下代碼:

          1、tpl1.art :

          <header>
          <h1>查看個人信息</h1>
          <br>
          </header>

          2、tpl2.art :

          <footer>
          <div>
          <p>? 2016 owlman.org;本站系純HTML5站點。</p>
          </div>
          </footer>

          2、在code/04_templatingEngine目錄下執行touch multiTpl.htm命令創建用于組合的 HTML 頁面文件,并在其中輸入以下代碼:

          <!DOCTYPE html>
          <html lang="zh-cn">
          <head>
          <meta charset="utf-8" />
          <link rel="stylesheet" type="text/css" href="style.css" />
          <title>查看個人信息</title>
          </head>
          <body>
          {{ include './tpl1.art' }}
          <h2>{{ name }}的個人信息</h2>
          <table>
          <tr><td>姓名:</td><td>{{ name }}</td></tr>
          <tr><td>年齡:</td><td>{{ age }}</td></tr>
          <tr><td>性別:</td><td>{{ sex }}</td></tr>
          <tr>
          <td>愛好:</td>
          <td>{{ each items }} {{ $value }} {{ /each }}</td>
          </tr>
          </table>
          {{ include './tpl2.art' }}
          </body>
          </html>

          3、在code/04_templatingEngine目錄下執行

          cp 04-useTemplating_engine.js 04-useTemplating_engine2.js命令,將之前的代碼復制一份,并修改如下:

          const http = require('http')
          const fs = require('fs')
          const template = require('art-template')
          
          template.defaults.root = __dirname // 配置模版的查找根目錄
          
          class human {
          constructor(name, age, sex, items=[])
          {
          this.name = name
          this.age = age
          this.sex = sex
          this.items = items
          }
          }
          
          const server = http.createServer()
          
          server.on('request', function(req, res){
          const url = req.url
          let boy = null
          if ( url === '/' ) {
          boy = new human('凌杰', '37', '男', ['看書', '看電影','旅游'])
          } else if ( url === '/wang' ) {
          boy = new human('蔓兒', '25', '女', ['看書', '看電影','寫作'])
          }
          
          if ( boy === null ) {
          return res.end('<h1>404 頁面沒找到!</h1>')
          }
          
          fs.readFile('./multiTpl.htm', function(err, data){ // 修改了要讀取的模版文件
          if ( err !== null ) {
          return res.end('<h1>404 沒找到模版文件!</h1>')
          }
          
          const strHtml = template.render(data.toString(), {
          name : boy.name,
          age : boy.age,
          sex : boy.sex,
          items: boy.items
          })
          
          res.end(strHtml)
          })
          })
          
          server.listen(8080, function(){
          console.log('請訪問http://localhost:8080/,按Ctrl+C終止服務!')
          })

          4、保存所有文件后,在code/04_templatingEngine目錄下執行node 04-useTemplating_engine2.js命令,然后打開瀏覽器并訪問http://localhost:8080,就會看到如下頁面:

          3. 多模版繼承渲染

          當然,如果重復的元素只有頭部和尾部的話,有時候使用模版繼承語法來渲染頁面會是一個更好的選擇,下面就來繼續演示一下art-template模板引擎的繼承語法來渲染 HTML 頁面,其具體步驟如下:

          1. 在code/04_templatingEngine目錄下執行touch baseTpl.art命令,創建父模版文件,然后在該文件中輸入以下代碼:
          <!DOCTYPE html>
          <html lang="zh-cn">
          <head>
          <meta charset="utf-8" />
          <link rel="stylesheet" type="text/css" href="style.css" />
          <title>{{ name }}的個人信息</title>
          </head>
          <body>
          <header>
          <h1>查看個人信息</h1>
          <br>
          </header>
          
          {{ block 'message' }}
          {{ /block }}
          
          <footer>
          <div>
          <p>? 2016 owlman.org;本站系純HTML5站點。</p>
          </div>
          </footer>
          </body>
          </html>

          2、在code/04_templatingEngine目錄下執行touch extendTpl.htm命令,創建子模版文件,然后在該文件中輸入以下代碼:

          {{ extend 'baseTpl.art' }}
          
          {{ block 'message' }}
          <h1>{{ name }}的個人信息</h1>
          <table>
          <tr><td>姓名:</td><td>{{ name }}</td></tr>
          <tr><td>年齡:</td><td>{{ age }}</td></tr>
          <tr><td>性別:</td><td>{{ sex }}</td></tr>
          <tr>
          <td>愛好:</td>
          <td>{{ each items }} {{ $value }} {{ /each }}</td>
          </tr>
          </table>
          {{ /block }}

          3、在code/04_templatingEngine目錄下執行cp 04-useTemplating_engine.js 04-useTemplating_engine3.js命令,將之前的代碼復制一份,并修改如下:

          // 用Node.js生成動態頁面
          // 作者:owlman
          // 時間:2019年07月12日
          
          const http = require('http')
          const fs = require('fs')
          const template = require('art-template')
          
          template.defaults.root = __dirname
          
          class human {
          constructor(name, age, sex, items=[])
          {
          this.name = name
          this.age = age
          this.sex = sex
          this.items = items
          }
          }
          
          const server = http.createServer()
          
          server.on('request', function(req, res) {
          const url = req.url
          let boy = null
          if (url === '/') {
          boy = new human('凌杰', '37', '男', ['看書', '看電影','旅游'])
          } else if (url === '/wang') {
          boy = new human('蔓兒', '25', '女', ['看書', '看電影','寫作'])
          }
          
          if (boy === null) {
          return res.end('<h1>404 頁面沒找到!</h1>')
          }
          
          fs.readFile('./extendTpl.htm', function(err, data) {
          if ( err !== null ) {
          return res.end('<h1>404 沒找到模版文件!</h1>')
          }
          
          const strHtml = template.render(data.toString(), {
          name : boy.name,
          age : boy.age,
          sex : boy.sex,
          items: boy.items
          })
          
          res.end(strHtml)
          })
          })
          
          server.listen(8080, function(){
          console.log('請訪問http://localhost:8080/,按Ctrl+C終止服務!')
          })

          4、保存所有文件后,在code/04_templatingEngine目錄下執行node 04-useTemplating_engine3.js命令,然后打開瀏覽器并訪問http://localhost:8080,就會看到與之前相同的頁面。

          示例5. Web 表單處理

          這一部分示例將致力于演示用 Node.js 處理 Web 表單,我們將會分別示范如何用get和post兩種方法來處理表單的請求。首先,我們要在code目錄下執行mkdir 05_webForm命令來創建用于存放這一組示例的目錄。

          1. get 方法

          先用一個信息查詢程序來演示一下如何處理使用get方法來發送請求的表單。首先,在code/05_webForm目錄下執行mkdir get_form命令,并執行以下步驟:

          在code/05_webForm/get_form目錄下執行npm install art-template命令,將art-template安裝到當前示例項目中。
          在code/05_webForm/get_form目錄下執行touch index.htm,創建一個模版文件,具體如下: <!DOCTYPE html>
          <html lang="zh-cn">
          <head>
          <meta charset="UTF-8">
          <title>個人信息查詢</title>
          </head>
          <body>
          <h1>個人信息查詢</h1>
          <form action="/query" method="GET">
          <label for="message">請輸入要查詢的姓名:</label>
          <input type="text" name="qname" />
          <input type="submit" value="查詢" />
          </form>
          <br />
          {{ if name }}
          <table>
          <caption>{{ name }}的個人信息</caption>
          <tr><td>姓名:</td><td>{{ name }}</td></tr>
          <tr><td>年齡:</td><td>{{ age }}</td></tr>
          <tr><td>性別:</td><td>{{ sex }}</td></tr>
          <tr>
          <td>愛好:</td>
          <td>{{ each items }} {{ $value }} {{ /each }}</td>
          </tr>
          </table>
          {{ else if query_error }}
          <h2>沒有找到相關信息!</h2>
          {{ /if }}
          </body>
          </html>

          3、在code/05_webForm/get_form目錄下執行touch app.js,創建一個腳本文件,具體如下: const http = require('http')

          const fs = require('fs')
          const url = require('url')
          const template = require('art-template')
          
          class human {
          constructor(name, age, sex, items=[])
          {
          this.name = name
          this.age = age
          this.sex = sex
          this.items = items
          }
          }
          
          const db = [
          new human('凌杰', '37', '男', ['看書', '看電影','旅游']),
          new human('蔓兒', '25', '女', ['看書', '看電影','寫作']),
          new human('張語', '32', '女', ['看書', '旅游','繪畫'])
          ]
          
          const server = http.createServer(function(req, res){
          const query = url.parse(req.url, true)
          let obj = null
          let query_error = false
          if ( query.pathname === '/' ) {
          query_error = false
          }
          else if (query.pathname === '/query') {
          for(let i = 0; i < db.length; ++i) {
          if (db[i].name == query.query["qname"]) {
          obj = db[i]
          }
          }
          if ( obj === null ) {
          query_error = true
          }
          } else {
          return res.end('<h1>404 頁面沒找到!</h1>')
          }
          
          fs.readFile('./index.htm', function(err, data){
          if ( err !== null ) {
          return res.end('<h1>404 沒找到模版文件!</h1>')
          }
          
          let strHtml = null
          if ( obj !== null ) {
          strHtml = template.render(data.toString(), {
          name : obj.name,
          age : obj.age,
          sex : obj.sex,
          items: obj.items,
          query_error: query_error
          })
          } else {
          strHtml = template.render(data.toString(), {
          name : false,
          query_error: query_error
          })
          }
          res.end(strHtml)
          })
          })
          
          server.listen(8080, function() {
          console.log('請訪問http://localhost:8080/,按Ctrl+C終止服務!')
          })

          4、保存所有文件后,在code/05_webForm/get_form目錄下執行node app.js命令,結果如下:

          2. post 方法

          先來演示如何處理使用post方法來發送請求的表單。首先,在code/05_webForm目錄下執行mkdir post_form命令,并執行以下步驟:

          1. 在code/05_webForm/get_form目錄下執行npm install art-template命令,將art-template安裝到當前示例項目中。
          2. 在code/05_webForm/post_form目錄下執行touch index.htm,創建一個模版文件,具體如下:
           <!DOCTYPE html>
          <html lang="zh-cn">
          <head>
          <meta charset="UTF-8">
          <title>個人信息管理</title>
          </head>
          <body>
          <h1>個人信息管理</h1>
          <table>
          <caption>個人數據表</caption>
          <tr><th>姓名</th><th>年齡</th><th>性別</th><th>愛好</th></tr>
          {{ each db }}
          <tr>
          <td>{{ $value.name }} </td>
          <td>{{ $value.age }} </td>
          <td>{{ $value.sex }} </td>
          <td>{{ each $value.items }} {{ $value }} {{ /each }}</td>
          </tr>
          {{ /each }}
          </table>
          
          <form action="/add" method="POST">
          <table>
          <caption>錄入新人員</caption>
          <tr><td>姓名:</td><td><input type="text" name="uname" /></td></tr>
          <tr><td>年齡:</td><td><input type="text" name="age"></td></tr>
          <tr><td>性別:</td><td><input type="text" name="sex"></td></tr>
          <tr><td>愛好:</td><td><input type="text" name="items"></td></tr>
          </table>
          <input type="submit" value="添加" />
          </form>
          </body>
          </html>

          3、在code/05_webForm/post_form目錄下執行touch app.js,創建一個腳本文件,具體如下:

          const http = require('http')
          const fs = require('fs')
          const url = require('url')
          const querystring = require('querystring')
          const template = require('art-template')
          
          class human {
          constructor(name, age, sex, items=[])
          {
          this.name = name
          this.age = age
          this.sex = sex
          this.items = items
          }
          }
          
          const db = [
          new human('凌杰', '37', '男', ['看書', '看電影','旅游']),
          new human('蔓兒', '25', '女', ['看書', '看電影','寫作']),
          new human('張語', '32', '女', ['看書', '旅游','繪畫'])
          ]
          
          const server = http.createServer(function(req, res){
          const query = url.parse(req.url, true)
          if ( query.pathname === '/' ) {
          fs.readFile('./index.htm', function(err, data) {
          if ( err !== null ) {
          return res.end('<h1>404 沒找到模版文件!</h1>')
          }
          
          const strHtml = template.render(data.toString(), {
          "db": db
          })
          
          res.end(strHtml)
          })
          }
          else if ( query.pathname === '/add' ) {
          req.on('data', function(chunk) {
          const obj = querystring.parse(chunk.toString())
          db.push(new human(
          obj['uname'],
          obj['age'],
          obj['sex'],
          obj['items'].split(','),
          ))
          })
          
          res.writeHead(302, {
          'location': `/`
          })
          
          res.end()
          } else {
          return res.end('<h1>404 頁面沒找到!</h1>')
          }
          })
          
          server.listen(8080, function(){
          console.log('請訪問http://localhost:8080/,按Ctrl+C終止服務!')
          })

          4、保存所有文件后,在code/05_webForm/post_form目錄下執行node app.js命令,結果如下:

          文講解怎樣用 Node.js 高效地從 Web 爬取數據。

          前提條件

          本文主要針對具有一定 JavaScript 經驗的程序員。如果你對 Web 抓取有深刻的了解,但對 JavaScript 并不熟悉,那么本文仍然能夠對你有所幫助。

          • ? 會 JavaScript
          • ? 會用 DevTools 提取元素選擇器
          • ? 會一些 ES6 (可選)

          你將學到

          通過本文你將學到:

          • 學到更多關于 Node.js 的東西
          • 用多個 HTTP 客戶端來幫助 Web 抓取的過程
          • 利用多個經過實踐考驗過的庫來爬取 Web

          了解 Node.js

          Javascript 是一種簡單的現代編程語言,最初是為了向瀏覽器中的網頁添加動態效果。當加載網站后,Javascript 代碼由瀏覽器的 Javascript 引擎運行。為了使 Javascript 與你的瀏覽器進行交互,瀏覽器還提供了運行時環境(document、window等)。

          這意味著 Javascript 不能直接與計算機資源交互或對其進行操作。例如在 Web 服務器中,服務器必須能夠與文件系統進行交互,這樣才能讀寫文件。

          Node.js 使 Javascript 不僅能夠運行在客戶端,而且還可以運行在服務器端。為了做到這一點,其創始人 Ryan Dahl 選擇了Google Chrome 瀏覽器的 v8 Javascript Engine,并將其嵌入到用 C++ 開發的 Node 程序中。所以 Node.js 是一個運行時環境,它允許 Javascript 代碼也能在服務器上運行。

          與其他語言(例如 C 或 C++)通過多個線程來處理并發性相反,Node.js 利用單個主線程并并在事件循環的幫助下以非阻塞方式執行任務。

          要創建一個簡單的 Web 服務器非常簡單,如下所示:

          const http = require('http');
          const PORT = 3000;
          
          const server = http.createServer((req, res) => {
            res.statusCode = 200;
            res.setHeader('Content-Type', 'text/plain');
            res.end('Hello World');
          });
          
          server.listen(port, () => {
            console.log(`Server running at PORT:${port}/`);
          });

          如果你已安裝了 Node.js,可以試著運行上面的代碼。Node.js 非常適合 I/O 密集型程序。

          HTTP 客戶端:訪問 Web

          HTTP 客戶端是能夠將請求發送到服務器,然后接收服務器響應的工具。下面提到的所有工具底的層都是用 HTTP 客戶端來訪問你要抓取的網站。

          Request

          Request 是 Javascript 生態中使用最廣泛的 HTTP 客戶端之一,但是 Request 庫的作者已正式聲明棄用了。不過這并不意味著它不可用了,相當多的庫仍在使用它,并且非常好用。用 Request 發出 HTTP 請求是非常簡單的:

          const request = require('request')
          request('https://www.reddit.com/r/programming.json', function (  error,
            response,
            body) {
            console.error('error:', error)
            console.log('body:', body)
          })

          你可以在 Github 上找到 Request 庫,安裝它非常簡單。你還可以在 https://github.com/request/request/issues/3142 找到棄用通知及其含義。

          Axios

          Axios 是基于 promise 的 HTTP 客戶端,可在瀏覽器和 Node.js 中運行。如果你用 Typescript,那么 axios 會為你覆蓋內置類型。通過 Axios 發起 HTTP 請求非常簡單,默認情況下它帶有 Promise 支持,而不是在 Request 中去使用回調:

          const axios = require('axios')
          
          axios
           .get('https://www.reddit.com/r/programming.json')
           .then((response) => {
            console.log(response)
           })
           .catch((error) => {
            console.error(error)
           });

          如果你喜歡 Promises API 的 async/await 語法糖,那么你也可以用,但是由于頂級 await 仍處于 stage 3 ,所以我們只好先用異步函數來代替:

          async function getForum() {
           try {
            const response = await axios.get(
             'https://www.reddit.com/r/programming.json'
            )
            console.log(response)
           } catch (error) {
            console.error(error)
           }
          }

          你所要做的就是調用 getForum!可以在 https://github.com/axios/axios 上找到Axios庫。

          Superagent

          與 Axios 一樣,Superagent 是另一個強大的 HTTP 客戶端,它支持 Promise 和 async/await 語法糖。它具有像 Axios 這樣相當簡單的 API,但是 Superagent 由于存在更多的依賴關系并且不那么流行。

          用 promise、async/await 或回調向 Superagent 發出HTTP請求看起來像這樣:

          const superagent = require("superagent")
          const forumURL = "https://www.reddit.com/r/programming.json"
          
          // callbacks
          superagent
           .get(forumURL)
           .end((error, response) => {
            console.log(response)
           })
          
          // promises
          superagent
           .get(forumURL)
           .then((response) => {
            console.log(response)
           })
           .catch((error) => {
            console.error(error)
           })
          
          // promises with async/await
          async function getForum() {
           try {
            const response = await superagent.get(forumURL)
            console.log(response)
           } catch (error) {
            console.error(error)
           }
          }

          可以在 https://github.com/visionmedia/superagent 找到 Superagent。

          正則表達式:艱難的路

          在沒有任何依賴性的情況下,最簡單的進行網絡抓取的方法是,使用 HTTP 客戶端查詢網頁時,在收到的 HTML 字符串上使用一堆正則表達式。正則表達式不那么靈活,而且很多專業人士和業余愛好者都難以編寫正確的正則表達式。

          讓我們試一試,假設其中有一個帶有用戶名的標簽,我們需要該用戶名,這類似于你依賴正則表達式時必須執行的操作

          const htmlString = '<label>Username: John Doe</label>'
          const result = htmlString.match(/<label>(.+)<\/label>/)
          
          console.log(result[1], result[1].split(": ")[1])
          // Username: John Doe, John Doe

          在 Javascript 中,match() 通常返回一個數組,該數組包含與正則表達式匹配的所有內容。第二個元素(在索引1中)將找到我們想要的 <label> 標記的 textContentinnerHTML。但是結果中包含一些不需要的文本( “Username: “),必須將其刪除。

          如你所見,對于一個非常簡單的用例,步驟和要做的工作都很多。這就是為什么應該依賴 HTML 解析器的原因,我們將在后面討論。

          Cheerio:用于遍歷 DOM 的核心 JQuery

          Cheerio 是一個高效輕便的庫,它使你可以在服務器端使用 JQuery 的豐富而強大的 API。如果你以前用過 JQuery,那么將會對 Cheerio 感到很熟悉,它消除了 DOM 所有不一致和與瀏覽器相關的功能,并公開了一種有效的 API 來解析和操作 DOM。

          const cheerio = require('cheerio')
          const $ = cheerio.load('<h2 class="title">Hello world</h2>')
          
          $('h2.title').text('Hello there!')
          $('h2').addClass('welcome')
          
          $.html()
          // <h2 class="title welcome">Hello there!</h2>

          如你所見,Cheerio 與 JQuery 用起來非常相似。

          但是,盡管它的工作方式不同于網絡瀏覽器,也就這意味著它不能:

          • 渲染任何解析的或操縱 DOM 元素
          • 應用 CSS 或加載外部資源
          • 執行 JavaScript

          因此,如果你嘗試爬取的網站或 Web 應用是嚴重依賴 Javascript 的(例如“單頁應用”),那么 Cheerio 并不是最佳選擇,你可能不得不依賴稍后討論的其他選項。

          為了展示 Cheerio 的強大功能,我們將嘗試在 Reddit 中抓取 r/programming 論壇,嘗試獲取帖子名稱列表。

          首先,通過運行以下命令來安裝 Cheerio 和 axios:npm install cheerio axios

          然后創建一個名為 crawler.js 的新文件,并復制粘貼以下代碼:

          const axios = require('axios');
          const cheerio = require('cheerio');
          
          const getPostTitles = async () => {
           try {
            const { data } = await axios.get(
             'https://old.reddit.com/r/programming/'
            );
            const $ = cheerio.load(data);
            const postTitles = [];
          
            $('div > p.title > a').each((_idx, el) => {
             const postTitle = $(el).text()
             postTitles.push(postTitle)
            });
          
            return postTitles;
           } catch (error) {
            throw error;
           }
          };
          
          getPostTitles()
          .then((postTitles) => console.log(postTitles));

          getPostTitles() 是一個異步函數,將對舊的 reddit 的 r/programming 論壇進行爬取。首先,用帶有 axios HTTP 客戶端庫的簡單 HTTP GET 請求獲取網站的 HTML,然后用 cheerio.load() 函數將 html 數據輸入到 Cheerio 中。

          然后在瀏覽器的 Dev Tools 幫助下,可以獲得可以定位所有列表項的選擇器。如果你使用過 JQuery,則必須非常熟悉 $('div> p.title> a')。這將得到所有帖子,因為你只希望單獨獲取每個帖子的標題,所以必須遍歷每個帖子,這些操作是在 each() 函數的幫助下完成的。

          要從每個標題中提取文本,必須在 Cheerio 的幫助下獲取 DOM元素( el 指代當前元素)。然后在每個元素上調用 text() 能夠為你提供文本。

          現在,打開終端并運行 node crawler.js,然后你將看到大約存有標題的數組,它會很長。盡管這是一個非常簡單的用例,但它展示了 Cheerio 提供的 API 的簡單性質。

          如果你的用例需要執行 Javascript 并加載外部源,那么以下幾個選項將很有幫助。

          JSDOM:Node 的 DOM

          JSDOM 是在 Node.js 中使用的文檔對象模型的純 Javascript 實現,如前所述,DOM 對 Node 不可用,但是 JSDOM 是最接近的。它或多或少地模仿了瀏覽器。

          由于創建了 DOM,所以可以通過編程與要爬取的 Web 應用或網站進行交互,也可以模擬單擊按鈕。如果你熟悉 DOM 操作,那么使用 JSDOM 將會非常簡單。

          const { JSDOM } = require('jsdom')
          const { document } = new JSDOM(
           '<h2 class="title">Hello world</h2>'
          ).window
          const heading = document.querySelector('.title')
          heading.textContent = 'Hello there!'
          heading.classList.add('welcome')
          
          heading.innerHTML
          // <h2 class="title welcome">Hello there!</h2>

          代碼中用 JSDOM 創建一個 DOM,然后你可以用和操縱瀏覽器 DOM 相同的方法和屬性來操縱該 DOM。

          為了演示如何用 JSDOM 與網站進行交互,我們將獲得 Reddit r/programming 論壇的第一篇帖子并對其進行投票,然后驗證該帖子是否已被投票。

          首先運行以下命令來安裝 jsdom 和 axios:npm install jsdom axios

          然后創建名為 crawler.js的文件,并復制粘貼以下代碼:

          const { JSDOM } = require("jsdom")
          const axios = require('axios')
          
          const upvoteFirstPost = async () => {
            try {
              const { data } = await axios.get("https://old.reddit.com/r/programming/");
              const dom = new JSDOM(data, {
                runScripts: "dangerously",
                resources: "usable"
              });
              const { document } = dom.window;
              const firstPost = document.querySelector("div > div.midcol > div.arrow");
              firstPost.click();
              const isUpvoted = firstPost.classList.contains("upmod");
              const msg = isUpvoted
                ? "Post has been upvoted successfully!"
                : "The post has not been upvoted!";
          
              return msg;
            } catch (error) {
              throw error;
            }
          };
          
          upvoteFirstPost().then(msg => console.log(msg));

          upvoteFirstPost() 是一個異步函數,它將在 r/programming 中獲取第一個帖子,然后對其進行投票。axios 發送 HTTP GET 請求獲取指定 URL 的HTML。然后通過先前獲取的 HTML 來創建新的 DOM。JSDOM 構造函數把HTML 作為第一個參數,把 option 作為第二個參數,已添加的 2 個 option 項執行以下功能:

          • runScripts:設置為 dangerously 時允許執行事件 handler 和任何 Javascript 代碼。如果你不清楚將要運行的腳本的安全性,則最好將 runScripts 設置為“outside-only”,這會把所有提供的 Javascript 規范附加到 “window” 對象,從而阻止在 inside 上執行的任何腳本。
          • resources:設置為“usable”時,允許加載用 <script> 標記聲明的任何外部腳本(例如:從 CDN 提取的 JQuery 庫)

          創建 DOM 后,用相同的 DOM 方法得到第一篇文章的 upvote 按鈕,然后單擊。要驗證是否確實單擊了它,可以檢查 classList 中是否有一個名為 upmod 的類。如果存在于 classList 中,則返回一條消息。

          打開終端并運行 node crawler.js,然后會看到一個整潔的字符串,該字符串將表明帖子是否被贊過。盡管這個例子很簡單,但你可以在這個基礎上構建功能強大的東西,例如,一個圍繞特定用戶的帖子進行投票的機器人。

          如果你不喜歡缺乏表達能力的 JSDOM ,并且實踐中要依賴于許多此類操作,或者需要重新創建許多不同的 DOM,那么下面將是更好的選擇。

          Puppeteer:無頭瀏覽器

          顧名思義,Puppeteer 允許你以編程方式操縱瀏覽器,就像操縱木偶一樣。它通過為開發人員提供高級 API 來默認控制無頭版本的 Chrome。

          Puppeteer 比上述工具更有用,因為它可以使你像真正的人在與瀏覽器進行交互一樣對網絡進行爬取。這就具備了一些以前沒有的可能性:

          • 你可以獲取屏幕截圖或生成頁面 PDF。
          • 可以抓取單頁應用并生成預渲染的內容。
          • 自動執行許多不同的用戶交互,例如鍵盤輸入、表單提交、導航等。

          它還可以在 Web 爬取之外的其他任務中發揮重要作用,例如 UI 測試、輔助性能優化等。

          通常你會想要截取網站的屏幕截圖,也許是為了了解競爭對手的產品目錄,可以用 puppeteer 來做到。首先運行以下命令安裝 puppeteer,:npm install puppeteer

          這將下載 Chromium 的 bundle 版本,根據操作系統的不同,該版本大約 180 MB 至 300 MB。如果你要禁用此功能。

          讓我們嘗試在 Reddit 中獲取 r/programming 論壇的屏幕截圖和 PDF,創建一個名為 crawler.js的新文件,然后復制粘貼以下代碼:

          const puppeteer = require('puppeteer')
          
          async function getVisual() {
           try {
            const URL = 'https://www.reddit.com/r/programming/'
            const browser = await puppeteer.launch()
            const page = await browser.newPage()
          
            await page.goto(URL)
            await page.screenshot({ path: 'screenshot.png' })
            await page.pdf({ path: 'page.pdf' })
          
            await browser.close()
           } catch (error) {
            console.error(error)
           }
          }
          
          getVisual()

          getVisual() 是一個異步函數,它將獲 URL 變量中 url 對應的屏幕截圖和 pdf。首先,通過 puppeteer.launch() 創建瀏覽器實例,然后創建一個新頁面。可以將該頁面視為常規瀏覽器中的選項卡。然后通過以 URL 為參數調用 page.goto() ,將先前創建的頁面定向到指定的 URL。最終,瀏覽器實例與頁面一起被銷毀。

          完成操作并完成頁面加載后,將分別使用 page.screenshot()page.pdf() 獲取屏幕截圖和 pdf。你也可以偵聽 javascript load 事件,然后執行這些操作,在生產環境級別下強烈建議這樣做。

          在終端上運行 node crawler.js ,幾秒鐘后,你會注意到已經創建了兩個文件,分別名為 screenshot.jpgpage.pdf

          Nightmare:Puppeteer 的替代者

          Nightmare 是類似 Puppeteer 的高級瀏覽器自動化庫,該庫使用 Electron,但據說速度是其前身 PhantomJS 的兩倍。

          如果你在某種程度上不喜歡 Puppeteer 或對 Chromium 捆綁包的大小感到沮喪,那么 nightmare 是一個理想的選擇。首先,運行以下命令安裝 nightmare 庫:npm install nightmare

          然后,一旦下載了 nightmare,我們將用它通過 Google 搜索引擎找到 ScrapingBee 的網站。創建一個名為crawler.js的文件,然后將以下代碼復制粘貼到其中:

          const Nightmare = require('nightmare')
          const nightmare = Nightmare()
          
          nightmare
           .goto('https://www.google.com/')
           .type("input[title='Search']", 'ScrapingBee')
           .click("input[value='Google Search']")
           .wait('#rso > div:nth-child(1) > div > div > div.r > a')
           .evaluate(
            () =>
             document.querySelector(
              '#rso > div:nth-child(1) > div > div > div.r > a'
             ).href
           )
           .end()
           .then((link) => {
            console.log('Scraping Bee Web Link': link)
           })
           .catch((error) => {
            console.error('Search failed:', error)
           })

          首先創建一個 Nighmare 實例,然后通過調用 goto() 將該實例定向到 Google 搜索引擎,加載后,使用其選擇器獲取搜索框,然后使用搜索框的值(輸入標簽)更改為“ScrapingBee”。完成后,通過單擊 “Google搜索” 按鈕提交搜索表單。然后告訴 Nightmare 等到第一個鏈接加載完畢,一旦完成,它將使用 DOM 方法來獲取包含該鏈接的定位標記的 href 屬性的值。

          最后,完成所有操作后,鏈接將打印到控制臺。

          總結

          • ? Node.js 是 Javascript 在服務器端的運行時環境。由于事件循環機制,它具有“非阻塞”性質。
          • ? HTTP客戶端(例如 Axios、Superagent 和 Request)用于將 HTTP 請求發送到服務器并接收響應。
          • ? CheerioJQuery 的優點抽出來,在服務器端 進行 Web 爬取是唯一的目的,但不執行 Javascript 代碼。
          • ? JSDOM 根據標準 Javascript規范 從 HTML 字符串中創建一個 DOM,并允許你對其執行DOM操作。
          • ? Puppeteer and Nightmare高級(high-level )瀏覽器自動化庫,可讓你以編程方式去操作 Web 應用,就像真實的人正在與之交互一樣。

          *若有侵權請聯系刪除,僅提供學習*/

          var clearLink,excludedTags,filter,linkMixInit,linkPack,linkify,observePage,observer,setLink,url_regexp,xpath;url_regexp=/((https?:\/\/|www\.)[\x21-\x7e]+[\w\/]|(\w[\w._-]+\.(com|cn|org|net|info|tv|cc))(\/[\x21-\x7e]*[\w\/])?|ed2k:\/\/[\x21-\x7e]+\|\/|thunder:\/\/[\x21-\x7e]+=)/gi;

          clearLink=function(a){var b;a=null!=(b=a.originalTarget)?b:a.target;if(null!=a&&"a"===a.localName&&-1!==a.className.indexOf("texttolink")&&(b=a.getAttribute("href"),0!==b.indexOf("http")&&0!==b.indexOf("ed2k://")&&0!==b.indexOf("thunder://")))return a.setAttribute("href","http://"+b)};document.addEventListener("mouseover",clearLink);

          setLink=function(a){if(null!=a&&-1===a.parentNode.className.indexOf("texttolink")&&"#cdata-section"!==a.nodeName){var b=a.textContent.replace(url_regexp,'<a href="" target="_blank" class="texttolink"></a>');if(a.textContent.length!==b.length){var c=document.createElement("span");c.innerHTML=b;return a.parentNode.replaceChild(c,a)}}};excludedTags="a svg canvas applet input button area pre embed frame frameset head iframe img option map meta noscript object script style textarea code".split(" ");

          xpath="//text()[not(ancestor::"+excludedTags.join(") and not(ancestor::")+")]";filter=new RegExp("^("+excludedTags.join("|")+")$","i");linkPack=function(a,b){var c,d;if(b+1E4<a.snapshotLength){var e=c=b;for(d=b+1E4;b<=d?c<=d:c>=d;e=b<=d?++c:--c)setLink(a.snapshotItem(e));setTimeout(function(){return linkPack(a,b+1E4)},15)}else for(e=c=b,d=a.snapshotLength;b<=d?c<=d:c>=d;e=b<=d?++c:--c)setLink(a.snapshotItem(e))};

          linkify=function(a){a=document.evaluate(xpath,a,null,XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,null);return linkPack(a,0)};observePage=function(a){for(a=document.createTreeWalker(a,NodeFilter.SHOW_TEXT,{acceptNode:function(a){if(!filter.test(a.parentNode.localName))return NodeFilter.FILTER_ACCEPT}},!1);a.nextNode();)setLink(a.currentNode)};

          observer=new window.MutationObserver(function(a){var b,c;var d=0;for(b=a.length;d<b;d++){var e=a[d];if("childList"===e.type){var g=e.addedNodes;var f=0;for(c=g.length;f<c;f++)e=g[f],observePage(e)}}});linkMixInit=function(){if(window===window.top&&""!==window.document.title)return linkify(document.body),observer.observe(document.body,{childList:!0,subtree:!0})};

          var clearlinkF=function(a){url=a.getAttribute("href");if(0!==url.indexOf("http")&&0!==url.indexOf("ed2k://")&&0!==url.indexOf("thunder://"))return a.setAttribute("href","http://"+url)},clearlinkE=function(){for(var a=document.getElementsByClassName("texttolink"),b=0;b<a.length;b++)clearlinkF(a[b])};setTimeout(clearlinkE,1500);setTimeout(linkMixInit,100);


          主站蜘蛛池模板: 成人免费观看一区二区| 国产精品一区二区三区高清在线| 亚洲国产精品一区二区久久hs| 精品国产AⅤ一区二区三区4区 | 一区二区乱子伦在线播放| 亚洲av无码一区二区三区不卡| 国产色欲AV一区二区三区| 国产伦理一区二区| 激情内射日本一区二区三区| 日韩在线视频一区二区三区| 国产成人精品第一区二区| 免费精品一区二区三区第35| 精品视频一区二区三区| 中文人妻无码一区二区三区| 亚洲av综合av一区| 久久中文字幕无码一区二区 | 3D动漫精品一区二区三区| 亚洲av无码片vr一区二区三区| 精品国产福利第一区二区三区| 精品乱码一区二区三区四区| 色婷婷AV一区二区三区浪潮| 无遮挡免费一区二区三区| 一级特黄性色生活片一区二区| 视频一区二区中文字幕| 免费萌白酱国产一区二区三区| 秋霞鲁丝片一区二区三区| 在线免费观看一区二区三区| 一区二区三区在线|欧| 在线视频一区二区三区三区不卡| jazzjazz国产精品一区二区| 国产成人精品无码一区二区三区 | 国产一区二区三区影院| 日韩伦理一区二区| 91无码人妻精品一区二区三区L| 亚洲日本一区二区三区在线| 日产精品久久久一区二区| 蜜臀AV一区二区| 搜日本一区二区三区免费高清视频 | 国产伦一区二区三区高清| 肥臀熟女一区二区三区| 无码一区二区三区免费|