国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

從零學習node.js之簡易的網絡爬蟲(四)

2019-11-19 17:27:50
字體:
來源:轉載
供稿:網友

前言

之前已經介紹了node.js的一些基本知識,下面這篇文章我們的目標是學習完本節課程后,能進行網頁簡單的分析與抓取,對抓取到的信息進行輸出和文本保存。

爬蟲的思路很簡單:

  1. 確定要抓取的URL;
  2. 對URL進行抓取,獲取網頁內容;
  3. 對內容進行分析并存儲;
  4. 重復第1步

在這節里做爬蟲,我們使用到了兩個重要的模塊:

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

一、 hello world

說是hello world,其實首先開始的是最簡單的抓取。我們就以cnode網站為例(https://cnodejs.org/),這個網站的特點是:

  1. 不需要登錄即可訪問首頁和其他頁面
  2. 頁面都是同步渲染的,沒有異步請求的問題
  3. DOM結構清晰

代碼如下:

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

這樣的一段代碼就實現了一個簡單的網絡爬蟲,爬取到源碼后,再對源碼進行拆解分析,比如我們要獲取首頁中第1頁的 問題標題,作者,跳轉鏈接,點擊數量,回復數量。通過chrome,我們可以得到這樣的結構:

每個div[.cell]是一個題目完整的單元,在這里面,一個單元暫時稱為$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()}

因此,循環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去掉數據兩端的空格  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, "");}

二、爬取多個頁面

上面我們只爬取了一個頁面,怎么在一個程序里爬取多個頁面呢?還是以CNode網站為例,剛才只是爬取了第1頁的數據,這里我們想請求它前6頁的數據(別同時抓太多的頁面,會被封IP的)。每個頁面的結構是一樣的,我們只需要修改url地址即可。

2.1 同時抓取多個頁面

首先把request請求封裝為一個方法,方便進行調用,若還是使用console.log方法的話,會把6頁的數據都輸出到控制臺,看起來很不方便。這里我們就使用到了上節文件操作內容,引入fs模塊,將獲取到的內容寫入到文件中,然后新建的文件放到file目錄下(需手動創建file目錄):

// 把page作為參數傳遞進去,然后調用request進行抓取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); // 通過time和timeEnd記錄抓取url的時間  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 + ' 寫入成功' );  }) } });}

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

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

這樣就能同時請求前6頁的數據了,執行文件后,會輸出每個鏈接抓取成功時消耗的時間,抓取成功后再把相關的信息寫入到文件中:

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

我們在file目錄下就能看到輸出的6個文件了。

2.2 控制同時請求的數量

我們在使用for循環后,會同時發起所有的請求,如果我們同時去請求100、200、500個頁面呢,會造成短時間內對服務器發起大量的請求,最后就是被封IP。這里我寫了一個調度方法,每次同時最多只能發起5個請求,上一個請求完成后,再從隊列中取出一個進行請求。

/* @param data [] 需要請求的鏈接的集合 @param max num 最多同時請求的數量*/function Dispatch(data, max){ var _max = max || 5, // 最多請求的數量 _dataObj = data || [], // 需要請求的url集合 _cur = 0, // 當前請求的個數 _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 中,寫入文件的后面,進行dis的回調調用:

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

這樣就實現了異步調用時控制同時請求的數量。

三、抓取需要登錄的頁面

比如我們在抓取CNode,百度貼吧等一些網站,是不需要登錄就可以直接抓取的,那么如知乎等網站,必須登錄后才能抓取,否則直接跳轉到登錄頁面。這種情況我們該怎么抓取呢?

使用cookie。 用戶登錄后,都會在cookie中記錄下用戶的一些信息,我們在抓取一些頁面,帶上這些cookie,服務器就會認為我們處于登錄狀態,程序就能抓取到我們想要的信息。

先在瀏覽器上登錄我們的帳號,然后在console中使用document.domain獲取到所有cookie的字符串,復制到下方程序的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);  }})

同時在request中,還可以設定referer,比如有的接口或者其他數據,設定了referer的限制,必須在某個域名下才能訪問。那么在request中,就可以設置referer來進行偽造。

四、保存抓取到的圖片

頁面中的文本內容可以提煉后保存到文本或者數據庫中,那么圖片怎么保存到本地呢。

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

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

var request = require('request'), cheerio = require('cheerio'), fs = require('fs'), path = require('path'), // 用于分析圖片的名稱或者后綴名 mkdirp = require('mkdirp'); // 用于創建多級目錄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+'/';// 根據日期創建目錄 ./img/2017/01/22/var stats = fs.statSync(dir);if( stats.isDirectory() ){ console.log(dir+' 已存在');}else{ console.log('正在創建目錄 '+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; // 命名方式:毫秒時間戳+隨機數+后綴名  // var filename = path.basename(imgurl); // 直接獲取圖片的原名稱  // console.log(filename);  download(imgurl, dir+filename); // 開始下載圖片 }) }});// 保存圖片var download = function(imgurl, filename){ request.head(imgurl, function(err, res, body) { request(imgurl).pipe(fs.createWriteStream(filename)); console.log(filename+' success!'); });}

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

總結

我們這里只是寫了一個簡單的爬蟲,針對更復雜的功能,則需要更復雜的算法的來控制了。還有如何抓取ajax的數據,我們會在后面進行講解。以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,小編還會繼續分享關于node入門學習的文章,感興趣的朋友們請繼續關注武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 祁东县| 濮阳市| 垫江县| 昌宁县| 宕昌县| 镇远县| 桐柏县| 宜兰市| 郯城县| 巫山县| 连江县| 肇庆市| 东平县| 齐齐哈尔市| 会理县| 雷山县| 柏乡县| 宝应县| 青海省| 烟台市| 郯城县| 和平县| 浦东新区| 孝感市| 如皋市| 榆社县| 宁都县| 孝昌县| 石狮市| 林周县| 新和县| 富平县| 大姚县| 澄迈县| 漳州市| 菏泽市| 漳平市| 葫芦岛市| 青浦区| 临夏县| 普洱|