起因 同学希望可以下载这个网站的连载漫画,之前爬过小说了,那么这次就来漫画吧,下载下来有快一个G的大小,162部,每部大约有二十几张图片。。
未解决的问题 【nodejs|nodejs爬取漫画】下载一定时间后服务器端不响应,虽然可以通过setTimeout来控制每次发送请求的间隔,但觉得依然指标不治本。基本上下完一部后间隔3s的话下载五十部左右后会挂,需要手动重启。我总共重启了三次
需要解决并解决的问题
异步与同步问题
- 问题
因为为了方便按顺序观看,以及防止一次请求过多导致服务器端断开连接,需要选择使用同步方式设计逻辑,但是Nodejs的一大特色就是异步,第一次我没有处理直接异步发送请求,下载了几张图片之后服务器主动断开连接。。
- 解决
将最新es6的async-await与Promise结合使用,效果超级棒,完全实现同步,async-await的入门教程请移步这里,至于Promise嘛,我之前也总结过一篇 :-)
- 注意事项
使用async-await的文件在用node启动时需要加上--harmony-async-await
后缀,而且node版本要是 7.0+的才行
- 问题
这个网站似乎是等页面加载完全时在动用脚本来载入图片的,所以使用简单的request模块获取的页面中没有图片链接,而且翻页也是通过脚本进行的,亦无法获取下一张图片的链接
- 解决
就事论事,从浏览器界面中获取多个图片链接,寻找链接的命名规律,这样以一个图片链接为入口,就可获取全部图片的链接
文章图片
- src文件夹用于存放下载后的漫画,具体是这样
文章图片
- download.js 脚本主入口
- download_comic.js 用于下载一册漫画的模块
- download_img.js 用于下载一页漫画(一张图片),并保存本地的脚本
- get_list_uri.js 用于获取目录信息(这个模块可有可无,因为下载下的目录对应的书的页面无法获取img的uri的)
- request 获取页面
- cheerio 分析页面
//get_list_uri.js
const request = require('request');
const cheerio = require('cheerio');
const fs = require('fs');
const path = require('path');
// 主入口
const main_uri = 'http://www.manhuatai.com/baozoulinjia/';
let getListUri = function (uri) {
return new Promise((resolve, reject) => {
request(uri, (err, response, body) => {
if (err) {
reject(err);
}
console.log(response && response.statusCode);
// 解析页面,获取每一部的序号与入口链接
$ = cheerio.load(body);
let lis = $('#topic1').children();
let keys = Object.getOwnPropertyNames(lis);
// 用于存储漫画各个章节信息
let comics = [];
for (let i = 0, length = keys.length;
i < length;
i++) {
let comic = {};
let context = $(lis[i]).text();
let first = '';
if (context.includes('回')) {
first = context.split('回')[0];
} else {
first = context.split('话')[0];
}
comic.id = first.substr(1);
if (comic.id === '')continue;
comic.uri = main_uri + $(lis[i]).find('a').attr('href');
comics.push(comic);
// 根据获取的序号新建文件夹
if (!fs.existsSync(path.join(__dirname, 'src', comic.id))) {
fs.mkdir(path.join(__dirname, 'src', comic.id), (err) => {
if (err)
console.log(err);
})
}
}
// 将章节信息写入一个文件中,存档
fs.writeFile('list.txt', JSON.stringify(comics, null, 2), 'utf8', (err) => {
if (err) {
console.log(err);
}
})
resolve(comics);
})
})
};
module.exports = getListUri;
// download_img.jsconst fs = require('fs');
const request = require('request');
const download = function(uri, filename){
//新建Promise对象,使其成为可回调函数,方便后面async-await调用
return new Promise((resolve, reject) => {
request(uri, (err, response, body) => {
if (err || response.statusCode === 404) {
reject(err);
} else {
console.log('reaching to ' + uri);
// 将下载内容写入本地文件,参(zhan)考(tie)stackoverflow解答写法
request(uri).pipe(fs.createWriteStream(filename)).on('close', ()=>{
resolve();
});
}
})
})
};
module.exports = download;
// download_comic.js
// 引入下载图片模块
const downloadImg = require('./download_img');
const reuqest = require('request');
const cheerio = require('cheerio');
const fs = require('fs');
const path = require('path');
/**
* @param comicId漫画章节序号
*/
let downloadComic = function (comicId) {
// 添加async前缀,说明函数内部存在同步写法
return new Promise(async function (resolve, reject){
// 漫画一章的序号,从1开始
let startPoint = 1;
// 这是图片链接,嵌入变量的地方为规律所在
let start_url = `http://mhpic.zymkcdn.com/comic/B%2F%E6%9A%B4%E8%B5%B0%E9%82%BB%E5%AE%B6%2F${comicId}%E8%AF%9D%2F${startPoint}.jpg-mht.middle`;
let filename = path.join(__dirname, 'src', comicId, '1.jpg');
// 这里的1000其实代表这个是死循环,因为我并不知道一章有多少页,我不断地发出请求,直到请求404,代表到头,跳出循环
for (let i = 0;
i < 1000;
i++) {
try {
await downloadImg(start_url, filename);
console.log('finish comic ' + comicId + ':' + startPoint);
} catch (e) {
console.log('****comic: ' + comicId +' finish******');
resolve();
break;
}
startPoint++;
// 重置图片链接与文件名
start_url = `http://mhpic.zymkcdn.com/comic/B%2F%E6%9A%B4%E8%B5%B0%E9%82%BB%E5%AE%B6%2F${comicId}%E8%AF%9D%2F${startPoint}.jpg-mht.middle`;
filename = path.join(__dirname, 'src', comicId, startPoint + '.jpg');
}
})
};
module.exports = downloadComic;
// download.js
// 引入之前写的几个子模块,第三方模块,以及node核心模块
const getListUri = require('./get_list_uri');
const downloadImg = require('./download_img');
const request = require('request');
// 网站主入口
const main_uri = 'http://www.manhuatai.com/baozoulinjia/';
const downloadComic = require('./download_comic');
let download = function () {
getListUri(main_uri)
.then(async function (comics) {
console.log('get list');
//这里是几次下载失败后从最近的没下载的地方开始。。。
// comics = comics.slice(44);
// comics = comics.slice(69);
// comics = comics.slice(120);
let keys = Object.getOwnPropertyNames(comics);
let length = keys.length;
//遍历获取的目录对象
for (let i = 0;
i < length - 1;
i++) {
let urlKey = '';
let key = keys[i];
//如果id为个位数,那么需要在前面加'0',根据对图片uri分析得出
if (comics[key].id.length === 1) {
urlKey = '0' + comics[key].id;
} else {
urlKey = comics[key].id;
}
await downloadComic(urlKey);
//下载间隙进行短暂停顿,防止服务器不响应(虽然效果不完美 :-( )
await timePause();
}
})
.catch((err) => {
console.log(err);
})
};
// 启动程序!
download();
// 对 setTimeout 函数进行Promise封装,使其可以同步调用,跳出事件循环
let timePause = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('next begin');
resolve();
}, 3000)
})
};
启动
node --harmony-async-await download.js
写在最后
- 箭头函数不支持async-await!!
- 箭头函数不支持async-await!!
- 箭头函数不支持async-await!!
- Array的变量方法里的函数(高阶函数)不支持async-await!!
- Array的变量方法里的函数(高阶函数)不支持async-await!!
- Array的变量方法里的函数(高阶函数)不支持async-await!!
推荐阅读
- Node.js时间戳和日期互相转换
- nodejs|package.json.lock 文件
- websocket|android中如何使用websocket,以及用nodejs做后台的实例
- javascript|使用vue实现自己音乐播放器仿网易云 移动端 (audio、播放、暂停、上一首、下一首、展示评论、音量控制、进度条拖拽)
- javascript|TODO-4(nodejs 实现一部漫画爬取)
- Nodejs使用前准备,基本使用、常用命令
- Node.js中相同模块是否会被加载多次()
- NodeJS使用lodash去重排序
- nodejs(五)node引入核心模块fs
- nodejs|windows 10系统nodejs安装ffi模块