如何使用Nodejs爬虫看漫画
追完动画,刚见到波波,战车这是咋了,啥是镇魂曲啊,怎么就完了,要等周六啊啊啊啊啊啊啊,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爬虫看漫画相关推荐
- Python爬虫,爬取快看漫画每日更新模块
文章目录 前言 一.爬虫是什么? 二.使用步骤 1.引入库 2.文件夹准备 3.将列表存储为txt格式文件 4.爬取每日更新页面具体代码: 5.运行结果 总结 前言 根据基本的爬虫知识,爬取快看漫画每 ...
- Python爬虫-爬取快看漫画网图片并拼接成漫画长图
Python爬虫-爬取快看漫画网图片并拼接成漫画长图 1.爬取图片 2.拼接图片 1.爬取图片 import os import requests from bs4 import BeautifulS ...
- 看漫画MHGmhgui,Python爬虫之神奇的eval,附赠一个压缩模块
文章目录 ⛳️ 看漫画MHG mhgui 实战分析 ⛳️ 看漫画MHG mhgui 实战分析 本文所有MHG使用 MHG 替代~ 无障碍阅读版本请参考:https://www.cnblogs.com/ ...
- 看漫画学python 豆瓣_过去,我买漫画看;现在,我用Python爬虫来看
原标题:运用Python多线程爬虫下载漫画 前言: 以前,我都是买漫画书看的,那个时候没有电脑.今天,我到网上看了一下,发现网上提供漫画看,但是时时需要网络啊!为什么不将它下载下来呢! 1.怎样实现 ...
- python 爬虫 快看漫画整站爬取(解决动态加载漫画图片地址、漫画图片合成长图、图片文件排序等问题)
运行结果: 这个是爬取的目标 爬取之后建立文件夹,合成长图之后删除文件夹 这里仅仅做几组演示, 由于合成的图片有单列长度限制,所有拆分成两列 开始: 首先打开网站,找到某个漫画,发现点鼠标右键不可以, ...
- 看漫画学Python,屏幕前的彦祖要不要试试?
Python是一门既简单又强大的编程语言,被广泛应用于数据分析.大数据.网络爬虫.自动化运维.科学计算和人工智能等领域.Python也越来越重要,成为国家计算机等级考试科目,某些中小学也开设了Pyth ...
- 看漫画学python电子书-看漫画还能学Python❓❓❓| 0基础小白福音
��你还在枯燥无味地学编程吗?你还在闷头背诵那些根本没有理解的内容?根本不用那么煎熬!想不想来体验一下翻着漫画就搞定Python的感觉?? <看漫画学Python:有趣.有料.好玩.好用(全彩版 ...
- python入门学习[看漫画学Python:有趣、有料、好玩、好用读书笔记]
写在前面:本文中绝大多数图片来源于图书:看漫画学Python:有趣.有料.好玩.好用,本文仅供个人学习使用,如有侵权,请联系删除. 学习编程语言最好的方式就是多写,多写,多写!!!哪有什么快速掌握,能 ...
- 利用Termux在手机上运行爬虫下载漫画
前言 前段时间喜欢上了几部漫画,发现了一个宝藏网站"拷贝漫画".上面有很多我想看的漫画,但是访问速度很慢,官方提供的下载又有次数限制.于是就在GITHUB上找了一个大佬写的爬虫. ...
最新文章
- (android)system ui 内存优化
- jquery tab点击切换的问题
- 计算机网络优化是啥,浅析计算机网络优化的方案.doc
- python遍历循环中的遍历结构可以是什么_(一)Python入门-4控制语句:06for循环结构-遍历各种可迭代对象-range对象...
- LeetCode 6061. 买钢笔和铅笔的方案数
- MD5 算法描述及实现
- 计算机网络模拟校园,计算机网络课程设计模拟校园网组网实验.doc
- 《树莓派Python编程入门与实战》——2.1 了解Linux
- basler相机参数简要中文说明_Basler相机参数在NI软件下打开相机参数说明
- 径向基神经网络与多层感知器的比较
- matlab函数表达式里分号_matlab中分号
- 一文了解驱动程序及更新方法
- 你为什么总是爱拖延?这个我知道
- Remix Icon
- SIM7600连接阿里云
- C语言两种方法求圆的面积与周长编程
- 画色彩如何画出体积感
- 计算机课学生电脑怎么打开任务管理器,电脑任务管理器的打开方法
- ACMCODER-股神
- 【概述】 无人驾驶汽车系统基本框架梳理