【nodejs】理想论坛帖子下载爬虫1.06
//======================================================
// 理想论坛帖子下载爬虫1.06
// 循环改成了递归,但最多下载千余文件就崩了
// 2018年4月27日
//======================================================
var http=require("http"); // http模块
var zlib = require('zlib'); // 用于解析gzip网页
var fs=require('fs'); // 文件处理模块
var iconv = require('iconv-lite'); // 用于转码。
var cheerio = require("cheerio"); // 用于从HTML中以类似jquery方式查找目标
var async=require('async'); // 用于异步流程控制
var EventProxy = require('eventproxy');// 用来控制并发
//--- 下面为全局变量 ---
var folder;// 存文件的目录
var topics=[]; // 帖子数组
var finalTopics=[];// 所有帖子加子贴的最终数组
//-------------------------------
// 用于创建目录
//-------------------------------
function createFolder(){
console.log('准备创建目录');
folder='infos('+currDateTime()+")";
fs.mkdir('./'+folder,function(err){
if(err){
console.log("目录"+folder+"已经存在");
}else{
console.log("目录"+folder+"已创建");
}
});
}
//-------------------------------
// 浏览页面找主贴
// start:开始页,end:结束页
//-------------------------------
function findTopics(start,end){
console.log('准备从以下页面寻找主贴');
for(var i=start;i<=end;i++){
pageUrl='http://www.55188.com/forum-8-'+i+'.html'
findTopicsInPage(pageUrl);
}
}
//-------------------------------
// 找到每个论坛页的帖子
// pageUrl:论坛页的地址
//-------------------------------
function findTopicsInPage(pageUrl){
console.log("page="+pageUrl);
var currUrl=pageUrl.replace("http://","");
var pos=currUrl.indexOf("/");
var hostname=currUrl.slice(0,pos);
var path=currUrl.slice(pos);
pos=currUrl.lastIndexOf("/");
var dir="http://"+currUrl.slice(0,pos);
var options={
hostname:hostname,
port:80,
path:path,
method:'GET',
};
var req=http.request(options,function(resp){
var html = [];
resp.on("data", function(data) {
html.push(data);
})
resp.on("end", function() {
var buffer = Buffer.concat(html);
var body = iconv.decode(buffer,'gb2312');
var $ = cheerio.load(body);
$("tbody").each(function(index,element){
var $tbody=cheerio.load($(element).html());
var topic={};
topic.pageCount=1;
topic.url=null;
topic.title=null;
$tbody(".forumdisplay a").each(function(index,element){
var topicUrl='http://www.55188.com/'+$tbody(element).attr("href");
var topicTitle=$tbody(element).text();
topic.url=topicUrl
topic.title=topicTitle;
})
$tbody(".threadpages").each(function(index,element){
topic.pageCount=$tbody(element).children().last().text();
})
if(topic.url!=null && topic.title!=null){
topics.push(topic);
}
})
}).on("error", function(err) {
console.log("findTopicsInPage函数请求后获取响应时出现异常"+err);
})
});
// 超时处理
req.setTimeout(7500,function(){
req.abort();
});
// 出错处理
req.on('error',function(err){
console.log('findTopicsInPage函数请求时发生错误'+err);
});
// 请求结束
req.end();
}
//-------------------------------
// 保存每个帖子到文件,完成一个递归调自己一次
// Nodejs特殊的回调处理强迫我们把for,while循环改成递归方式
//-------------------------------
function saveTopicDetails(){
if(finalTopics.length>0){
var topic=finalTopics.pop();
var topicUrl=topic.url;
var topicTitle=topic.title;
var index=topic.index;
var currUrl=topicUrl.replace("http://","");
var pos=currUrl.indexOf("/");
var hostname=currUrl.slice(0,pos);
var path=currUrl.slice(pos);
pos=currUrl.lastIndexOf("/");
var dir="http://"+currUrl.slice(0,pos);
var options={
hostname:hostname,
port:80,
path:path,
method:'GET',
headers:{
'Connection':'close',
}
};
var req=http.request(options,function(resp){
req.setSocketKeepAlive(false);
var html = [];
resp.on("data", function(data) {
html.push(data);
})
resp.on("end", function() {
try {
var buffer = Buffer.concat(html);
var body = iconv.decode(buffer,'gb2312');
var $ = cheerio.load(body);
var infos=[];// 获得的发帖人信息
// 得到发帖人信息
$(".postinfo").each(function(index,element){
var content=$(element).text();
content=content.replace(/\s+/g,' ');// 空白字符替换为一个空格
var arr=content.split(" ");// 以空格劈分
if(arr.length==7){
info={'url':topicUrl,
'title':topicTitle,
'楼层':arr[1],
'作者':arr[2].replace('只看:',''),
'日期':arr[4],
'时间':arr[5]};
infos.push(info);
//console.log('info='+info);
}else if(arr.length==8){
info={'url':topicUrl,
'title':topicTitle,
'楼层':arr[1],
'作者':arr[2].replace('只看:',''),
'日期':arr[5],
'时间':arr[6]};
infos.push(info);
//console.log('info='+info);
}
})
if(infos.length>0){
var text=JSON.stringify(infos);
filename='./'+folder+'/'+index+'.dat';
fs.writeFile(filename,text,function(err){
if(err){
console.log('写入文件'+filename+'失败,因为'+err);
}
});
if(index % 50==0){
console.log(coloredText(nowTime()+'第'+index+'个文件保存完毕','green'));//让控制台出点动静
}
//req.end();
saveTopicDetails();//一个帖子完成,递归一次
}
} catch(e) {
var text=nowTime()+"saveTopicDetail函数请求时(on_end)获取响应时出现异常"+e+'\n';
text+=nowTime()+" url="+topicUrl+'\n';
text+=nowTime()+" title="+topicTitle+'\n';
text+=nowTime()+" index="+index+'\n';
console.log(coloredText(text,'magenta'));
///
//req.end();
saveTopicDetails();//出错后依旧递归
}
}).on("error", function(err) {
var text=nowTime()+"saveTopicDetail函数请求后(on_error)获取响应时出现异常"+err+'\n';
text+=nowTime()+" url="+topicUrl+'\n';
text+=nowTime()+" title="+topicTitle+'\n';
text+=nowTime()+" index="+index+'\n';
console.log(coloredText(text,'red'));
///
//req.end();
saveTopicDetails();//出错后依旧递归
})
});
// 超时处理
req.setTimeout(1000,function(){
req.abort();
});
// 出错处理
// 这里最容易出错 Error: socket hang up
req.on('error',function(err){
var text=nowTime()+"saveTopicDetails函数请求时出现异常"+err+'\n';
text+=nowTime()+" url="+topicUrl+'\n';
text+=nowTime()+" title="+topicTitle+'\n';
text+=nowTime()+" index="+index+'\n';
console.log(coloredText(text,'red'));
///
req.end();
//finalTopics.push(topic);
saveTopicDetails();//出错后依旧递归
});
// 请求结束
req.end();
}
}
//-------------------------------
// 入口函数
// start:起始页,从1开始
// end:终止页,>start
//-------------------------------
function main(start,end){
var flow=require('nimble');
flow.series([
function(callback){
setTimeout(function(){
createFolder();
callback();
},100);
},
function(callback){
setTimeout(function(){
findTopics(start,end);
callback();
},100);
},
function(callback){
setTimeout(function(){
var n=topics.length;
console.log("共找到"+n+"个帖子");
// 获得每个子贴所在地址,序号和标题
var index=0;
var arr=[];
for(var i=0;i<n;i++){
var topic=topics[i];
for(var j=1;j<=topic.pageCount;j++){
var regexp=new RegExp(/-(\d+)-(\d+)-(\d+)/);
var topicUrl=topic.url.replace(regexp,"-$1-"+j+"-$3");// 用正则表达式替换第二个数字
index++;
var item={'index':index,'url':topicUrl,'title':topic.title};
arr.push(item);
}
}
finalTopics=arr;// 所有帖子加子贴的最终数组
finalTopics.reverse();
console.log('拟生成文件'+finalTopics.length+'个');
saveTopicDetails();
callback();
},3000);
},
]);
}
//--------------------------------------
// 通用函数,返回当前日期时间 创建目录用
//--------------------------------------
function currDateTime() {
var date = new Date();
var seperator1 = "-";
var seperator2 = "_";
var month = date.getMonth() + 1;
var strDate = date.getDate();
if (month >= 1 && month <= 9) {
month = "0" + month;
}
if (strDate >= 0 && strDate <= 9) {
strDate = "0" + strDate;
}
var currentdate =date.getFullYear() + seperator1 + month + seperator1 + strDate
+ " " + date.getHours() + seperator2 + date.getMinutes()
+ seperator2 + date.getSeconds();
return currentdate;
}
//--------------------------------------
// 通用函数,返回当前日期时间 控制台输出时间用
//--------------------------------------
function nowTime() {
var date = new Date();
var seperator1 = "-";
var seperator2 = ":";
var month = date.getMonth() + 1;
var strDate = date.getDate();
if (month >= 1 && month <= 9) {
month = "0" + month;
}
if (strDate >= 0 && strDate <= 9) {
strDate = "0" + strDate;
}
var currentdate =date.getFullYear() + seperator1 + month + seperator1 + strDate
+ " " + date.getHours() + seperator2 + date.getMinutes()
+ seperator2 + date.getSeconds()+ " ";
return currentdate;
}
//-------------------------------
// 得到带颜色(前景色)的文字,用于在控制台输出
// text:文字,color:前景色
//-------------------------------
function coloredText(text,color){
var dic = new Array();
dic["white"] = ['\x1B[37m', '\x1B[39m'];
dic["grey"] = ['\x1B[90m', '\x1B[39m'];
dic["black"] = ['\x1B[30m', '\x1B[39m'];
dic["blue"] = ['\x1B[34m', '\x1B[39m'];
dic["cyan"] = ['\x1B[36m', '\x1B[39m'];
dic["green"] = ['\x1B[32m', '\x1B[39m'];
dic["magenta"] = ['\x1B[35m', '\x1B[39m'];
dic["red"] = ['\x1B[31m', '\x1B[39m'];
dic["yellow"] = ['\x1B[33m', '\x1B[39m'];
return dic[color][0]+text+dic[color][1];
}
// 开始
main(2,2);
【nodejs】理想论坛帖子下载爬虫1.06的更多相关文章
- 【nodejs】理想论坛帖子下载爬虫1.07 使用request模块后稳定多了
在1.06版本时,访问网页采用的时http.request,但调用次数多以后就问题来了. 寻找别的方案时看到了https://cnodejs.org/topic/53142ef833dbcb076d0 ...
- 【nodejs】理想论坛帖子下载爬虫1.08
//====================================================== // 理想论坛帖子下载爬虫1.09 // 使用断点续传模式,因为网络传输会因各种原因中 ...
- 【Nodejs】理想论坛帖子下载爬虫1.04
一直想做一个能把理想论坛指定页范围的帖子都能完整下载下来的爬虫,但未能如愿. 主要的障碍在并发数的控制和长时间任务的突然退出,比如想下载前五页的帖子,分析后可得到大约15000个主贴或子贴,如果用回调 ...
- 【Python】理想论坛帖子读取爬虫1.04版
1.01-1.03版本都有多线程争抢DB的问题,线程数一多问题就严重了. 这个版本把各线程要添加数据的SQL放到数组里,等最后一次性完成,这样就好些了.但乱码问题和未全部完成即退出现象还在,而且速度上 ...
- 【Nodejs】理想论坛帖子爬虫1.01
用Nodejs把Python实现过的理想论坛爬虫又实现了一遍,但是怎么判断所有回调函数都结束没有好办法,目前的spiderCount==spiderFinished判断法在多页情况下还是会提前中止. ...
- 【Nodejs】理想论坛帖子爬虫1.02
在1.01版本中,我发现各回调函数找到数据后再插入数据库有个竞争问题不好解决,如果等所有回调都完成也没有好的处理方法,因为启动不止一处启动了新的TopicSpider实例. 于是我决定把读数据和写DB ...
- 【Python】爬取理想论坛单帖爬虫
代码: # 单帖爬虫,用于爬取理想论坛帖子得到发帖人,发帖时间和回帖时间,url例子见main函数 from bs4 import BeautifulSoup import requests impo ...
- 【python】理想论坛帖子爬虫1.06
昨天认识到在本期同时起一百个回调/线程后程序会崩溃,造成结果不可信. 于是决定用Python单线程操作,因为它理论上就用主线程跑不会有问题,只是时间长点. 写好程序后,测试了一中午,210个主贴,11 ...
- 【pyhon】理想论坛单帖爬虫取得信息存入MySql数据库
代码: # 单帖爬虫,用于爬取理想论坛单个帖子得到发帖人,发帖时间和回帖时间并存入数据库,url例子见main函数 from bs4 import BeautifulSoup import reque ...
随机推荐
- Splay和LCT的复杂度分析
\(Splay\)的复杂度分析 不论插入,删除还是访问,我们可以发现它们的复杂度都和\(splay\)操作的复杂度同阶,只是一点常数的区别 我们不妨假设有\(n\)个点的\(splay\),进行了\( ...
- poj 3463 最短路+次短路
独立写查错不能,就是维护一个次短路的dist 题意:给定一个有向图,问从起点到终点,最短路+比最短路距离长1的路的个数. Sample Input25 81 2 31 3 21 4 52 3 12 5 ...
- Problem A: 象棋比赛
Description 1月6日,教职工象棋协会在6号楼办了一次比赛,很多老师都参加了.比赛共进行了5轮,赢1局积3分,和了1分,输了0分,你能帮忙算一下各位老师的积分吗? Input 多组测试数据, ...
- BZOJ 2330 SCOI2011糖果 差分约束
2330: [SCOI2011]糖果 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 2819 Solved: 820 题目连接 http://www ...
- Python知识(6)--numpy做矩阵运算
矩阵运算 论numpy中matrix 和 array的区别:http://blog.csdn.net/vincentlipan/article/details/20717163 matrix 和 ar ...
- JavaMail_测试编写
@Test public void test1() throws Exception{ // import java.util.Properties; // import javax.mail.Add ...
- Swift3.0字符串相关操作
以下有关字符串的常用操作都可直接复制到Xcode中进行验证,如发现错误,请在评论区留言指正! 1.字符串的定义 var str1="hello, swift." //字符串变量 相 ...
- apache上部署django的静态文件
一直在优化自己博客的代码, 昨天把css样式表分离出来, 用作静态 文件, 但是自己还没学django怎么使用静态文件, 经过一番google 终于解决了. django 使用静态文件有两种方法, 一 ...
- Android自己定义组件系列【3】——自己定义ViewGroup实现側滑
有关自己定义ViewGroup的文章已经非常多了,我为什么写这篇文章,对于刚開始学习的人或者对自己定义组件比較生疏的朋友尽管能够拿来主义的用了,可是要一步一步的实现和了解当中的过程和原理才干真真脱离别 ...
- QN-H618 遥控器复制再生仪(拷贝机)
针对现在市场上日益更新的遥控器种类,本公司经过长时间的研究,推出新一代拷贝机,本产品有以下特点: 1. 众多车库门遥控分析信息被集成在一台机器内,只要一种遥控器,就可以复制众多品牌的车库遥控.免去积压 ...