博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Re: 从零开始的【comic spider】《最简单的实现》(上)
阅读量:7043 次
发布时间:2019-06-28

本文共 8545 字,大约阅读时间需要 28 分钟。

前言

一个最简单的漫画脚本最基本的功能是下载,但是在是下载之前还要做什么?是找资源!那么“搜索”功能也应该算在一个最简单漫画爬虫的基本功能。所以,接下来围绕这两个功能看看从一零开始的 !

ps:我们用于爬取漫画的站点确定为

欢迎 和 yo!

正文

实现目标

# 下载漫画全本icsdr http://xxx# 搜索漫画icsdr --search [comic name]复制代码

分析页面

目录页面

我们以 的目录页为例子。 目录页面的分析目标是:1. 获取漫画名;2. 获取漫画每章节的链接;

审查元素,可以找到漫画名的节点:

我们可以通过以下方式复制节点路径方便待会获取对应节点:

# 漫画名的节点路径#intro_l > div.title > h1# 目录章节节点路径#play_0 ul > li > a复制代码

 

章节内漫画图片页

图片页就不能直接通过审查元素获取节点路径获取图片的资源链接了。图片的节点一般都不会直接在服务端渲染后,基本上都是在浏览器端渲染,因此我们直接用axios请求到的html文本是浏览器渲染前的,言外之意就是里面是没有图片的节点的!

那么,想要获取图片的资源链接我们就需要直接从源码中找图片链接的组成规则,当前这个网站也是如此!

现在我打开,审查元素可以看到解析后的图片链接:

根据这个渲染后的链接去源码里面找链接的组成方式:打开
控制台 ->
source,开始阅读源码找组合图片链接的逻辑,此处省略十年!

然后,以下是我找到的组合规则,并且封装成函数:

const imgOrigins = ['http://2.csc1998.com/', 'http://img.csc1998.com/'];const viewidThreshold = 519253;/** * 获取图片origin * @param { number } viewId */function getImgOrigin(viewId) {  let origin;  if (viewId >= viewidThreshold) {    origin = imgOrigins[0];  } else {    origin = imgOrigins[1];  }  return origin;}/** * 获取图片链接的后半部分 * @param { string } _doc html文本 */function getImgPaths(_doc) {  const photosr = [];  const packedMatcheds = _doc.match(/packed=\"(.*)\"\;/);  const packed = packedMatcheds && packedMatcheds[1];  if (packed) {    const temp = Buffer.from(packed, 'base64').toString();    eval(eval(temp.slice(4)));    photosr.shift();  } else {    const photosrMatched = _doc.match(/photosr\[.+\] =\".*";/);    if (photosrMatched) {        eval(photosrMatched[0]);        photosr.shift();    }  }  return photosr;}/** * 组合img的origin和 paths * @param { string } _origin * @param { string[] } _paths */function generateImgSrcs(_origin, _paths) {    const srcs = _paths.map((imgPath) => {        return url.resolve(_origin, imgPath);    });    return srcs;}复制代码

 

搜索的分析

首先是先找到搜索的api是什么!


控制台 -> Network,然后进行搜索,你就会发现这个搜索的api(大部分的站点也是这么找)

那么搜索的api即是:

http://www.chuixue.net/e/search/?searchget=1&show=title,player,playadmin,pinyin&keyboard=[keywork]复制代码

 

接下来就是分析结果列表


搜索结果的节点路径

#dmList > ul > li > dl > dt > a复制代码

这样就可以拿到搜索到的资源了!

 

 

实现下载

需要用到的npm包:

  • :请求资源(获取页面html和下载漫画图片流)

  • :将html文本转化成dom,方便提取目标资源链接

    主要用到的几个方法是:

    1. .text( [textString] ):获取节点文本
    2. .attr([string]):获取节点属性
    3. .html([string]):获取节点html文本
    4. .eq([number]):获取节点列表中的其中之一
  • iconv-lite: 用于转码请求回来的html文本,主要是为了解决中文乱码问题

    这里补充说明如何知道html文本的编码。这里有两个方法:

    1. 控制台 -> Element:

    1. 控制台 -> Console, 打印document.characterSet

获取目录资源

获取目录的dom对象


  1. axios请求目录页面,获取html文本;
  2. iconv-lite将html文本转码成utf-8解决中文乱码;
  3. 将html文本转化成cheerio的dom对象;
/** * 获取目录dom对象 * @param {*} _url  */function getCatalogDom(_url) {  const reqOptions = {    method: 'GET',    url: _url,    responseType: 'arraybuffer'  };  return axios.request(reqOptions).then((resp) => {    const data = iconv.decode(resp.data, 'GBK');    const doc = cheerio.load(data);    return doc;  }).catch((err) => {    console.log(err);  });}复制代码

 

获取漫画名字


漫画名字节点路径我们已经知道,使用刚刚拿到的dom对象,使用cheerio的.text()获取名字。

/** * 获取漫画名字 * @param { string } _doc */function getComicName(_doc) {  const selectorPath = '#intro_l > div.title > h1';  return _doc(selectorPath).text();}复制代码

 

获取章节列表资源


使用上面找到的节点路径即可获取到章节的节点列表,然后使用.eq遍历配合.attr方法即可获取到章节的名字和链接。

/** * 获取章节列表资源 * @param { object } _doc  * @param { string } _catalogUrl  */function getComicChaptes(_doc, _catalogUrl) {  const { URL } = url;  const origin = (new URL(_catalogUrl)).origin;  const selectorPath = '#play_0 ul > li > a';  const chapterEles = _doc(selectorPath);  const list = [];  for (let i = 0, len = chapterEles.length; i < len; i+=1) {    const ele = chapterEles.eq(i);    list.push({      title: ele.attr('title'),      url: url.resolve(origin, ele.attr('href'))    })  }  return list;}复制代码

 

封装一下以上函数


/** * 获取目录资源 * @param { string } _catalogUrl */function parseCatalog(_catalogUrl) {  return getCatalogDom(_catalogUrl)    .then((_doc) => {      const comicName = getComicName(_doc);      const catalogs = getComicChaptes(_doc, _catalogUrl);      return {        comicName,        catalogs      };    });}复制代码

然后我们只需调用这个parseCatalog函数即可获取到漫画的目录资源

 

 

获取漫画图片资源

解析完目录之后,就是要获取到每个章节的图片支资源链接,这个也是站点极力想要保护的部分,或多或少都会做一些防盗链的措施,比如就把图片资源放列表用base64加密,然后将解密逻辑隐藏在其他文件,这样直接抓取漫画页就不能很容易地拿到图片链接。

上面我已经找到组合图片链接的逻辑,现在我们首先要做的是把html文本爬回来,然后正则匹配到我们需要的资源;

/** * 获取章节的图片资源链接 * @param { string } _chapterUrl  */function getImageSrcList(_chapterUrl) {  const imgSrcList = [];  const reqOptions = {    method: 'GET',    url: _chapterUrl  };  return axios.request(reqOptions).then((resp) => {    const doc = resp.data || '';    const viewId = doc.match(/var viewid = \"(.*)\"\;/)[1];    const origin = getImgOrigin(viewId);    const imgPaths = getImgPaths(doc);    const imgSrcList = generateImgSrcs(origin, imgPaths);    return imgSrcList;  })}复制代码

现在我们就获取第127话的图片资源:

(function __main() {  parseCatalog(config.catalog)    .then((_data) => {      // 最后一章节(127话)      const lastedChapter = _data.catalogs.pop();      getImageSrcList(lastedChapter.url).then((imgSrcList) => {        console.log(imgSrcList);      });    });})();复制代码

 

 

下载资源

以上我们已经实现了一部分逻辑,已经可以拿到图片的资源链接,接下来我们只需要将图片下载回来就ok!

下面用递归实现对第127话漫画图片的下载(当然你也可以直接遍历,并在遍历的过程中直接发请求,让所有的下载一起并发,但是啊,请求得过于频繁怕不是会被禁了你ip?!)。

(function _download(index) {  const imgSrc = imgSrcList[index];  if (imgSrc) {    console.log('download:', imgSrc);    const format = imgSrc.split('.').pop();    const picIndex = index + 1;    const fileName = picIndex + '.' + format;    const savePath = path.join(process.cwd(), fileName);    downloadPic(imgSrc, savePath).then(() => {      _download(++index);    }).catch((error) => {      console.log(error.message);    });  } else {    console.log('finish chapter!');  }})(0);复制代码

下载部分到此就基本结束,接下我们在实现搜索的逻辑,然后就可以进入优化的阶段~

 

 

搜索逻辑的实现

  1. axios请求搜索的api,获取搜索到的html文本
  2. 解析html文本,获取到搜索的结果
/** * 站内搜索漫画 * @param { string } keyword  */function search(_keyword) {  console.log('search: ' + _keyword);  const selectorPath = '#dmList > ul > li > dl > dt > a';  const keyword = encodeText(_keyword);  const origin = 'http://www.chuixue.net/';  const searchUrl = 'http://www.chuixue.net/e/search/?searchget=1&show=title,player,playadmin,pinyin&keyboard=' + keyword;  const reqOptions = {    method: 'GET',    url: searchUrl,    responseType: 'arraybuffer'  };  const promise = axios.request(reqOptions).then((resp) => {    const html = iconv.decode(resp.data, 'GBK');    const doc = cheerio.load(html);    const eles = doc(selectorPath);    let searchList = [];    for (let i = 0, len = eles.length; i < len; i += 1) {      const ele = eles.eq(i);      const name = ele.attr('title');      const src = url.resolve(origin, ele.attr('href'));      searchList.push({ name, src });    }    searchList = searchList.filter((item) => {      return item.name;    });    return searchList;  });  return promise;}复制代码

看上面的实现逻辑,你在函数的第三行你会发现这么一个函数encodeText,这个不是语言自带的函数,而是自己封装的,这个转码函数是个什么东西???

/** * 转码搜索关键字 * @param { string } _text  */function encodeText(_text) {  const buf = iconv.encode(_text, 'GBK');  const hex = buf.toString('hex');  const chars = hex.split('');  let text = '';  for (let i = 0, len = chars.length; i < len; i += 2) {    const char = chars.slice(i, i + 2).join('');    text += `%${char.toUpperCase()}`;  }  return text;}复制代码

你会发现转码后的结果和urlencode的结果很像,但其实不是~,你打开控制台的Network你会发现

搜索的关键字确实是被转码了,但是又不是被urlencode了,但是你用encode试下你就会发现:

并不一样~

这是为什么?

假如你对此感兴趣你去看看这篇()。你去看这部分搜索的源码你会发现,代码并没有做任何的转码操作,仅仅只是提交了表单!言外之意这个转码的过程是浏览器做的~

回归正题,搜索的实现结果:

 

 

优化脚本

回一下我们的实现目标:

# 下载漫画全本icsdr http://xxx# 搜索漫画icsdr --search [comic name]复制代码

做成命令行执行~

这时候我们就需要这个库!

#!/usr/bin/env node const spider = require('./index');const program = require('commander');const config = {  isSearch: false,  isDownload: true,  comicName: '',  downloadLink: ''};program  .version('0.1.0')  .option('-s, --search [comic name]', 'Search Comic', function(comicName) {    config.isSearch = true;    config.isDownload = false;    config.comicName = comicName;  })  .parse(process.argv);(function __main() {  if (config.isDownload) {    if (!config.downloadLink) {      config.downloadLink = process.argv.pop();    }    spider.download(config.downloadLink);  } else {    spider.search(config.comicName);  }})();复制代码

此时此刻你已经可以使用命令行执行脚本:

# 下载漫画node icsdr.js [catalog link]# 搜索漫画node icsdr.js -s [comic name]复制代码

已经很接近我们的目标,这个时候我们还需要修改一下我们的package.json(如果没有就npm init)

下面关键且必须的部分:

"name": "icsdr",...// 指明脚本执行文件"bin": {    "icsdr": "./icsdr.js"},复制代码

你想要直接使用icsdr命令,有好几个方法懂得自己然懂。

  • 写好发布到npm源上,然后全局安装就好
  • 本地调试可以使用npm link
  • 笨点可以直接使用路径全局安装:npm i [absolute-path/relative-path] -g

然后你就可以:

当然这里为了演示只是下载了第127话!

本次代码例子已经上传到,可以自行食用

欢迎star我的

小结

  1. axios获取html文本;
  2. cheerio转化html文本cheerio的dom和正则匹配获取所需资源;
  3. axios获取图片流资源,fs保存文件;
  4. commander将脚本命令行化;

以上简单说了一下comic spider的基本实现,下一篇将基于这个“基本实现”扩展一下下载相关的功能!

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

你可能感兴趣的文章
端口镜像 SPAN && RSPAN
查看>>
跨界的一点点感悟
查看>>
在有母版页的页面里使用FindControl的困惑
查看>>
UI设计师面试时必须注意的6大问题
查看>>
无线宽带路由器300M导致的网络故障
查看>>
VMware Virtual SAN 设计与规模调整指南
查看>>
我的友情链接
查看>>
SET-MAP现代诗一首
查看>>
ConfigParser
查看>>
CentOS系统启动
查看>>
浅尝 Windows Server 2016 —— Container 容器:概念
查看>>
我的友情链接
查看>>
MSBuild
查看>>
6.3 bash编程 字符测试
查看>>
配置管理小报091124:*** glibc detected *** double free or corruption (fasttop): 0x08b60068 ***
查看>>
配置管理小报101010:数据库修复方法。
查看>>
Linux安装配置apache
查看>>
PHP 调试利器 debug_print_backtrace
查看>>
SQL 结果集标识
查看>>
20分钟轻松制作移动网站
查看>>