追完动画,刚见到波波,战车这是咋了,啥是镇魂曲啊,怎么就完了,要等周六啊啊啊啊啊啊啊,act3附体,小嘴就像抹了蜜......

ヽ(。>д<)p

于是想到看漫画版,但网页体验较差,一次只能看一页,一页只有一张图,还不能存缓。

╮(╯_╰)╭

行吧,没缓存,我自己做。

大致看了下该页面的结构,做的不错,结构清晰、代码整洁、可读性好,那爬取就很方便了。

初步想法

明确下目标,需要的是任意一本漫画的全部内容,也就是图片src列表,然后批量下载到本地。没想过要爬整站(我是良民),入口就设为漫画的详情页好了,目测需要如下几个步骤:

编号 => 基本信息 => 章节列表 => 页列表 => 图片地址

个人比较熟悉node,插件应有尽有,写起来比较顺手,实现如下:

// 根据编号获取url地址
export async function getIndexUrl(number) {let url = `${baseUrl}/manhua/`;if (number) url += number;return url;
}// 获取漫画基本信息和章节目录
export async function getData(url) {const html = await fetch(url).then(res => res.text());const $ = cheerio.load(html, { decodeEntities: false });//   获取titleconst title = await $('h1.comic-title').text();//   获取简介const description = await $('p.comic_story').text();//   获取creatorsconst creators = [];function getCreators() {creators.push($(this).find('a').text());}await $('.creators').find('li').map(getCreators);//   获取卷const list = [];function getVlue() {const href = `${baseUrl}${$(this).find('a').attr('href')}`;list.push({href,});}await $('.active .links-of-books.num_div').find('li.sort_div').map(getVlue);//  获取数据const data = [];function getComicData() {const key = $(this).find('td').attr('class') || $(this).find('td a').attr('class');const value = key === 'comic-cover' ? $(this).find('td img').attr('src') : $(this).find('td').text();const label = $(this).find('th').text();data.push({key,label,value,});}await $('.table.table-striped.comic-meta-data-table').find('tr').map(getComicData);const detail = await $('article.comic-detail-section').html();return {title,creators,description,list,data,detail,};
}// 获取章节内全部页
export async function getPageList(url) {const html = await fetch(url).then(res => res.text());const $ = cheerio.load(html, { decodeEntities: false });const list = [];function getPage2() {list.push(`${baseUrl}${$(this).attr('value')}`);}await $('select.form-control').eq(0).find('option').map(getPage2);return list;
}// 获取页内图片地址
export async function getPage(url) {const html = await fetch(url).then(res => res.text());const $ = cheerio.load(html, { decodeEntities: false });const src = await $('img.img-fluid').attr('src');return `${baseUrl}${src}`;
}
复制代码

先将写好的步骤组合起来:

async function test(number) {const indexUrl = await getIndexUrl(number);const data = await getData(indexUrl);await Promise.all(data.list.map(async (i, idx) => {let pageList = await getPageList(i.href);const imgs = [];await Promise.all(pageList.map(async (pages, pdx) => {await Promise.all(pages.map(async (j, jdx) => {let src = await getPage(j);imgs.push(src);}));}));}));fs.writeFileSync(`../jojo/${number}.json`, JSON.stringify(data));
}
复制代码

嗯,很简单嘛。就是看起来太暴力了,这样不好,别给人服务器太大压力。

串行promise

实现类似Promise.all,但不同点在于,如果是数组直接返回promise,众所周知promise在创建时就已经执行了,不可能串行。于是将数组改写,返回一个可执行函数的队列,在串行方法里去执行,这样就可以实现了。

export function sequence(promises) {return new Promise((resolve, reject) => {let i = 0;const result = [];function callBack() {return promises[i]().then((res) => {i += 1;result.push(res);if (i === promises.length) {resolve(result);}callBack();}).catch(reject);}return callBack();});
}// 使用方法
sequence([1, 2, 3, 4, 5].map(number => () => new Promise((resolve, reject) => {setTimeout(() => {console.log(`$${number}`);resolve(number);}, 1000);
}))).then((data) => {console.log('成功');console.log(data);
}).catch((err) => {console.log('失败');console.log(err);
});复制代码

分组并发

完全串行的话,和正常逐步浏览网页没什么区别,性能是没问题了,但那有什么意义,我们得加快速度。于是想到将200页分组,每10个一组,每组串行执行,组内并行执行,相当于10个用户同时访问网站,这总不可能扛不住吧,如此应该能兼顾效率和性能。

分组就没必要自己写了,使用lodash/chunk和Promise.all即可。

异常处理

考虑到爬取一个章节,至少都是200页,还是并行的,不敢保证中途不出任何意外,基本的异常处理还是要做的。

赶时间,直接将利用race来实现,只要没有结果,一律重试,实践起来还是比较稳的。

完整的爬取方法

async function test(number) {const indexUrl = await getIndexUrl(number);const data = await getData(indexUrl);await sequence(data.list.map((i, idx) => async () => {await sleep(Math.random() * 1000);let pageList = await getPageList(i.href);pageList = pageList.map(src => ({ src, index: parseInt(src.substr(42, 3).replace('_', ''), 0) })).sort((x, y) => x.index - y.index).map(({ src }) => src);const imgs = [];const len = 5;// 分len一组,串行访问await sequence(chunk(pageList, len).map((pages, pdx) => async () => {await sleep(Math.random() * 5000);// 以len一组,并行访问await Promise.all(pages.map(async (j, jdx) => {let src;async function race() {await sleep(Math.random() * 1000);const res = await Promise.race([ getPage(j), sleep(5000) ]);if (res) {src = res;console.log(`拿到src: ${src}`);} else {console.log('重新加入队列');await race();}}await race();imgs.push(src);}));}));data.list[idx].list = imgs.map(src => ({ src, index: parseInt(src.substr(42, 3).replace('_', ''), 0) })).sort((x, y) => x.index - y.index).map(({ src }) => src);}));console.log('爬取成功!!!');fs.writeFileSync(`../jojo/${number}.json`, JSON.stringify(data));
}
复制代码

批量下载

以上,已经拿到了所需的全部信息。

接下来只需要批量下载即可,这好办,先整一个promise化的下载方法:

function downloadFile(url, filepath) {return new Promise((resolve, reject) => {// 块方式写入文件const ws = fs.createWriteStream(filepath);ws.on('open', () => {console.log('下载:', url, filepath);});ws.on('error', (err) => {console.log('出错:', url, filepath);reject(err);});ws.on('finish', () => {console.log('完成:', url, filepath);resolve(true);});request(url).pipe(ws);});
}
复制代码

再如法炮制一套串行并行混合的下载规则:

async function readJson() {
//   await checkPath('../jojo');const data = await readFile('../jojo/128.json');const json = JSON.parse(data);console.log(json.list[0].list.length);const cur = 10;await sequence(json.list.map((list, idx) => async () => {await sequence(chunk(list.list, cur).map((pages, pdx) => async () => {await sleep(Math.random() * 1000);console.log('正在获取:', `/jojo/${idx + 1}/`, ` 第${pdx + 1}批`);await Promise.all(pages.map(async (j, jdx) => {const dirpath = `../jojo/${idx + 1}`;const filepath = `../jojo/${idx + 1}/${cur * pdx + jdx + 1}.jpg`;await checkPath(dirpath);async function race() {await sleep(Math.random() * 1000);const res = await Promise.race([downloadFile(j, filepath),sleep(20000),]);if (res) {console.log('下载成功');} else {console.log('重新加入队列');await race();}}await race();}));}));}));console.log('获取成功');
}
复制代码

尝试一下,问题不大,图片会按章节下载到对应目录,速度比自己翻快多了,目标达成!

折腾了几个小时,终于又可以愉快地在ipad上看漫画咯,我真是嗨到不行了~

[]~( ̄▽ ̄)~* (~ ̄▽ ̄)~

完整代码:github.com/liumin1128/…。

PS1:仅供学习,请在24小时内删除。

PS2:有机会请支持正版。

PS3:整天看动漫,感觉自己不务正业啊。

转载于:https://juejin.im/post/5d0090c0e51d45772a49ad22

如何使用Nodejs爬虫看漫画相关推荐

  1. Python爬虫,爬取快看漫画每日更新模块

    文章目录 前言 一.爬虫是什么? 二.使用步骤 1.引入库 2.文件夹准备 3.将列表存储为txt格式文件 4.爬取每日更新页面具体代码: 5.运行结果 总结 前言 根据基本的爬虫知识,爬取快看漫画每 ...

  2. Python爬虫-爬取快看漫画网图片并拼接成漫画长图

    Python爬虫-爬取快看漫画网图片并拼接成漫画长图 1.爬取图片 2.拼接图片 1.爬取图片 import os import requests from bs4 import BeautifulS ...

  3. 看漫画MHGmhgui,Python爬虫之神奇的eval,附赠一个压缩模块

    文章目录 ⛳️ 看漫画MHG mhgui 实战分析 ⛳️ 看漫画MHG mhgui 实战分析 本文所有MHG使用 MHG 替代~ 无障碍阅读版本请参考:https://www.cnblogs.com/ ...

  4. 看漫画学python 豆瓣_过去,我买漫画看;现在,我用Python爬虫来看

    原标题:运用Python多线程爬虫下载漫画 前言: 以前,我都是买漫画书看的,那个时候没有电脑.今天,我到网上看了一下,发现网上提供漫画看,但是时时需要网络啊!为什么不将它下载下来呢! 1.怎样实现 ...

  5. python 爬虫 快看漫画整站爬取(解决动态加载漫画图片地址、漫画图片合成长图、图片文件排序等问题)

    运行结果: 这个是爬取的目标 爬取之后建立文件夹,合成长图之后删除文件夹 这里仅仅做几组演示, 由于合成的图片有单列长度限制,所有拆分成两列 开始: 首先打开网站,找到某个漫画,发现点鼠标右键不可以, ...

  6. 看漫画学Python,屏幕前的彦祖要不要试试?

    Python是一门既简单又强大的编程语言,被广泛应用于数据分析.大数据.网络爬虫.自动化运维.科学计算和人工智能等领域.Python也越来越重要,成为国家计算机等级考试科目,某些中小学也开设了Pyth ...

  7. 看漫画学python电子书-看漫画还能学Python❓❓❓| 0基础小白福音

    ��你还在枯燥无味地学编程吗?你还在闷头背诵那些根本没有理解的内容?根本不用那么煎熬!想不想来体验一下翻着漫画就搞定Python的感觉?? <看漫画学Python:有趣.有料.好玩.好用(全彩版 ...

  8. python入门学习[看漫画学Python:有趣、有料、好玩、好用读书笔记]

    写在前面:本文中绝大多数图片来源于图书:看漫画学Python:有趣.有料.好玩.好用,本文仅供个人学习使用,如有侵权,请联系删除. 学习编程语言最好的方式就是多写,多写,多写!!!哪有什么快速掌握,能 ...

  9. 利用Termux在手机上运行爬虫下载漫画

    前言 前段时间喜欢上了几部漫画,发现了一个宝藏网站"拷贝漫画".上面有很多我想看的漫画,但是访问速度很慢,官方提供的下载又有次数限制.于是就在GITHUB上找了一个大佬写的爬虫. ...

最新文章

  1. (android)system ui 内存优化
  2. jquery tab点击切换的问题
  3. 计算机网络优化是啥,浅析计算机网络优化的方案.doc
  4. python遍历循环中的遍历结构可以是什么_(一)Python入门-4控制语句:06for循环结构-遍历各种可迭代对象-range对象...
  5. LeetCode 6061. 买钢笔和铅笔的方案数
  6. MD5 算法描述及实现
  7. 计算机网络模拟校园,计算机网络课程设计模拟校园网组网实验.doc
  8. 《树莓派Python编程入门与实战》——2.1 了解Linux
  9. basler相机参数简要中文说明_Basler相机参数在NI软件下打开相机参数说明
  10. 径向基神经网络与多层感知器的比较
  11. matlab函数表达式里分号_matlab中分号
  12. 一文了解驱动程序及更新方法
  13. 你为什么总是爱拖延?这个我知道
  14. Remix Icon
  15. SIM7600连接阿里云
  16. C语言两种方法求圆的面积与周长编程
  17. 画色彩如何画出体积感
  18. 计算机课学生电脑怎么打开任务管理器,电脑任务管理器的打开方法
  19. ACMCODER-股神
  20. 【概述】 无人驾驶汽车系统基本框架梳理

热门文章

  1. Kirito 的博客崩了,这次是因为...
  2. 作为国企程序员,是一种怎样的体验?
  3. 今天的面试官是个锤子,Spring为什么建议使用构造器来注入?
  4. 你真的会写单例模式吗?
  5. Nacos 1.3.0 发布, 全新内核构建
  6. springboot应用如何提高服务吞吐量?
  7. Spring AOP是什么?你都拿它做什么?
  8. 数据质量和模型调优哪个更重要?
  9. 周志华,李航来智源大会了!
  10. 官宣!英雄联盟、王者荣耀、街霸……这些电子竞技入选杭州亚运会