//cnpm install superagent cheerio eventproxy fs path
var superagent = require('superagent'); var cheerio = require('cheerio'); var eventproxy = require('eventproxy'); var fs = require("fs"); var path = require("path"); var ep = new eventproxy(); //全局變量 var g = { //抓取時間間隔 list_fetch_sec : 500, //抓取頁碼數 list_fetch_num : 50, //抓取失敗待重試的數組 list_fail_url : [], //最終獲取的圖片數組 list_href_arr : [], //抓取的版塊 pid:'douban_explore_ent', //每一個文件下的數據條數 file_data_num:30 }; get_article_list_url(); function get_list_url(){ var url_arr = []; //控制抓取頁碼數 for(var i=0;i<g.list_fetch_num;i++){ var strat = i*30; url_arr.push('https://www.douban.com/group/explore/ent?start='+strat); } return url_arr; } //解析列表頁dom function parseList(all_arr){ //遍歷 all_arr.forEach(function (item) { var itemUrl = item[0]; var itemHtml = item[1]; console.log('列表頁抓取完成'); var $ = cheerio.load(itemHtml); var a_dom = $('#content .channel-item .bd h3 a'); a_dom.each(function(){ var href = $(this).attr('href'); console.log(href); g.list_href_arr.push(href); }); }); } //解析詳情頁dom function praseDetail(all_arr){ var text_arr = []; all_arr.forEach(function (item) { var itemUrl = item[0]; var itemHtml = item[1]; var group_no = item[2]; if(itemHtml){ //decodeEntities 是否解碼實體 var $ = cheerio.load(itemHtml,{decodeEntities: false}); var content_jq = $('#content .topic-doc .topic-content'); var title_jq = $('#content h1'); try{ var first_floor = content_jq.html(); var title = title_jq.text(); var data = { content:first_floor, title:title, url:itemUrl }; text_arr.push(data); }catch(msg){ console.log('error'); } } }); return text_arr; } //獲取帖子列表 //feeling function get_article_list_url(){ var url_arr = get_list_url(); url_arr.forEach(function (url,index) { var _index = index+1; fetch_op(url,_index,g.list_fetch_sec,'list_parse',''); }); ep.after('list_parse', url_arr.length, function (all_arr) { parseList(all_arr); console.log('開始抓取詳情頁'); get_article_detail(); }); } //獲取帖子詳情 function get_article_detail(){ //分割數組 var obj = {}; g.list_href_arr.forEach(function (url,index) { var _group = parseInt(index/g.file_data_num)+1; //沒有則新建數組 if(!obj[_group]){ obj[_group] = []; } obj[_group].push(url); }); console.log(obj); var group_no; for(group_no in obj){ var group_data = obj[group_no]; var len = group_data.length; //設置計數器 ep_after(group_no,len); //每組再遍歷 var j; var count = 0; for(j in group_data){ count++; var url = group_data[j]; var _index = count; fetch_op(url,_index,g.list_fetch_sec,'detail_parse_'+group_no,group_no); } } } function ep_after(_group,len){ //計數器做用 當emit的detail_parse達到指定的數量時出發回調 ep.after('detail_parse_'+_group, len, function (all_arr) { console.log('詳情頁第['+_group+']組抓取完成'); var text_arr = praseDetail(all_arr); if(text_arr.length){ //若是目錄不存在 同步建立目錄 var dir_path_name = get_dir_path_name(g.pid); if (!fs.existsSync(dir_path_name)) { console.log('新建目錄: '+dir_path_name); fs.mkdirSync(dir_path_name); } console.log('saveing '+'詳情頁第['+_group+']組'); var save_data = {data:text_arr}; var path_name = get_file_path_name(g.pid,_group); fs.writeFile(path_name, JSON.stringify(save_data), function (err) { if (err) throw err; console.log('save done!'); }); } }); } function get_file_path_name(dirname,no){ var filename = dirname+'_'+no+'.js'; return path.join(__dirname,'data',dirname,filename); } function get_dir_path_name(dirname){ return path.join(__dirname,'data',dirname); } function fetch_op(url,i,sec,emit_name,group_no){ setTimeout(function(){ superagent.get(url) .end(function (err, res) { if(res){ console.log('抓取 第['+group_no+']組 ' + url + ' 成功'); ep.emit(emit_name, [url,res.text,group_no]); }else{ ep.emit(emit_name, [url,'',group_no]); console.log('抓取 第['+group_no+']組 ' + url + ' 失敗'); } }); },i*sec); }
注意:以上代碼請僅用於學習用途,切勿用於生產環境或者其餘非法用途,不然後果請自行承擔html
superagent 是一個輕量的,漸進式的ajax api,可讀性好,學習曲線低,內部依賴nodejs原生的請求api前端
cheerio 用於解析dom,用法與jquery相似node
eventproxy 併發控制(計數器功能)jquery
功能:爬取豆瓣的某版塊列表頁中的詳情的內容,自動建立文件夾並寫入文件中存儲,可供接口調用。ajax
代碼解讀:npm
執行get_article_list_url方法獲取列表的url存進g.list_href_arr中,在執行完成的計數器回調中調用get_article_detail方法,該方法首先根據g.file_data_num對g.list_href_arr的url進行分組,
分完組後根據組數控制請求間隔,在執行完成的計數器回調中新建目錄,將抓取回來的數據寫入文件。
可直接供前端作接口使用。api