Quantcast
Channel: CNode:Node.js专业中文社区
Viewing all 14821 articles
Browse latest View live

请教 request上传文件 已上传字节数怎么获取

$
0
0

再https://github.com/request/request上只看到了 request .on(‘data’, function(chunk){ bytes += chunk } 这个方法获取接收到的字节

但是没找到获取已经上传的字节数的方法


微信支付回调?回调用总是重定向

$
0
0

我用的wechat-pay包 但是微信回调地址为/weChat/notify 但是每次微信发送回调请求,post该地址时总是302,不知道为什么?

mongodb更新东西的时候,字段是只传更新的呢,还是所有字段都传呢

$
0
0

RT,老哥们一般是怎么做的呢,我是所有字段都让前端带过来了,但这样是不是不太好呢,但是如果前端只传跟新的字段,我后端怎么知道他传的是那些字段呢

有人开发过firefox下的WebExtensions吗?

$
0
0

据说新版的firefox扩展都要用WebExtensions来写了

写了一个简单的编译器

有用gulp的大神吗? 今天脚本突然报错 TypeError: file.isSymbolic is not a function

$
0
0

环境: macos, nodejs 8.6.0 npm 5.3.0 gulp 4.0

报错:

[14:57:34] Finished 'ESlintJS' after 1.54 s
[14:57:34] Starting 'uglifyJs'...
[14:57:34] Starting 'uglifyCss'...
[14:57:34] Starting 'uglifyHtml'...
[14:57:45] Finished 'uglifyHtml' after 11 s
[14:57:46] 'uglifyCss' errored after 12 s
[14:57:46] TypeError: file.isSymbolic is not a function
    at DestroyableTransform.normalize [as _transform] (/Users/guoxin/Documents/anxin_workspace/cooperation-html5/node_modules/vinyl-fs/lib/dest/prepare.js:31:15)
    at DestroyableTransform.Transform._read (/Users/guoxin/Documents/anxin_workspace/cooperation-html5/node_modules/readable-stream/lib/_stream_transform.js:182:10)
    at DestroyableTransform.Transform._write (/Users/guoxin/Documents/anxin_workspace/cooperation-html5/node_modules/readable-stream/lib/_stream_transform.js:170:83)
    at doWrite (/Users/guoxin/Documents/anxin_workspace/cooperation-html5/node_modules/readable-stream/lib/_stream_writable.js:406:64)
    at writeOrBuffer (/Users/guoxin/Documents/anxin_workspace/cooperation-html5/node_modules/readable-stream/lib/_stream_writable.js:395:5)
    at DestroyableTransform.Writable.write (/Users/guoxin/Documents/anxin_workspace/cooperation-html5/node_modules/readable-stream/lib/_stream_writable.js:322:11)
    at Pumpify.Duplexify._write (/Users/guoxin/Documents/anxin_workspace/cooperation-html5/node_modules/duplexify/index.js:201:22)
    at doWrite (/Users/guoxin/Documents/anxin_workspace/cooperation-html5/node_modules/readable-stream/lib/_stream_writable.js:406:64)
    at writeOrBuffer (/Users/guoxin/Documents/anxin_workspace/cooperation-html5/node_modules/readable-stream/lib/_stream_writable.js:395:5)
    at Pumpify.Writable.write (/Users/guoxin/Documents/anxin_workspace/cooperation-html5/node_modules/readable-stream/lib/_stream_writable.js:322:11)
[14:57:46] 'publish' errored after 15 s
[14:57:46] The following tasks did not complete: uglifyJs
[14:57:46] Did you forget to signal async completion?

exports.uglifyCss = function uglifyCss(done) {
    gulp.src(CONFIG.path.distCss)
        .pipe(sourcemaps.init())
        .pipe(cleanCss())
        .pipe(rev())
        .pipe(sourcemaps.write())
        .pipe(gulp.dest(CONFIG.path.publish))
        .pipe(rev.manifest('css-manifest.json'))   // 调试发现这段有问题. 
        .pipe(gulp.dest(path.join(CONFIG.path.publish, 'rev')))
        .on('finish', function () {
            done();
        });
};

之前用的还是好好的 今天突然就跪了… 同样的代码 我同事用windows 没问题 我mac就跪了 nodejs 和 npm 一致的情况下…

百度, google都没找到有解决办法 不知道怎么解决 有大神可以帮帮吗…

刚刚又试了下, 删掉node_modules重新安装就会出这个了… npm没有锁版本… 不知道npm i 升级了哪个包报了这个错… 好坑爹…

出现500问题了。但是看不到详细信息了。有什么办法知道哪出问题了吗?

$
0
0

image.png如上图所示: 蹦了个500错误。没有详细描述。 求怎么才能得到详细描述从而解决问题。

ide:webstorm; 框架:mean,monk,angular-ui-router

Nodejs中使用Promise后,对于异步操作中的异常如何正确的捕捉呢?

$
0
0

在异步过程中,又调用了一个异步的方法,见下面代码演示:


Promise1.isValid(key, sc)
.then(
	// 这里调用一个第三方实现的异步方法,但是是通过回调函数。
	fs.readFileSync(filename, function(err, content){
		if(err){
		   Promise.reject( ErrorFileNotExist);   /// <------ 这里希望能将异常传递出来。如何写是正确的呢?
		}
	}
}
.catch(ErrorFileNotExist, function(err){
	console.log(err.message);
	//  <--- 这里无法获得响应。
});

还请有熟悉的朋友提点一下,这块回调为何不能扑捉到呢?


最近使用Charles,Map Remote : https - > http 在iOS上边无法访问~有遇到过的么?

$
0
0

手机上已经安装了charles证书的,https能抓包看,就是不能map remote 映射; 有遇到相同情况的同学么?

[上海]Wiredcraft 招聘Javascript CSS前端开发;Node.js Golang Python后端开发程序猿!还有QA Tester;DevOps;项目经理等职位等你来投!!

$
0
0

About us:

Wiredcraft

Wiredcraft is a digital product agency with over 50 experts in Shanghai, Berlin and soon HK/Shenzhen. We help the largest brands in the world to create software and hardware products that impact millions of people through strategy, design, technology and marketing. Our clients include Apple, Starbucks, Google, PwC, the World Bank, the United Nations, Walmart, Etam and BASF. We’ve built things like the software running the elections of Myanmar, or the loyalty and payment apps (mobile, WeChat, web) for Starbucks China. We’re genuine community leaders, organizing regular events and conference that attract thousands of designers, technologists and leaders. This includes the UI/UX Conference (http://uiuxconf.com) and JSConf China (http://2017.jsconf.cn).

We’re all kinds of nerds We’re a mix of French, Dutch, German, Chinese, Finnish, English and American folks working out of Washington DC, Berlin and Shanghai. We spend our days filling walls with post-its, polishing UIs in Sketch and pushing React or Python code around.

What the heck are you waiting for? Apply for the best job of your life!

Here’s a few reasons on why you’ll have a blast, guaranteed: We build sh*t that matters for some of the biggest organizations in the world, from Google to Starbucks and the UN. We built the software running the elections of Myanmar (not kidding) and fixed HR with machine learning for billion-dollar companies. We’re wicked smart, passionate and don’t settle on quality. You’ll get to learn from some of the smartest folks in town and grow your skills like never before. We care about each other… a lot! We try really hard to keep everybody happy, whether it’s with perks (free snacks, Macbook, ergonomic chair, books, standing desks…), flexible working hours, company trips or generous vacation and remote work policies. Our playbook has all the deets. We use cool tech and encourage people to learn. Most of our colleagues are T-shaped. If you’re not, we’ll help you get there. We used Node.js, Marketing automation, React, A/B testing, Figma, Docker and design thinking before it was cool. (We know, so hipster). We have a strong culture built on trust. We’re transparent and treat our colleagues like responsible adults. Read more about our culture in our playbook. We’re in Berlin, Shanghai, and soon NYC. We also occasionally have folks moving around offices or working remotely. Oh, and one last thing: we do not work with recruiters, headhunters, agencies, freelancers or outsourcing companies. Cheers!


Hiring Position


Contact:

You can send email to hazel.wu@wiredcraft.com or click the above postion apply.


Location

SH - Jingan

200并发,为什么NGINX平均响应时间会比node 代理透传快一倍多

入门到放弃node系列之网络模块(二)

$
0
0

前言

本文首发【一名打字员】 上一节我们刚刚介绍完node的HTTP和HTTPS模块,相信我们也对nodejs有了更深层次的理解,接下来紧接着上一节的内容继续继续。

模块概述

  • URL 在我们处理HTTP请求时,URL模块是我们使用频率最高的,因为它可以帮助我们更快的解析、生成和拼接URL。 URL提供了一个parse方法,可以将url变成一个对象。
let url = require('url')
url.parse('http://www.mrpann.cn:8080/p/a/t/h?query=string#hash');
 =>
{ protocol: 'http:',
  host: 'www.mrpann.cn:8080',
  port: '8080',
  hostname: 'www.mrpann.cn',
  hash: '#hash',
  search: '?query=string',
  query: 'query=string',
  pathname: '/p/a/t/h',
  path: '/p/a/t/h?query=string',
  href: 'http://www.mrpann.cn:8080/p/a/t/h?query=string#hash' }

通过上面的方法,我们可以很轻松的拿到请求地址中有关的信息,同时,我们也能通过使用format方法,将一个URL对象格式化成一个请求地址。这个时候有的童鞋问了,打字员大大,我想拼接url怎么办呢,除了原始的+来拼接之外,我们还可以用resolve。示例如下:

url.resolve('http://www.mrpann.cn/api', '../user');
=>
/* http://www.mrpann.cn/api/user */
  • QUERY STRING 在实际运用中,当我们客户端发送请求的时候,我们往往URL参数字符串与参数对象的互相转换。querystring就是为此而生的,举个例子,当我们使用get方法请求一张图片的时候,url地址为www.mpann.cn/resourse/img/user?type=img&isHistory=2。我就可以使用querystring.parse方法,转换为{ type: 'img', isHistory: 2 }。 同理,我们也能够使用querystring.stringify将参数对象转换为url字符串。
  • ZLIB 当我们开发一个稍微庞大的系统的时候,往往某个请求中需要发送比较大的数据给服务端或者返回稍大的数据给客户端,但是往往因为数据量过大,导致请求失败或者后台无法接收。这个时候我们就可以使用zlib这个模块,它提供了数据压缩和解压的功能,通过zlib我们能很方便的压缩HTTP响应体数据。在下面这个示例中我们展示了如何使用zlib这个模块。
//服务端
http.createServer(function (request, response) {
    var body = []
    var headers = request.headers
    //判断是否支持gzip
    if(headers['accept-encoding'] || '').indexOf('gzip') != -1){
        zlib.gzip(data, function (err, data) {
            response.writeHead(200, {
                'Content-Type': 'text/plain',
                'Content-Encoding': 'gzip'
            });
            response.end(data);
        });
    }else {
        response.writeHead(200, {
            'Content-Type': 'text/plain'
        });
        response.end(data);
    }
    console.log(request.headers)
    request.on('data', function (chunk) {
        body.push(chunk)
    })

    request.on('end', function () {
        body = Buffer.concat(body)
        console.log(body.toString())
    })
}).listen(8080)//监听8080端口

//客户端
//request模块请求
//构建请求头信息
var options = {  
    hostname: 'www.mrpann.cn',  
    port: 8880,  
    path: '/api/v1/user/login',  
    method: 'POST',  
    headers: {  
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'Accept-Encoding': 'gzip, deflate'  
    },
}
//发起服务端请求
var request = http.request(options, function(result) {
        var body = []
        result.on('data', function (chunk) {
            body.push(chunk)
        });
        result.on('end', function () {
            body = Buffer.concat(body)
            var headers = result.headers
            //判断是否支持gzip压缩
            if (headers['content-encoding'] === 'gzip') {
                //进行解压响应体
                zlib.gunzip(body, function (err, data) {
                    console.log(data.toString());
                });
            } else {
                console.log(data.toString());
            }
        });
    })
    request.on('error', function (e) {})
    //写入请求体内容
    request.write(qs.stringify({username:userName,password:pwd,captcha:cap}))
    request.end()
  • NET 接触过其它语言网络编程这一块的童鞋应该比较熟悉socket。nodejs里面有没有呢,答案是肯定的。我们利用net模块可以创建一个socket服务端或者是客户端。但是由于前端的局限性,据本猿所知,使用socket的应用场景还很少,在这里仅仅展示一下利用net来创建一个简单的客户端和服务端应用。
//服务端应用
net.createServer(function (conn) {
    conn.on('data', function (data) {
        conn.write([
            'HTTP/1.1 200 OK',
            'Content-Type: text/plain',
            'Content-Length: 11',
            '',
            'Hello World'
        ].join('\n'));
    });
}).listen(8080);
//客户端应用
var options = {
        port: 8080,
        host: 'www.example.com'
    };

var client = net.connect(options, function () {
        client.write([
            'GET / HTTP/1.1',
            'User-Agent: curl/7.26.0',
            'Host: www.baidu.com',
            'Accept: */*',
            '',
            ''
        ].join('\n'));
    });

client.on('data', function (data) {
    console.log(data.toString());
    client.end();
});

结语

到这里我们的网络模块基本上就结束了。在这一模块里面我们知道了以下几点内容

  • 我们能够很方便的通过http和https进行一个客户端的请求与搭建一个服务端应用
  • 处理http请求时往往回家上url.parse方法
  • 可以通过request和response对象来读写对应的数据
  • node中支持压缩和解压数据以及支持socket编程
  • 人生无奈,我用node

下一次我们的入门到放弃node系列分享点什么呢,请发送到公众号后台一起投票吧!

如何把这段mongoose缩减得更加优美?

$
0
0

听说有一个方法是如果已经有了相同的结果,则不更新.

	Friendlink.find(
		{
			linkname: new_linkname
		},
		null,
		{
			limit: 1
		}
		function(err,result){
			if(err) throw err;
			if(result.length){
				res.send('相同的链接名已经存在!');
			}else{
				Friendlink.update(
					{
						linkname: initial_linkname
					},
					{
						linkname: new_linkname, 
						linkurl: new_linkurl
					},
					{
						upsert: true
					},
					function(err){
						if(err) throw err;
						res.send('Success to Update!');
					}
				);
			}
		}
	);

Mongodb+express+jade+Boostrap小项目

lllllllllllllllllllll


http://npm.taobao.org/挂掉了吗?一直打不开

安装node-inspector失败

$
0
0

image.pngimage.pngimage.pngimage.png为什么node-inspecto安装不成功

怎样可以有效的解决因为循环引用导致空对象的问题?

$
0
0

今天代码遇到方法未定义has no method问题。发现是循环引用的问题。

nodejs在遇到循环require时,会把require结果得到的结果变成空对象{}。这个结果包括的是循环引用链中的每一个。

例子:

a.js console.log(‘a.js’); var b = require(’./b’); console.log(‘a+.js’); console.log(b); exports={name:‘a’}; console.log(‘a++.js’);

b.js

console.log(‘b.js’); var c = require(’./c’); console.log(‘b+.js’); console.log©; exports={name:‘b’}; console.log(‘b++.js’);

c.js

console.log(‘c.js’); var a = require(’./a’); console.log(‘c+.js’); console.log(a); exports={name:‘c’}; console.log(‘c++.js’);

然后我执行

node a.js

打印如下:

a.js b.js c.js c+.js {} c++.js b+.js {} b++.js a+.js {} a++.js

可以看到node会按引用链引用,直到发现循环引用了为止。

然后开始回溯,所有的代码都会执行到。

唯一不同的是所有循环链中require的结果都是空对象{}.

在代码编写过程中怎样可以避免这个问题的出现呢?

求node包(download-git-repo)的使用方法!

$
0
0

想构建一个脚手架,但是用download-git-repo报错:‘git clone’ failed with status 128

download(‘github地址’, process.cwd(), { clone: true }, function (err) { if (err) console.log(err) })

求指点这个包怎么用?我想让脚手架工具的模板是从github上下载的而不是和工具放在一起的!

NodeJs爬虫抓取古代典籍,共计16000个页面心得体会总结,附带本项目和对应的React+ Redux 前端 和 Koa2服务端代码

$
0
0

前言

之前研究数据,零零散散的写过一些数据抓取的爬虫,不过写的比较随意。有很多地方现在看起来并不是很合理 这段时间比较闲,本来是想给之前的项目做重构的。 后来 利用这个周末,索性重新写了一个项目,就是本项目 guwen-spider。目前这个爬虫还是比较简单的类型的, 直接抓取页面,然后在页面中提取数据,保存数据到数据库。 通过与之前写的对比,我觉得难点在于整个程序的健壮性,以及相应的容错机制。在昨天写代码的过程中其实也有反映, 真正的主体代码其实很快就写完了 ,花了大部分时间是在 做稳定性的调试, 以及寻求一种更合理的方式来处理数据与流程控制的关系。

spider-shortcut1.png

背景

项目的背景是抓取一个一级页面是目录列表 ,点击一个目录进去 是一个章节 及篇幅列表 ,点击章节或篇幅进入具体的内容页面。

概述

本项目github地址 : guwen-spider(PS:最后面还有彩蛋 ~~逃

项目技术细节项目大量用到了 ES7 的async 函数, 更直观的反应程序了的流程。为了方便,在对数据遍历的过程中直接使用了著名的async这个库,所以不可避免的还是用到了回调promise ,因为数据的处理发生在回调函数中,不可避免的会遇到一些数据传递的问题,其实也可以直接用ES7的async await 写一个方法来实现相同的功能。这里其实最赞的一个地方是使用了 Class 的 static 方法封装对数据库的操作, static 顾名思义 静态方法 就跟 prototype 一样 ,不会占用额外空间。 项目主要用到了

  • 1 ES7的 async await 协程做异步有关的逻辑处理。
  • 2 使用 npm的 async库 来做循环遍历,以及并发请求操作。
  • 3 使用 log4js 来做日志处理
  • 4 使用 cheerio 来处理dom的操作。
  • 5 使用 mongoose 来连接mongoDB 做数据的保存以及操作。

目录结构

<pre> ├── bin // 入口 │  ├── booklist.js // 抓取书籍逻辑 │  ├── chapterlist.js // 抓取章节逻辑 │  ├── content.js // 抓取内容逻辑 │  └── index.js // 程序入口 ├── config // 配置文件 ├── dbhelper // 数据库操作方法目录 ├── logs // 项目日志目录 ├── model // mongoDB 集合操作实例 ├── node_modules
├── utils // 工具函数 ├── package.json
</pre>

项目实现方案分析

项目是一个典型的多级抓取案例,目前只有三级,即 书籍列表, 书籍项对应的 章节列表,一个章节链接对应的内容。 抓取这样的结构可以采用两种方式, 一是 直接从外层到内层 内层抓取完以后再执行下一个外层的抓取, 还有一种就是先把外层抓取完成保存到数据库,然后根据外层抓取到所有内层章节的链接,再次保存,然后从数据库查询到对应的链接单元 对之进行内容抓取。这两种方案各有利弊,其实两种方式我都试过, 后者有一个好处,因为对三个层级是分开抓取的, 这样就能够更方便,尽可能多的保存到对应章节的相关数据。 可以试想一下 ,如果采用前者 按照正常的逻辑 对一级目录进行遍历抓取到对应的二级章节目录, 再对章节列表进行遍历 抓取内容,到第三级 内容单元抓取完成 需要保存时,如果需要很多的一级目录信息,就需要 这些分层的数据之间进行数据传递 ,想想其实应该是比较复杂的一件事情。所以分开保存数据 一定程度上避开了不必要的复杂的数据传递。

目前我们考虑到 其实我们要抓取到的古文书籍数量并不多,古文书籍大概只有180本囊括了各种经史。其和章节内容本身是一个很小的数据 ,即一个集合里面有180个文档记录。 这180本书所有章节抓取下来一共有一万六千个章节,对应需要访问一万六千个页面爬取到对应的内容。所以选择第二种应该是合理的。

项目实现

主程有三个方法 bookListInit ,chapterListInit,contentListInit, 分别是抓取书籍目录,章节列表,书籍内容的方法对外公开暴露的初始化方法。通过async 可以实现对这三个方法的运行流程进行控制,书籍目录抓取完成将数据保存到数据库,然后执行结果返回到主程序,如果运行成功 主程序则执行根据书籍列表对章节列表的抓取,同理对书籍内容进行抓取。

项目主入口

/**
 * 爬虫抓取主入口
 */
const start = async() => {
    let booklistRes = await bookListInit();
    if (!booklistRes) {
        logger.warn('书籍列表抓取出错,程序终止...');
        return;
    }
    logger.info('书籍列表抓取成功,现在进行书籍章节抓取...');

    let chapterlistRes = await chapterListInit();
    if (!chapterlistRes) {
        logger.warn('书籍章节列表抓取出错,程序终止...');
        return;
    }
    logger.info('书籍章节列表抓取成功,现在进行书籍内容抓取...');

    let contentListRes = await contentListInit();
    if (!contentListRes) {
        logger.warn('书籍章节内容抓取出错,程序终止...');
        return;
    }
    logger.info('书籍内容抓取成功');
}
// 开始入口
if (typeof bookListInit === 'function' && typeof chapterListInit === 'function') {
    // 开始抓取
    start();
}

引入的 bookListInit ,chapterListInit,contentListInit, 三个方法

booklist.js

/**
 * 初始化方法 返回抓取结果 true 抓取成果 false 抓取失败
 */
const bookListInit = async() => {
    logger.info('抓取书籍列表开始...');
    const pageUrlList = getPageUrlList(totalListPage, baseUrl);
    let res = await getBookList(pageUrlList);
    return res;
}

chapterlist.js

/**
 * 初始化入口
 */
const chapterListInit = async() => {
    const list = await bookHelper.getBookList(bookListModel);
    if (!list) {
        logger.error('初始化查询书籍目录失败');
    }
    logger.info('开始抓取书籍章节列表,书籍目录共:' + list.length + '条');
    let res = await asyncGetChapter(list);
    return res;
};

content.js

/**
 * 初始化入口
 */
const contentListInit = async() => {
    //获取书籍列表
    const list = await bookHelper.getBookLi(bookListModel);
    if (!list) {
        logger.error('初始化查询书籍目录失败');
        return;
    }
    const res = await mapBookList(list);
    if (!res) {
        logger.error('抓取章节信息,调用 getCurBookSectionList() 进行串行遍历操作,执行完成回调出错,错误信息已打印,请查看日志!');
        return;
    }
    return res;
}

内容抓取的思考

书籍目录抓取其实逻辑非常简单,只需要使用async.mapLimit做一个遍历就可以保存数据了,但是我们在保存内容的时候 简化的逻辑其实就是 遍历章节列表 抓取链接里的内容。但是实际的情况是链接数量多达几万 我们从内存占用角度也不能全部保存到一个数组中,然后对其遍历,所以我们需要对内容抓取进行单元化。 普遍的遍历方式 是每次查询一定的数量,来做抓取,这样缺点是只是以一定数量做分类,数据之间没有关联,以批量方式进行插入,如果出错 则容错会有一些小问题,而且我们想一本书作为一个集合单独保存会遇到问题。因此我们采用第二种就是以一个书籍单元进行内容抓取和保存。 这里使用了 async.mapLimit(list, 1, (series, callback) => {})这个方法来进行遍历,不可避免的用到了回调,感觉很恶心。async.mapLimit()的第二个参数可以设置同时请求数量。

/* 
 * 内容抓取步骤:
 * 第一步得到书籍列表, 通过书籍列表查到一条书籍记录下 对应的所有章节列表, 
 * 第二步 对章节列表进行遍历获取内容保存到数据库中 
 * 第三步 保存完数据后 回到第一步 进行下一步书籍的内容抓取和保存
 */

/**
 * 初始化入口
 */
const contentListInit = async() => {
    //获取书籍列表
    const list = await bookHelper.getBookList(bookListModel);
    if (!list) {
        logger.error('初始化查询书籍目录失败');
        return;
    }
    const res = await mapBookList(list);
    if (!res) {
        logger.error('抓取章节信息,调用 getCurBookSectionList() 进行串行遍历操作,执行完成回调出错,错误信息已打印,请查看日志!');
        return;
    }
    return res;
}
/**
 * 遍历书籍目录下的章节列表
 * @param {*} list 
 */
const mapBookList = (list) => {
    return new Promise((resolve, reject) => {
        async.mapLimit(list, 1, (series, callback) => {
            let doc = series._doc;
            getCurBookSectionList(doc, callback);
        }, (err, result) => {
            if (err) {
                logger.error('书籍目录抓取异步执行出错!');
                logger.error(err);
                reject(false);
                return;
            }
            resolve(true);
        })
    })
}

/**
 * 获取单本书籍下章节列表 调用章节列表遍历进行抓取内容
 * @param {*} series 
 * @param {*} callback 
 */
const getCurBookSectionList = async(series, callback) => {

    let num = Math.random() * 1000 + 1000;
    await sleep(num);
    let key = series.key;
    const res = await bookHelper.querySectionList(chapterListModel, {
        key: key
    });
    if (!res) {
        logger.error('获取当前书籍: ' + series.bookName + ' 章节内容失败,进入下一部书籍内容抓取!');
        callback(null, null);
        return;
    }
    //判断当前数据是否已经存在
    const bookItemModel = getModel(key);
    const contentLength = await bookHelper.getCollectionLength(bookItemModel, {});
    if (contentLength === res.length) {
        logger.info('当前书籍:' + series.bookName + '数据库已经抓取完成,进入下一条数据任务');
        callback(null, null);
        return;
    }
    await mapSectionList(res);
    callback(null, null);
}


数据抓取完了 怎么保存是个问题

这里我们通过key 来给数据做分类,每次按照key来获取链接,进行遍历,这样的好处是保存的数据是一个整体,现在思考数据保存的问题

  • 1 可以以整体的方式进行插入

    优点 : 速度快 数据库操作不浪费时间。

    缺点 : 有的书籍可能有几百个章节 也就意味着要先保存几百个页面的内容再进行插入,这样做同样很消耗内存,有可能造成程序运行不稳定。

  • 2可以以每一篇文章的形式插入数据库。

    优点 : 页面抓取即保存的方式 使得数据能够及时保存,即使后续出错也不需要重新保存前面的章节,

    缺点 : 也很明显 就是慢 ,仔细想想如果要爬几万个页面 做 几万次*N 数据库的操作 这里还可以做一个缓存器一次性保存一定条数 当条数达到再做保存这样也是一个不错的选择。

/**
 * 遍历单条书籍下所有章节 调用内容抓取方法
 * @param {*} list 
 */
const mapSectionList = (list) => {
    return new Promise((resolve, reject) => {
        async.mapLimit(list, 1, (series, callback) => {
            let doc = series._doc;
            getContent(doc, callback)
        }, (err, result) => {
            if (err) {
                logger.error('书籍目录抓取异步执行出错!');
                logger.error(err);
                reject(false);
                return;
            }
            const bookName = list[0].bookName;
            const key = list[0].key;

            // 以整体为单元进行保存
            saveAllContentToDB(result, bookName, key, resolve);

            //以每篇文章作为单元进行保存
            // logger.info(bookName + '数据抓取完成,进入下一部书籍抓取函数...');
            // resolve(true);

        })
    })
}

两者各有利弊,这里我们都做了尝试。 准备了两个错误保存的集合,errContentModel, errorCollectionModel,在插入出错时 分别保存信息到对应的集合中,二者任选其一即可。增加集合来保存数据的原因是 便于一次性查看以及后续操作, 不用看日志。

(PS ,其实完全用 errorCollectionModel 这个集合就可以了 ,errContentModel这个集合可以完整保存章节信息)

//保存出错的数据名称
const errorSpider = mongoose.Schema({
    chapter: String,
    section: String,
    url: String,
    key: String,
    bookName: String,
    author: String,
})
// 保存出错的数据名称 只保留key 和 bookName信息
const errorCollection = mongoose.Schema({
    key: String,
    bookName: String,
})

我们将每一条书籍信息的内容 放到一个新的集合中,集合以key来进行命名。

总结

写这个项目 其实主要的难点在于程序稳定性的控制,容错机制的设置,以及错误的记录,目前这个项目基本能够实现直接运行 一次性跑通整个流程。 但是程序设计也肯定还存在许多问题 ,欢迎指正和交流。

彩蛋

写完这个项目 做了一个基于React开的前端网站用于页面浏览 和一个基于koa2.x开发的服务端, 整体技术栈相当于是 React + Redux + Koa2 ,前后端服务是分开部署的,各自独立可以更好的去除前后端服务的耦合性,比如同一套服务端代码,不仅可以给web端 还可以给 移动端 ,app 提供支持。目前整个一套还很简陋,但是可以满足基本的查询浏览功能。希望后期有时间可以把项目变得更加丰富。

  • 本项目地址 地址 : guwen-spider
  • 对应前端 React + Redux + semantic-ui 地址 : guwen-react
  • 对应Node端 Koa2.2 + mongoose 地址 : guwen-node项目挺简单的 ,但是多了一个学习和研究 从前端到服务端的开发的环境。

以上です

Viewing all 14821 articles
Browse latest View live