1. 寫在前面 往常都是利用 Python/.NET 語言實現爬蟲,然現在作為一名前端開發人員,自然需要熟練 NodeJS。下麵利用 NodeJS 語言實現一個糗事百科的爬蟲。另外,本文使用的部分代碼是 es6 語法。 實現該爬蟲所需要的依賴庫如下。 1. request: 利用 get 或者 po ...
1. 寫在前面
往常都是利用 Python/.NET 語言實現爬蟲,然現在作為一名前端開發人員,自然需要熟練 NodeJS。下麵利用 NodeJS 語言實現一個糗事百科的爬蟲。另外,本文使用的部分代碼是 es6 語法。
實現該爬蟲所需要的依賴庫如下。
- request: 利用 get 或者 post 等方法獲取網頁的源碼。
- cheerio: 對網頁源碼進行解析,獲取所需數據。
本文首先對爬蟲所需依賴庫及其使用進行介紹,然後利用這些依賴庫,實現一個針對糗事百科的網路爬蟲。
2. request 庫
request 是一個輕量級的 http 庫,功能十分強大且使用簡單。可以使用它實現 Http 的請求,並且支持 HTTP 認證, 自定請求頭等。下麵對 request 庫中一部分功能進行介紹。
安裝 request 模塊如下:
npm install request
在安裝好 request 後,即可進行使用,下麵利用 request 請求一下百度的網頁。
const req = require('request');
req('http://www.baidu.com', (error, response, body) => {
if (!error && response.statusCode == 200) {
console.log(body)
}
})
在沒有設置 options 參數時,request 方法預設是 get 請求。而我喜歡利用 request 對象的具體方法,使用如下:
req.get({
url: 'http://www.baidu.com'
},(err, res, body) => {
if (!err && res.statusCode == 200) {
console.log(body)
}
});
然而很多時候,直接去請求一個網址所獲取的 html 源碼,往往得不到我們需要的信息。一般情況下,需要考慮到請求頭和網頁編碼。
- 網頁的請求頭
- 網頁的編碼
下麵介紹在請求的時候如何添加網頁請求頭以及設置正確的編碼。
req.get({
url : url,
headers: {
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36",
"Host" : "www.zhihu.com",
"Upgrade-Insecure-Requests" : "1"
},
encoding : 'utf-8'
}, (err, res, body)=>{
if(!err)
console.log(body);
})
設置 options 參數, 添加 headers
屬性即可實現請求頭的設置;添加 encoding
屬性即可設置網頁的編碼。需要註意的是,若 encoding:null
,那麼 get 請求所獲取的內容則是一個 Buffer
對象,即 body 是一個 Buffer 對象。
上面介紹的功能足矣滿足後面的所需了,更多功能請參看官網的文檔 request
3. cheerio 庫
cheerio 是一款伺服器端的 Jquery,以輕、快、簡單易學等特點被開發者喜愛。有 Jquery 的基礎後再來學習 cheerio 庫非常輕鬆。它能夠快速定位到網頁中的元素,其規則和 Jquery 定位元素的方法是一樣的;它也能以一種非常方便的形式修改 html 中的元素內容,以及獲取它們的數據。下麵主要針對 cheerio 快速定位網頁中的元素,以及獲取它們的內容進行介紹。
首先安裝 cheerio 庫
npm install cheerio
下麵先給出一段代碼,再對代碼進行解釋 cheerio 庫的用法。對博客園首頁進行分析,然後提取每一頁中文章的標題。
首先對博客園首頁進行分析。如下圖:
對 html 源代碼進行分析後,首先通過 .post_item
獲取所有標題,接著對每一個 .post_item
進行分析,使用 a.titlelnk
即可匹配每個標題的 a 標簽。下麵通過代碼進行實現。
const req = require('request');
const cheerio = require('cheerio');
req.get({
url: 'https://www.cnblogs.com/'
}, (err, res, body) => {
if (!err && res.statusCode == 200) {
let cnblogHtmlStr = body;
let $ = cheerio.load(cnblogHtmlStr);
$('.post_item').each((index, ele) => {
let title = $(ele).find('a.titlelnk');
let titleText = title.text();
let titletUrl = title.attr('href');
console.log(titleText, titletUrl);
});
}
});
當然,cheerio 庫也支持鏈式調用,上面的代碼也可改寫成:
let cnblogHtmlStr = body;
let $ = cheerio.load(cnblogHtmlStr);
let titles = $('.post_item').find('a.titlelnk');
titles.each((index, ele) => {
let titleText = $(ele).text();
let titletUrl = $(ele).attr('href');
console.log(titleText, titletUrl);
上面的代碼非常簡單,就不再用文字進行贅述了。下麵總結一點自己認為比較重要的幾點。
- 使用
find()
方法獲取的節點集合 A,若再次以 A 集合中的元素為根節點定位它的子節點以及獲取子元素的內容與屬性,需對 A 集合中的子元素進行$(A[i])
包裝,如上面的$(ele)
一樣。 - 在上面代碼中使用
$(ele)
,其實還可以使用$(this)
但是由於我使用的是 es6 的箭頭函數,因此改變了each
方法中回調函數的 this 指針,因此,我使用$(ele)
; - cheerio 庫也支持鏈式調用,如上面的
$('.post_item').find('a.titlelnk')
,需要註意的是,cheerio 對象 A 調用方法find()
,如果 A 是一個集合,那麼 A 集合中的每一個子元素都調用find()
方法,並放回一個結果結合。如果 A 調用text()
,那麼 A 集合中的每一個子元素都調用text()
並返回一個字元串,該字元串是所有子元素內容的合併(直接合併,沒有分隔符)。
最後在總結一些我比較常用的方法。
- first()
- last()
- children([selector]): 該方法和 find 類似,只不過該方法只搜索子節點,而 find 搜索整個後代節點。
關於更多 cheerio 庫的用法,請參考文檔 cheerio
4. 糗事百科爬蟲
通過上面對 request
和 cheerio
類庫的介紹,下麵利用這兩個類庫對糗事百科的頁面進行爬取。
1、在項目目錄中,新建 httpHelper.js
文件,通過 url 獲取糗事百科的網頁源碼,代碼如下:
//爬蟲
const req = require('request');
function getHtml(url){
return new Promise((resolve, reject) => {
req.get({
url : url,
headers: {
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
"Referer" : "https://www.qiushibaike.com/"
},
encoding : 'utf-8'
}, (err, res, body)=>{
if(err) reject(err);
else resolve(body);
})
});
}
exports.getHtml = getHtml;
2、在項目目錄中,新建一個 Splider.js
文件,分析糗事百科的網頁代碼,提取自己需要的信息,並且建立一個邏輯通過更改 url 的 id 來爬取不同頁面的數據。
const cheerio = require('cheerio');
const httpHelper = require('./httpHelper');
function getQBJok(htmlStr){
let $ = cheerio.load(htmlStr);
let jokList = $('#content-left').children('div');
let rst = [];
jokList.each((i, item)=>{
let node = $(item);
let titleNode = node.find('h2');
let title = titleNode ? titleNode.text().trim() : '匿名用戶';
let content = node.find('.content span').text().trim();
let likeNumber = node.find('i[class=number]').text().trim();
rst.push({
title : title,
content : content,
likeNumber : likeNumber
});
});
return rst;
}
async function splider(index = 1){
let url = `https://www.qiushibaike.com/8hr/page/${index}/`;
let htmlStr = await httpHelper.getHtml(url);
let rst = getQBJok(htmlStr);
return rst;
}
splider(1);
在獲取糗事百科網頁信息的時候,首先在瀏覽器中對源碼進行分析,定位到自己所需要標簽,然後提取標簽的文本或者屬性值,這樣就完成了網頁的解析。
Splider.js
文件入口是 splider
方法,首先根據傳入該方法的 index 索引,構造糗事百科的 url,接著獲取該 url 的網頁源碼,最後將獲取的源碼傳入 getQBJok
方法,進行解析,本文只解析每條文本笑話的作者、內容以及喜歡個數。
直接運行 Splider.js
文件,即可爬取第一頁的笑話信息。然後可以更改 splider
方法的參數,實現抓取不同頁面的信息。
在上面已有代碼的基礎上,使用 koa
和 vue2.0
搭建一個瀏覽文本的頁面,效果如下:
源碼已上傳到 github 上。下載地址:https://github.com/StartAction/SpliderQB ;
項目運行依賴 node v7.6.0
以上, 首先從 Github 上面克隆整個項目。
git clone https://github.com/StartAction/SpliderQB.git
克隆之後,進入項目目錄,運行下麵命令即可。
node app.js
5. 總結
通過實現一個完整的爬蟲功能,加深自己對 Node
的理解,且實現的部分語言都是使用 es6
的語法,讓自己加快對 es6
語法的學習進度。另外,在這次實現中,遇到了 Node
的非同步控制的知識,本文是採用的是 async
和 await
關鍵字,也是我最喜歡的一種,然而在 Node
中,實現非同步控制有好幾種方式。關於具體的方式以及原理,有時間再進行總結。