【Nodejs】理想论坛帖子爬虫1.02
在1.01版本中,我发现各回调函数找到数据后再插入数据库有个竞争问题不好解决,如果等所有回调都完成也没有好的处理方法,因为启动不止一处启动了新的TopicSpider实例。
于是我决定把读数据和写DB分开,爬虫负责前一部分,insertDB.js负责后一部分。
这样做避免了爬虫写DB竞争和判断所有回调都结束的难点问题。
而insertDB.js中,等待所有读文件的回调都结束是有办法的,那就是并行化控制,毕竟读各个文件比较简单,易于用task控制。
下面是爬虫代码:
//======================================================
// 理想论坛帖子下载爬虫1.02
// 目标网址:http://www.55188.com/forum-8-1.html
// 已经将获得数据和插入数据分开
// 此文件代码负责找到数据并存入文件
// 插入数据由insertDB.js负责
// 2018年4月16日
//======================================================
// 内置http模块
var http=require("http");
// 用于解析gzip网页(ungzip,https得到的网页是用gzip进行压缩的)
var zlib = require('zlib');
// 内置文件处理模块
var fs=require('fs');
// 用于转码。非Utf8的网页如gb2132会有乱码问题,需要iconv将其转码
var iconv = require('iconv-lite');
// cheerio模块,提供了类似jQuery的功能,用于从HTML code中查找图片地址和下一页
var cheerio = require("cheerio");
// 请求参数,JSON格式,http和https都有使用
var options;
// request请求
var req;
// 爬虫总数
var spiderCount=0;
// 完成任务的爬虫数
var spiderFinished=0;
// 爬虫获得的全部信息
var allInfos=[];
// 存放文件的目录
var folder="";
//-------------------------------
// 爬虫类
// index:爬虫序号
// title:帖子标题
// url:帖子地址
// pageNo:帖子页码
//-------------------------------
function TopicSpider(title,url){
var obj=new Object;
spiderCount++;
obj.index=spiderCount; // 序号
obj.title=title; // 标题
obj.url=url; // 地址
obj.pageNo=1; // 页码
obj.crawl=function(){
// 得到hostname和path
var currUrl=this.url.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);
// 初始化options
options={
hostname:hostname,
port:80,
path:path,// 子路径
method:'GET',
};
// 请求并处理response
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);
var infos=[];// 获得的发帖人信息
// 得到下一页信息
var nexturl=null;
$(".pages_btns .pages strong").each(function(index,element){
if(nexturl==null){
obj.pageNo=$(element).text();
var nextNode=$(element).next();
if(nextNode && nextNode.attr("href")){
// 如有下一页再起新爬虫
nexturl='http://www.55188.com/'+nextNode.attr("href");
var nextspider=new TopicSpider(obj.title,nexturl);
nextspider.crawl();
}
}
})
// 得到发帖人信息
$(".postinfo").each(function(index,element){
var content=$(element).text();
content=content.replace(/\s+/g,' ');// 空白字符替换为一个空格
var arr=content.split(" ");// 以空格劈分
if(arr.length==7){
info={'url':obj.url,
'title':obj.title,
'楼层':arr[1],
'作者':arr[2].replace('只看:',''),
'日期':arr[4],
'时间':arr[5]};
infos.push(info);
//console.log('info='+info);
}else if(arr.length==8){
info={'url':obj.url,
'title':obj.title,
'楼层':arr[1],
'作者':arr[2].replace('只看:',''),
'日期':arr[5],
'时间':arr[6]};
infos.push(info);
//console.log('info='+info);
}
})
// 信息获取结束,打印出爬虫信息
var msg='';
msg+='小爬序号:'+obj.index+'\n';
msg+='小爬爬取的帖子标题:'+obj.title+'\n';
msg+='小爬爬取的页码:'+obj.pageNo+'\n';
msg+='小爬爬取的帖子地址:'+obj.url+'\n';
msg+='小爬获取的信息数量:'+infos.length+'\n';
obj.savefile(obj.index,infos); // 存文件
msg+='小爬#:'+obj.index+'任务完成\n';
console.log(msg);
// 计算比例
spiderFinished++;
var percent=(spiderFinished*100/spiderCount).toFixed(2);
console.log('\n启动的爬虫数:'+spiderCount+',完成任务的爬虫数:'+spiderFinished+',完成比例:'+percent+"%");
}).on("error", function(err) {
console.log(coloredText('请求失败,异常为'+err,'red'));
})
});
// 超时处理
req.setTimeout(7500,function(){
req.abort();
});
// 出错处理
req.on('error',function(err){
console.log(coloredText('请求发生错误'+err,'red'));
});
// 请求结束
req.end();
};
// 保存文件
obj.savefile=function(index,infos){
var text=JSON.stringify(infos);
filename='./'+folder+'/'+index+'.dat';
fs.writeFile(filename,text,function(err){
if(err){
console.log('写入文件'+filename+'失败,因为'+err);
}
});
}
return obj;
}
//-------------------------------
// 找到每个论坛页的帖子
// pageUrl:论坛页的地址
//-------------------------------
function findTopicsInPage(pageUrl){
console.log("Current page="+pageUrl);
// 得到hostname和path
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);
// 初始化options
options={
hostname:hostname,
port:80,
path:path,// 子路径
method:'GET',
};
// 请求并处理response
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);
$(".forumdisplay a").each(function(index,element){
var topicUrl='http://www.55188.com/'+$(element).attr("href");
var topicTitle=$(element).text();
// 启动子贴爬虫
var spider=new TopicSpider(topicTitle,topicUrl);
spider.crawl();
})
}).on("error", function(err) {
console.log("findTopicsInPage函数失败"+err);
})
});
// 超时处理
req.setTimeout(7500,function(){
req.abort();
});
// 出错处理
req.on('error',function(err){
console.log('请求发生错误'+err);
});
// 请求结束
req.end();
}
//-------------------------------
// 得到带颜色(前景色)的文字,用于在控制台输出
// 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]
}
//-------------------------------
// 入口函数
// start:起始页,从1开始
// end:终止页,>start
//-------------------------------
function main(start,end){
console.log(coloredText('爬取任务开始','yellow'));
folder='infos('+getNowFormatDate()+")";
// 创建目录
fs.mkdir('./'+folder,function(err){
if(err){
console.log("目录"+folder+"已经存在");
}
});
for(var i=start;i<=end;i++){
pageUrl='http://www.55188.com/forum-8-'+i+'.html'
findTopicsInPage(pageUrl);
}
}
//-------------------------------
// 测试函数,用于测试TopicSpider类
//-------------------------------
function testTopicSpider(){
var spider=new TopicSpider('测试','http://www.55188.com/thread-8325667-1-1.html');
spider.crawl();
}
//--------------------------------------
// 取得当前时间
//--------------------------------------
function getNowFormatDate() {
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;
}
// 开始
main(1,5);
//testTopicSpider();
写出的文件示例截图如下:

这些文件是json格式的,示例:
[{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"楼主","作者":"先见天机","日期":"2018-4-15","时间":"15:13"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"2楼","作者":"短线冲","日期":"2018-4-15","时间":"15:27"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"3楼","作者":"勇儿马甲","日期":"2018-4-15","时间":"15:52"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"4楼","作者":"崇心开始","日期":"2018-4-15","时间":"15:52"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"5楼","作者":"老法儿","日期":"2018-4-15","时间":"16:28"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"6楼","作者":"金山水财","日期":"2018-4-15","时间":"16:33"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"7楼","作者":"生活如愿","日期":"2018-4-15","时间":"17:19"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"8楼","作者":"李汶安","日期":"2018-4-15","时间":"17:41"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"9楼","作者":"极品之星","日期":"2018-4-16","时间":"07:57"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"10楼","作者":"叼叼狼","日期":"2018-4-16","时间":"08:54"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"11楼","作者":"先见天机","日期":"2018-4-16","时间":"09:47"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"12楼","作者":"先见天机","日期":"2018-4-16","时间":"09:56"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"13楼","作者":"先见天机","日期":"2018-4-16","时间":"10:10"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"14楼","作者":"先见天机","日期":"2018-4-16","时间":"10:23"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"15楼","作者":"先见天机","日期":"2018-4-16","时间":"10:35"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"16楼","作者":"先见天机","日期":"2018-4-16","时间":"10:51"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"17楼","作者":"先见天机","日期":"2018-4-16","时间":"10:58"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"18楼","作者":"先见天机","日期":"2018-4-16","时间":"11:00"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"19楼","作者":"先见天机","日期":"2018-4-16","时间":"11:10"},{"url":"http://www.55188.com/thread-8337207-1-5.html","title":" 大势分析 ","楼层":"20楼","作者":"先见天机","日期":"2018-4-16","时间":"11:17"}]
而insertDB.js的代码如下:
//======================================================
// 在理想论坛帖子下载爬虫1.02生成数据文件后读取数据插入数据库
// 2018年4月17日
//======================================================
var fs=require('fs'); // 内置文件处理模块
var allInfos=[]; // 数据文件里获得的全部信息
var folder; // 数据文件所在的目录
var tasks=[]; // 任务数组
var finished=0; // 已完成任务的数量
//--------------------------------------
// 将数据插入数据库
//--------------------------------------
function insertDB(){
console.log("开始插入DB");
console.log('总计将有'+allInfos.length+"条数据将被插入数据库");
var mysql=require('mysql'); // 连接mysql数据库的模块
var conn=mysql.createConnection({
host:'127.0.0.1',
port:'3306',
database:'test',
user:'root',
password:'12345678',
});
conn.connect(function(err){
if(err){
console.log('与MySQL数据库建立连接失败');
}else{
console.log('与MySQL数据库建立连接成功');
for(var i=0;i<allInfos.length;i++){
var info=allInfos[i];
sql="insert into test.topic7(floor,author,tdate,ttime,addtime,url,title) values ('"
+info['楼层']+"','"
+info['作者']+"','"
+info['日期']+"','"
+info['时间']+"',"
+"now(),'"
+info['url']+"','"
+info['title']+"' "
+" )";
conn.query(sql,function(err,result){
if(err){
console.log(err);
}else{
//console.log("Insert succeed");
}
})
}
conn.end();
//console.log('全部数据插入完成');
}
});
}
//--------------------------------------
// 检查是否完成
//--------------------------------------
function checkIfFinished(){
finished++;
// 读取任务全完成再插DB
if(finished==tasks.length){
console.log("数据总量:"+allInfos.length);
insertDB();
}
}
//--------------------------------------
// 入口函数
//--------------------------------------
function main() {
folder='./'+'infos(2018-04-17 5_38_18)';// 数据文件所在目录
fs.readdir(folder,function(err,files){
if(err) throw err;
for(var index in files){
var task=(function(file){
return function(){
fs.readFile(file,'utf8',function(err,data){
if(err){
console.log('读取文件失败,因为'+err);
}else{
var infos=JSON.parse(data);
allInfos=allInfos.concat(infos);
checkIfFinished();
}
});
}
})(folder+'/'+files[index]);
tasks.push(task);
}
for(var task in tasks){
tasks[task]();
}
});
}
// 开始
main();
下面是数据库截图:

这样分离后,原有的问题就算解决了,Python爬虫也可以照此解决。
2018年4月17日08点45分
【Nodejs】理想论坛帖子爬虫1.02的更多相关文章
- 【Nodejs】理想论坛帖子爬虫1.01
用Nodejs把Python实现过的理想论坛爬虫又实现了一遍,但是怎么判断所有回调函数都结束没有好办法,目前的spiderCount==spiderFinished判断法在多页情况下还是会提前中止. ...
- 【python】理想论坛帖子爬虫1.06
昨天认识到在本期同时起一百个回调/线程后程序会崩溃,造成结果不可信. 于是决定用Python单线程操作,因为它理论上就用主线程跑不会有问题,只是时间长点. 写好程序后,测试了一中午,210个主贴,11 ...
- 【nodejs】理想论坛帖子下载爬虫1.08
//====================================================== // 理想论坛帖子下载爬虫1.09 // 使用断点续传模式,因为网络传输会因各种原因中 ...
- 【nodejs】理想论坛帖子下载爬虫1.07 使用request模块后稳定多了
在1.06版本时,访问网页采用的时http.request,但调用次数多以后就问题来了. 寻找别的方案时看到了https://cnodejs.org/topic/53142ef833dbcb076d0 ...
- 【nodejs】理想论坛帖子下载爬虫1.06
//====================================================== // 理想论坛帖子下载爬虫1.06 // 循环改成了递归,但最多下载千余文件就崩了 / ...
- 【Nodejs】理想论坛帖子下载爬虫1.04
一直想做一个能把理想论坛指定页范围的帖子都能完整下载下来的爬虫,但未能如愿. 主要的障碍在并发数的控制和长时间任务的突然退出,比如想下载前五页的帖子,分析后可得到大约15000个主贴或子贴,如果用回调 ...
- 【Python】理想论坛帖子读取爬虫1.04版
1.01-1.03版本都有多线程争抢DB的问题,线程数一多问题就严重了. 这个版本把各线程要添加数据的SQL放到数组里,等最后一次性完成,这样就好些了.但乱码问题和未全部完成即退出现象还在,而且速度上 ...
- 【Python】爬取理想论坛单帖爬虫
代码: # 单帖爬虫,用于爬取理想论坛帖子得到发帖人,发帖时间和回帖时间,url例子见main函数 from bs4 import BeautifulSoup import requests impo ...
- 【pyhon】理想论坛爬虫1.05版,将读取和写DB分离成两个文件
下午再接再厉仿照Nodejs版的理想帖子爬虫把Python版的也改造了下,但美中不足的是完成任务的线程数量似乎停滞在100个左右,让人郁闷.原因还待查. 先把代码贴出来吧,也算个阶段性成果. 爬虫代码 ...
随机推荐
- Codeforces Round #494 (Div 3) (A~E)
目录 Codeforces 1003 A.Polycarp's Pockets B.Binary String Constructing C.Intense Heat D.Coins and Quer ...
- disable_functions php-fpm root
php.ini disable_functions 禁用某些函数 需要时注意打开 php-fpm 对应conf user group为root时 ERROR: [pool www] please sp ...
- 升级Tornado到4后weibo oauth登录不了
把 Tornado 升级到4后,发现正常运行的微博登录不可以了. 原因是4已经移除 RequestHandler.async_callback and WebSocketHandler.async_c ...
- Codeforces Round #279 (Div. 2) A. Team Olympiad 水题
#include<stdio.h> #include<iostream> #include<memory.h> #include<math.h> usi ...
- PAT甲级1114. Family Property
PAT甲级1114. Family Property 题意: 这一次,你应该帮我们收集家族财产的数据.鉴于每个人的家庭成员和他/她自己的名字的房地产(房产)信息,我们需要知道每个家庭的规模,以及他们的 ...
- spring---transaction(4)---源代码分析(事务的状态TransactionStatus)
写在前面 TransactionStatus表示一个具体的事务状态(这里应用到了Java的一个多继承,接口允许多继承) TransactionStatus它继承了SavepointManager接口, ...
- 三个实例演示 Java Thread Dump 日志分析(转)
原文链接:http://www.cnblogs.com/zhengyun_ustc/archive/2013/01/06/dumpanalysis.html 转来当笔记^_^ jstack Dump ...
- redis的搜索组件 redis-search4j
redis-search4j是一款基于redis的搜索组件. 特点 1.基于redis,性能高效 2.实时更新索引 3.支持Suggest前缀.拼音查找(AutoComplete功能) 4.支持单个或 ...
- C#程序集系列08,设置程序集版本
区别一个程序集,不仅仅是程序集名称,还包括程序集版本.程序集公匙.程序集文化等,本篇体验通过界面和编码设置程序集版本. □ 通过Visual Studio设置程序集版本 →右键项目,选择"属 ...
- 关于deselectRowAtIndexPath
有没有遇到过,导航+UITableView,在push,back回来之后,当前cell仍然是选中的状态.当然,解决办法简单,添加一句[tableView deselectRowAtIndexPath: ...