最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

從零學(xué)習(xí)node.js之簡(jiǎn)易的網(wǎng)絡(luò)爬蟲(chóng)(四)

 更新時(shí)間:2017年02月22日 09:30:53   作者:Wenzi  
簡(jiǎn)單的爬蟲(chóng)實(shí)現(xiàn)原理很簡(jiǎn)單:發(fā)送http請(qǐng)求至目標(biāo)地址獲取HTML頁(yè)面數(shù)據(jù),然后從獲取來(lái)的頁(yè)面數(shù)據(jù)中提取需要的數(shù)據(jù)保存。下面這篇文章主要介紹了利用node.js實(shí)現(xiàn)簡(jiǎn)易的網(wǎng)絡(luò)爬蟲(chóng)的相關(guān)資料,需要的朋友可以參考下。

前言

之前已經(jīng)介紹了node.js的一些基本知識(shí),下面這篇文章我們的目標(biāo)是學(xué)習(xí)完本節(jié)課程后,能進(jìn)行網(wǎng)頁(yè)簡(jiǎn)單的分析與抓取,對(duì)抓取到的信息進(jìn)行輸出和文本保存。

爬蟲(chóng)的思路很簡(jiǎn)單:

  1. 確定要抓取的URL;
  2. 對(duì)URL進(jìn)行抓取,獲取網(wǎng)頁(yè)內(nèi)容;
  3. 對(duì)內(nèi)容進(jìn)行分析并存儲(chǔ);
  4. 重復(fù)第1步

在這節(jié)里做爬蟲(chóng),我們使用到了兩個(gè)重要的模塊:

  • request : 對(duì)http進(jìn)行封裝,提供更多、更方便的接口供我們使用,request進(jìn)行的是異步請(qǐng)求。更多信息可以去這篇文章上進(jìn)行查看
  • cheerio : 類(lèi)似于jQuery,可以使用$(), find(), text(), html()等方法提取頁(yè)面中的元素和數(shù)據(jù),不過(guò)若仔細(xì)比較起來(lái),cheerio中的方法不如jQuery的多。

一、 hello world

說(shuō)是hello world,其實(shí)首先開(kāi)始的是最簡(jiǎn)單的抓取。我們就以cnode網(wǎng)站為例(https://cnodejs.org/),這個(gè)網(wǎng)站的特點(diǎn)是:

  1. 不需要登錄即可訪問(wèn)首頁(yè)和其他頁(yè)面
  2. 頁(yè)面都是同步渲染的,沒(méi)有異步請(qǐng)求的問(wèn)題
  3. DOM結(jié)構(gòu)清晰

代碼如下:

var request = require('request'),
 cheerio = require('cheerio');

request('https://cnodejs.org/', function(err, response, body){
 if( !err && response.statusCode == 200 ){
 // body為源碼
 // 使用 cheerio.load 將字符串轉(zhuǎn)換為 cheerio(jQuery) 對(duì)象,
 // 按照jQuery方式操作即可
 var $ = cheerio.load(body);
 
 // 輸出導(dǎo)航的html代碼
 console.log( $('.nav').html() );
 }
});

這樣的一段代碼就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的網(wǎng)絡(luò)爬蟲(chóng),爬取到源碼后,再對(duì)源碼進(jìn)行拆解分析,比如我們要獲取首頁(yè)中第1頁(yè)的 問(wèn)題標(biāo)題,作者,跳轉(zhuǎn)鏈接,點(diǎn)擊數(shù)量,回復(fù)數(shù)量。通過(guò)chrome,我們可以得到這樣的結(jié)構(gòu):

每個(gè)div[.cell]是一個(gè)題目完整的單元,在這里面,一個(gè)單元暫時(shí)稱為$item

{
 title : $item.find('.topic_title').text(),
 url : $item.find('.topic_title').attr('href'),
 author : $item.find('.user_avatar img').attr('title'),
 reply : $item.find('.count_of_replies').text(),
 visits : $item.find('.count_of_visits').text()
}

因此,循環(huán)div[.cell] ,就可以獲取到我們想要的信息了:

request('https://cnodejs.org/?_t='+Date.now(), function(err, response, body){
 if( !err && response.statusCode == 200 ){
 var $ = cheerio.load(body);

 var data = [];
 $('#topic_list .cell').each(function(){
  var $this = $(this);
 
 // 使用trim去掉數(shù)據(jù)兩端的空格
  data.push({
  title : trim($this.find('.topic_title').text()),
  url : trim($this.find('.topic_title').attr('href')),
  author : trim($this.find('.user_avatar img').attr('title')),
  reply : trim($this.find('.count_of_replies').text()),
  visits : trim($this.find('.count_of_visits').text())
  })
 });
 // console.log( JSON.stringify(data, ' ', 4) );
 console.log(data);
 }
});

// 刪除字符串左右兩端的空格
function trim(str){ 
 return str.replace(/(^\s*)|(\s*$)/g, "");
}

二、爬取多個(gè)頁(yè)面

上面我們只爬取了一個(gè)頁(yè)面,怎么在一個(gè)程序里爬取多個(gè)頁(yè)面呢?還是以CNode網(wǎng)站為例,剛才只是爬取了第1頁(yè)的數(shù)據(jù),這里我們想請(qǐng)求它前6頁(yè)的數(shù)據(jù)(別同時(shí)抓太多的頁(yè)面,會(huì)被封IP的)。每個(gè)頁(yè)面的結(jié)構(gòu)是一樣的,我們只需要修改url地址即可。

2.1 同時(shí)抓取多個(gè)頁(yè)面

首先把request請(qǐng)求封裝為一個(gè)方法,方便進(jìn)行調(diào)用,若還是使用console.log方法的話,會(huì)把6頁(yè)的數(shù)據(jù)都輸出到控制臺(tái),看起來(lái)很不方便。這里我們就使用到了上節(jié)文件操作內(nèi)容,引入fs模塊,將獲取到的內(nèi)容寫(xiě)入到文件中,然后新建的文件放到file目錄下(需手動(dòng)創(chuàng)建file目錄):

// 把page作為參數(shù)傳遞進(jìn)去,然后調(diào)用request進(jìn)行抓取
function getData(page){
 var url = 'https://cnodejs.org/?tab=all&page='+page;
 console.time(url);
 request(url, function(err, response, body){
 if( !err && response.statusCode == 200 ){
  console.timeEnd(url); // 通過(guò)time和timeEnd記錄抓取url的時(shí)間

  var $ = cheerio.load(body);

  var data = [];
  $('#topic_list .cell').each(function(){
  var $this = $(this);

  data.push({
   title : trim($this.find('.topic_title').text()),
   url : trim($this.find('.topic_title').attr('href')),
   author : trim($this.find('.user_avatar img').attr('title')),
   reply : trim($this.find('.count_of_replies').text()),
   visits : trim($this.find('.count_of_visits').text())
  })
  });
  // console.log( JSON.stringify(data, ' ', 4) );
  // console.log(data);
  var filename = './file/cnode_'+page+'.txt';
  fs.writeFile(filename, JSON.stringify(data, ' ', 4), function(){
  console.log( filename + ' 寫(xiě)入成功' );
  })
 }
 });
}

CNode分頁(yè)請(qǐng)求的鏈接:https://cnodejs.org/?tab=all&page=2,我們只需要修改page的值即可:

var max = 6;
for(var i=1; i<=max; i++){

 getData(i);
}

這樣就能同時(shí)請(qǐng)求前6頁(yè)的數(shù)據(jù)了,執(zhí)行文件后,會(huì)輸出每個(gè)鏈接抓取成功時(shí)消耗的時(shí)間,抓取成功后再把相關(guān)的信息寫(xiě)入到文件中:

$ node test.js
開(kāi)始請(qǐng)求...
https://cnodejs.org/?tab=all&page=1: 279ms
./file/cnode_1.txt 寫(xiě)入成功
https://cnodejs.org/?tab=all&page=3: 372ms
./file/cnode_3.txt 寫(xiě)入成功
https://cnodejs.org/?tab=all&page=2: 489ms
./file/cnode_2.txt 寫(xiě)入成功
https://cnodejs.org/?tab=all&page=4: 601ms
./file/cnode_4.txt 寫(xiě)入成功
https://cnodejs.org/?tab=all&page=5: 715ms
./file/cnode_5.txt 寫(xiě)入成功
https://cnodejs.org/?tab=all&page=6: 819ms
./file/cnode_6.txt 寫(xiě)入成功

我們?cè)趂ile目錄下就能看到輸出的6個(gè)文件了。

2.2 控制同時(shí)請(qǐng)求的數(shù)量

我們?cè)谑褂胒or循環(huán)后,會(huì)同時(shí)發(fā)起所有的請(qǐng)求,如果我們同時(shí)去請(qǐng)求100、200、500個(gè)頁(yè)面呢,會(huì)造成短時(shí)間內(nèi)對(duì)服務(wù)器發(fā)起大量的請(qǐng)求,最后就是被封IP。這里我寫(xiě)了一個(gè)調(diào)度方法,每次同時(shí)最多只能發(fā)起5個(gè)請(qǐng)求,上一個(gè)請(qǐng)求完成后,再?gòu)年?duì)列中取出一個(gè)進(jìn)行請(qǐng)求。

/*
 @param data [] 需要請(qǐng)求的鏈接的集合
 @param max num 最多同時(shí)請(qǐng)求的數(shù)量
*/
function Dispatch(data, max){
 var _max = max || 5, // 最多請(qǐng)求的數(shù)量
 _dataObj = data || [], // 需要請(qǐng)求的url集合
 _cur = 0, // 當(dāng)前請(qǐng)求的個(gè)數(shù)
 _num = _dataObj.length || 0,
 _isEnd = false,
 _callback;

 var ss = function(){
 var s = _max - _cur;
 while(s--){
  if( !_dataObj.length ){
  _isEnd = true;
  break;
  }
  var surl = _dataObj.shift();
  _cur++;

  _callback(surl);
 }
 }

 this.start = function(callback){
 _callback = callback;

 ss();
 },

 this.call = function(){
 if( !_isEnd ){
  _cur--;
  ss();
 }
 }
}

var dis = new Dispatch(urls, max);
dis.start(getData);

然后在 getData 中,寫(xiě)入文件的后面,進(jìn)行dis的回調(diào)調(diào)用:

var filename = './file/cnode_'+page+'.txt';
fs.writeFile(filename, JSON.stringify(data, ' ', 4), function(){
 console.log( filename + ' 寫(xiě)入成功' );
})
dis.call();

這樣就實(shí)現(xiàn)了異步調(diào)用時(shí)控制同時(shí)請(qǐng)求的數(shù)量。

三、抓取需要登錄的頁(yè)面

比如我們?cè)谧トNode,百度貼吧等一些網(wǎng)站,是不需要登錄就可以直接抓取的,那么如知乎等網(wǎng)站,必須登錄后才能抓取,否則直接跳轉(zhuǎn)到登錄頁(yè)面。這種情況我們?cè)撛趺醋ト∧兀?br />

使用cookie。 用戶登錄后,都會(huì)在cookie中記錄下用戶的一些信息,我們?cè)谧ト∫恍╉?yè)面,帶上這些cookie,服務(wù)器就會(huì)認(rèn)為我們處于登錄狀態(tài),程序就能抓取到我們想要的信息。

先在瀏覽器上登錄我們的帳號(hào),然后在console中使用document.domain獲取到所有cookie的字符串,復(fù)制到下方程序的cookie處(如果你知道哪些cookie不需要,可以剔除掉)。

request({
 url:'https://www.zhihu.com/explore',
 headers:{
 // "Referer":"www.zhihu.com"
 cookie : xxx
 }
}, function(error, response, body){
 if (!error && response.statusCode == 200) {
 // console.log( body );
 var $ = cheerio.load(body);

 
 }
})

同時(shí)在request中,還可以設(shè)定referer,比如有的接口或者其他數(shù)據(jù),設(shè)定了referer的限制,必須在某個(gè)域名下才能訪問(wèn)。那么在request中,就可以設(shè)置referer來(lái)進(jìn)行偽造。

四、保存抓取到的圖片

頁(yè)面中的文本內(nèi)容可以提煉后保存到文本或者數(shù)據(jù)庫(kù)中,那么圖片怎么保存到本地呢。

圖片可以使用request中的pipe方法輸出到文件流中,然后使用fs.createWriteStream輸出為圖片。

這里我們把圖片保存到以日期創(chuàng)建的目錄中,mkdirp可一次性創(chuàng)建多級(jí)目錄(./img/2017/01/22)。保存的圖片名稱,可以使用原名稱,也可以根據(jù)自己的規(guī)則進(jìn)行命名。

var request = require('request'),
 cheerio = require('cheerio'),
 fs = require('fs'),
 path = require('path'), // 用于分析圖片的名稱或者后綴名
 mkdirp = require('mkdirp'); // 用于創(chuàng)建多級(jí)目錄

var date = new Date(),
 year = date.getFullYear(),
 month = date.getMonth()+1,
 month = ('00'+month).slice(-2), // 添加前置0
 day = date.getDate(),
 day = ('00'+day).slice(-2), // 添加前置0
 dir = './img/'+year+'/'+month+'/'+day+'/';

// 根據(jù)日期創(chuàng)建目錄 ./img/2017/01/22/
var stats = fs.statSync(dir);
if( stats.isDirectory() ){
 console.log(dir+' 已存在');
}else{
 console.log('正在創(chuàng)建目錄 '+dir);
 mkdirp(dir, function(err){
 if(err) throw err;
 })
}

request({
 url : 'http://desk.zol.com.cn/meinv/?_t='+Date.now()
}, function(err, response, body){
 if(err) throw err;

 if( response.statusCode == 200 ){
 var $ = cheerio.load(body);
 
 $('.photo-list-padding img').each(function(){
  var $this = $(this),
  imgurl = $this.attr('src');
  
  var ext = path.extname(imgurl); // 獲取圖片的后綴名,如 .jpg, .png .gif等
  var filename = Date.now()+'_'+ parseInt(Math.random()*10000)+ext; // 命名方式:毫秒時(shí)間戳+隨機(jī)數(shù)+后綴名
  // var filename = path.basename(imgurl); // 直接獲取圖片的原名稱
  // console.log(filename);
  download(imgurl, dir+filename); // 開(kāi)始下載圖片
 })
 }
});

// 保存圖片
var download = function(imgurl, filename){
 request.head(imgurl, function(err, res, body) {
 request(imgurl).pipe(fs.createWriteStream(filename));
 console.log(filename+' success!');
 });
}

在對(duì)應(yīng)的日期目錄里(如./img/2017/01/22/),就可以看到下載的圖片了。

總結(jié)

我們這里只是寫(xiě)了一個(gè)簡(jiǎn)單的爬蟲(chóng),針對(duì)更復(fù)雜的功能,則需要更復(fù)雜的算法的來(lái)控制了。還有如何抓取ajax的數(shù)據(jù),我們會(huì)在后面進(jìn)行講解。以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,小編還會(huì)繼續(xù)分享關(guān)于node入門(mén)學(xué)習(xí)的文章,感興趣的朋友們請(qǐng)繼續(xù)關(guān)注腳本之家。

相關(guān)文章

  • 使用express來(lái)代理服務(wù)的方法

    使用express來(lái)代理服務(wù)的方法

    這篇文章主要介紹了使用express來(lái)代理服務(wù)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • Node.js同時(shí)安裝多個(gè)版本及相關(guān)配置指南(簡(jiǎn)單易操作)

    Node.js同時(shí)安裝多個(gè)版本及相關(guān)配置指南(簡(jiǎn)單易操作)

    在實(shí)際開(kāi)發(fā)過(guò)程中我們可能需要安裝多個(gè)版本的 nodejs,下面這篇文章主要給大家介紹了關(guān)于Node.js同時(shí)安裝多個(gè)版本及相關(guān)配置的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-11-11
  • ndm:NPM的桌面GUI應(yīng)用程序

    ndm:NPM的桌面GUI應(yīng)用程序

    今天小編就為大家分享一篇關(guān)于ndm:NPM的桌面GUI應(yīng)用程序,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-10-10
  • node.js通過(guò)axios實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求的方法

    node.js通過(guò)axios實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求的方法

    下面小編就為大家分享一篇node.js通過(guò)axios實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-03-03
  • Node.js開(kāi)發(fā)之訪問(wèn)Redis數(shù)據(jù)庫(kù)教程

    Node.js開(kāi)發(fā)之訪問(wèn)Redis數(shù)據(jù)庫(kù)教程

    這篇文章主要介紹了Node.js開(kāi)發(fā)之訪問(wèn)Redis數(shù)據(jù)庫(kù)教程,本文講解了安裝Redis的Node.js驅(qū)動(dòng)、編寫(xiě)測(cè)試程序以及npm遠(yuǎn)程服務(wù)器連接十分緩慢的解決方法,需要的朋友可以參考下
    2015-01-01
  • node.js安裝及HbuilderX配置詳解

    node.js安裝及HbuilderX配置詳解

    這篇文章主要介紹了node.js安裝及HbuilderX配置的相關(guān)資料,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-03-03
  • nodejs配置express服務(wù)器運(yùn)行自動(dòng)打開(kāi)瀏覽器詳細(xì)步驟

    nodejs配置express服務(wù)器運(yùn)行自動(dòng)打開(kāi)瀏覽器詳細(xì)步驟

    在nodejs中使用express來(lái)搭建框架可以說(shuō)是非常的簡(jiǎn)單方便,下面這篇文章主要給大家介紹了關(guān)于nodejs配置express服務(wù)器運(yùn)行自動(dòng)打開(kāi)瀏覽器的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-01-01
  • 如何在Express4.x中愉快地使用async的方法

    如何在Express4.x中愉快地使用async的方法

    這篇文章主要介紹了如何在Express4.x中愉快地使用async的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • 詳解如何模擬實(shí)現(xiàn)node中的Events模塊(通俗易懂版)

    詳解如何模擬實(shí)現(xiàn)node中的Events模塊(通俗易懂版)

    這篇文章主要介紹了如何模擬實(shí)現(xiàn)node中的Events模塊(通俗易懂版),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • nodejs中實(shí)現(xiàn)阻塞實(shí)例

    nodejs中實(shí)現(xiàn)阻塞實(shí)例

    這篇文章主要介紹了nodejs中實(shí)現(xiàn)阻塞實(shí)例,本文直接給出實(shí)例代碼,需要的朋友可以參考下
    2015-03-03

最新評(píng)論

北宁市| 大丰市| 富川| 太仆寺旗| 四子王旗| 奉节县| 深州市| 宣威市| 商南县| 新疆| 芜湖市| 紫金县| 伊宁市| 华阴市| 镇坪县| 德江县| 华坪县| 芦山县| 大埔县| 潜江市| 宜城市| 金山区| 深水埗区| 普格县| 禹城市| 光泽县| 绥棱县| 玉田县| 普兰县| 邛崃市| 宣化县| 昭通市| 万宁市| 乐山市| 永和县| 西昌市| 兴隆县| 岳池县| 宁夏| 新宾| 祥云县|