nodejs 使用superagent+cheerio+eventproxy爬取豆瓣帖子


//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); }

注意:以上代码请仅用于学习用途,切勿用于生产环境或者其他非法用途,否则后果请自行承担


superagent 是一个轻量的,渐进式的ajax api,可读性好,学习曲线低,内部依赖nodejs原生的请求api

cheerio 用于解析dom,用法与jquery类似

eventproxy 并发控制(计数器功能)

功能:爬取豆瓣的某版块列表页中的详情的内容,自动创建文件夹并写入文件中存储,可供接口调用。

代码解读:

执行get_article_list_url方法获取列表的url存进g.list_href_arr中,在执行完成的计数器回调中调用get_article_detail方法,该方法首先根据g.file_data_num对g.list_href_arr的url进行分组,
分完组后根据组数控制请求间隔,在执行完成的计数器回调中新建目录,将抓取回来的数据写入文件。
可直接供前端做接口使用。

原文地址:https://www.cnblogs.com/lzs-888/p/7235835.html