1.前言分析
往常都是利用 Python/.NET 語(yǔ)言實(shí)現(xiàn)爬蟲,然現(xiàn)在作為一名前端開發(fā)人員,自然需要熟練 NodeJS。下面利用 NodeJS 語(yǔ)言實(shí)現(xiàn)一個(gè)糗事百科的爬蟲。另外,本文使用的部分代碼是 es6 語(yǔ)法。
實(shí)現(xiàn)該爬蟲所需要的依賴庫(kù)如下。
request: 利用 get 或者 post 等方法獲取網(wǎng)頁(yè)的源碼。 cheerio: 對(duì)網(wǎng)頁(yè)源碼進(jìn)行解析,獲取所需數(shù)據(jù)。
本文首先對(duì)爬蟲所需依賴庫(kù)及其使用進(jìn)行介紹,然后利用這些依賴庫(kù),實(shí)現(xiàn)一個(gè)針對(duì)糗事百科的網(wǎng)絡(luò)爬蟲。
2. request 庫(kù)
request 是一個(gè)輕量級(jí)的 http 庫(kù),功能十分強(qiáng)大且使用簡(jiǎn)單??梢允褂盟鼘?shí)現(xiàn) Http 的請(qǐng)求,并且支持 HTTP 認(rèn)證, 自定請(qǐng)求頭等。下面對(duì) request 庫(kù)中一部分功能進(jìn)行介紹。
安裝 request 模塊如下:
npm install request
在安裝好 request 后,即可進(jìn)行使用,下面利用 request 請(qǐng)求一下百度的網(wǎng)頁(yè)。
const req = require('request'); req('http://www.baidu.com', (error, response, body) => { if (!error && response.statusCode == 200) { console.log(body) } })
在沒有設(shè)置 options 參數(shù)時(shí),request 方法默認(rèn)是 get 請(qǐng)求。而我喜歡利用 request 對(duì)象的具體方法,使用如下:
req.get({ url: 'http://www.baidu.com' },(err, res, body) => { if (!err && res.statusCode == 200) { console.log(body) } });
然而很多時(shí)候,直接去請(qǐng)求一個(gè)網(wǎng)址所獲取的 html 源碼,往往得不到我們需要的信息。一般情況下,需要考慮到請(qǐng)求頭和網(wǎng)頁(yè)編碼。
網(wǎng)頁(yè)的請(qǐng)求頭網(wǎng)頁(yè)的編碼
下面介紹在請(qǐng)求的時(shí)候如何添加網(wǎng)頁(yè)請(qǐng)求頭以及設(shè)置正確的編碼。
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); })
設(shè)置 options 參數(shù), 添加 headers
屬性即可實(shí)現(xiàn)請(qǐng)求頭的設(shè)置;添加 encoding
屬性即可設(shè)置網(wǎng)頁(yè)的編碼。需要注意的是,若 encoding:null
,那么 get 請(qǐng)求所獲取的內(nèi)容則是一個(gè) Buffer
對(duì)象,即 body 是一個(gè) Buffer 對(duì)象。
上面介紹的功能足矣滿足后面的所需了
3. cheerio 庫(kù)
cheerio 是一款服務(wù)器端的 Jquery,以輕、快、簡(jiǎn)單易學(xué)等特點(diǎn)被開發(fā)者喜愛。有 Jquery 的基礎(chǔ)后再來學(xué)習(xí) cheerio 庫(kù)非常輕松。它能夠快速定位到網(wǎng)頁(yè)中的元素,其規(guī)則和 Jquery 定位元素的方法是一樣的;它也能以一種非常方便的形式修改 html 中的元素內(nèi)容,以及獲取它們的數(shù)據(jù)。下面主要針對(duì) cheerio 快速定位網(wǎng)頁(yè)中的元素,以及獲取它們的內(nèi)容進(jìn)行介紹。
首先安裝 cheerio 庫(kù)
npm install cheerio
下面先給出一段代碼,再對(duì)代碼進(jìn)行解釋 cheerio 庫(kù)的用法。對(duì)博客園首頁(yè)進(jìn)行分析,然后提取每一頁(yè)中文章的標(biāo)題。
首先對(duì)博客園首頁(yè)進(jìn)行分析。如下圖:
對(duì) html 源代碼進(jìn)行分析后,首先通過 .post_item
獲取所有標(biāo)題,接著對(duì)每一個(gè) .post_item
進(jìn)行分析,使用 a.titlelnk
即可匹配每個(gè)標(biāo)題的 a 標(biāo)簽。下面通過代碼進(jìn)行實(shí)現(xiàn)。
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); }); } });
當(dāng)然,cheerio 庫(kù)也支持鏈?zhǔn)秸{(diào)用,上面的代碼也可改寫成:
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);
上面的代碼非常簡(jiǎn)單,就不再用文字進(jìn)行贅述了。下面總結(jié)一點(diǎn)自己認(rèn)為比較重要的幾點(diǎn)。
使用 find()
方法獲取的節(jié)點(diǎn)集合 A,若再次以 A 集合中的元素為根節(jié)點(diǎn)定位它的子節(jié)點(diǎn)以及獲取子元素的內(nèi)容與屬性,需對(duì) A 集合中的子元素進(jìn)行 $(A[i])
包裝,如上面的$(ele)
一樣。在上面代碼中使用 $(ele)
,其實(shí)還可以使用 $(this)
但是由于我使用的是 es6 的箭頭函數(shù),因此改變了 each
方法中回調(diào)函數(shù)的 this 指針,因此,我使用 $(ele)
; cheerio 庫(kù)也支持鏈?zhǔn)秸{(diào)用,如上面的 $('.post_item').find('a.titlelnk')
,需要注意的是,cheerio 對(duì)象 A 調(diào)用方法 find()
,如果 A 是一個(gè)集合,那么 A 集合中的每一個(gè)子元素都調(diào)用 find()
方法,并放回一個(gè)結(jié)果結(jié)合。如果 A 調(diào)用 text()
,那么 A 集合中的每一個(gè)子元素都調(diào)用 text()
并返回一個(gè)字符串,該字符串是所有子元素內(nèi)容的合并(直接合并,沒有分隔符)。
最后在總結(jié)一些我比較常用的方法。
first() last() children([selector]): 該方法和 find 類似,只不過該方法只搜索子節(jié)點(diǎn),而 find 搜索整個(gè)后代節(jié)點(diǎn)。
4. 糗事百科爬蟲
通過上面對(duì) request
和 cheerio
類庫(kù)的介紹,下面利用這兩個(gè)類庫(kù)對(duì)糗事百科的頁(yè)面進(jìn)行爬取。
1、在項(xiàng)目目錄中,新建 httpHelper.js
文件,通過 url 獲取糗事百科的網(wǎng)頁(yè)源碼,代碼如下:
//爬蟲 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、在項(xiàng)目目錄中,新建一個(gè) Splider.js
文件,分析糗事百科的網(wǎng)頁(yè)代碼,提取自己需要的信息,并且建立一個(gè)邏輯通過更改 url 的 id 來爬取不同頁(yè)面的數(shù)據(jù)。
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);
在獲取糗事百科網(wǎng)頁(yè)信息的時(shí)候,首先在瀏覽器中對(duì)源碼進(jìn)行分析,定位到自己所需要標(biāo)簽,然后提取標(biāo)簽的文本或者屬性值,這樣就完成了網(wǎng)頁(yè)的解析。
Splider.js
文件入口是 splider
方法,首先根據(jù)傳入該方法的 index 索引,構(gòu)造糗事百科的 url,接著獲取該 url 的網(wǎng)頁(yè)源碼,最后將獲取的源碼傳入 getQBJok
方法,進(jìn)行解析,本文只解析每條文本笑話的作者、內(nèi)容以及喜歡個(gè)數(shù)。
直接運(yùn)行 Splider.js
文件,即可爬取第一頁(yè)的笑話信息。然后可以更改 splider
方法的參數(shù),實(shí)現(xiàn)抓取不同頁(yè)面的信息。
在上面已有代碼的基礎(chǔ)上,使用 koa
和 vue2.0
搭建一個(gè)瀏覽文本的頁(yè)面,效果如下:
源碼已上傳到 github 上。下載地址:https://github.com/StartAction/SpliderQB ;
項(xiàng)目運(yùn)行依賴 node v7.6.0
以上, 首先從 Github 上面克隆整個(gè)項(xiàng)目。
git clone https://github.com/StartAction/SpliderQB.git
克隆之后,進(jìn)入項(xiàng)目目錄,運(yùn)行下面命令即可。
node app.js
5. 總結(jié)
通過實(shí)現(xiàn)一個(gè)完整的爬蟲功能,加深自己對(duì) Node
的理解,且實(shí)現(xiàn)的部分語(yǔ)言都是使用 es6
的語(yǔ)法,讓自己加快對(duì) es6
語(yǔ)法的學(xué)習(xí)進(jìn)度。另外,在這次實(shí)現(xiàn)中,遇到了 Node
的異步控制的知識(shí),本文是采用的是 async
和 await
關(guān)鍵字,也是我最喜歡的一種,然而在 Node
中,實(shí)現(xiàn)異步控制有好幾種方式。關(guān)于具體的方式以及原理,有時(shí)間再進(jìn)行總結(jié)。
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com