Node.js 实现简单小说爬虫实例


Posted in Javascript onNovember 18, 2016

最近因为剧荒,老大追了爱奇艺的一部网剧,由丁墨的同名小说《美人为馅》改编,目前已经放出两季,虽然整部剧槽点满满,但是老大看得不亦乐乎,并且在看完第二季之后跟我要小说资源,直接要奔原著去看结局……

随手搜了下,都是在线资源,下载的话需要登录,注册登录好麻烦,写个爬虫玩玩也好,于是动手用 node 写了一个,这里做下笔记

工作流程

  • 获取 URLs 列表(请求资源 request模块)
  • 根据 URLs 列表获取相关页面源码(可能遇到页面编码问题,iconv-lite模块)
  • 源码解析,获取小说信息( cheerio模块)
  • 保存小说信息到 Markdown 文件,并且加适当修饰以及章节信息(写文件 fs、同步请求资源 sync-request 模块)
  • Markdown 转 PDF (使用 Pandoc 或者 Chrome 的打印功能)

获取 URLs

根据小说的导航页,获取小说所有章节的 URL,并且以 JSON 数组的方式存储。

  • 首选通过 http.get() 方法获取页面源码
  • 获取到源码,打印发现中文乱码,查看发现 charset = 'gbk',需要进行转码
  • 使用 iconv-lite 模块进行转码,中文显示正常后开始解析源码,获取需要的 URL,为了更方便地解析,需要引进 cheerio 模块,cheerio 可以理解为运行在后台的 jQuery,用法与 jQuery 也十分相似,熟悉 jQuery 的同学可以很快的上手
  • 将源码加载进 cheerio,分析了源码后得知所有章节信息都存于被 div 包裹的 a 标签中,通过 cheerio 取出符合条件的 a 标签组,进行遍历,获取章节的 title 和 URL,保存为对象,存进数组,(因为链接中存储的 URL 不完整,所以存储时需要补齐)
  • 将对象数组序列化,写进 list.json 文件
var http = require("http")
var fs = require("fs")
var cheerio = require("cheerio")
var iconv = require("iconv-lite")
var url = 'http://www.17fa.com/files/article/html/90/90747/index.html'
http.get(url, function(res) { //资源请求
  var chunks = []
  res.on('data', function(chunk) {
    chunks.push(chunk)
  })
  res.on('end', function() {
    var html = iconv.decode(Buffer.concat(chunks), 'gb2312') //转码操作
    var $ = cheerio.load(html, {
      decodeEntities: false
    })
    var content = $("tbody")
    var links = []
    $('div').children('a').each(function(i, elem) {
      var link = new Object()
      link.title = $(this).text()
      link.link = 'http://www.17fa.com/files/article/html/90/90747/' + $(this).attr('href') //补齐 URL 信息
      if (i > 5) {
        links.push(link)
      }
    })
    fs.writeFile("list.json", JSON.stringify(links), function(err) {
      if (!err) {
        console.log("写文件成功")
      }
    })
  }).on('error', function() {
    console.log("网页访问出错")
  })
})

获取的列表示例

[{
  "title": "3 法医司白",
  "link": "http://www.17fa.com/files/article/html/90/90747/16548771.html"
}, {
  "title": "4 第1个梦 ",
  "link": "http://www.17fa.com/files/article/html/90/90747/16548772.html"
}, {
  "title": "5 刑警韩沉 ",
  "link": "http://www.17fa.com/files/article/html/90/90747/16548773.html"
}, {
  "title": "6 最初之战",
  "link": "http://www.17fa.com/files/article/html/90/90747/16548774.html "
}]

获取数据

有了 URLs 列表,接下来的工作就很机械了,遍历 URLs 列表请求资源,获取源码,解析源码,获取小说,写文件,但是,因为最终将所有的章节保存入一个文件,要保证章节的顺序,因此写文件需要 同步操作,实际上,我在编码的时候所有的操作都改成了同步方式

获取源码

通过解析读取的 list.json 文件,获取到 URLs 列表,遍历列表获取资源,因为需要确保章节的顺序,所以这里引进 sync-request 模块进行同步 request 请求资源,请求资源后照例转码

var http = require("http")
var fs = require("fs")
var cheerio = require("cheerio")
var iconv = require("iconv-lite")
var request = require('sync-request')
var urlList = JSON.parse(fs.readFileSync('list.json', 'utf8'))
function getContent(chapter) {
  var res = request('GET',chapter.link)
  var html = iconv.decode(res.body, 'gb2312') //获取源码
}
for (let i = 0; i < urlList.length; i++) {
  getContent(urlList[i])
}

解析源码,获取小说

还是通过 cheerio 模块获取小说内容,避免影响观感,写操作之前去除内容中的的 html 标签

function getContent(chapter) {
  var res = request('GET',chapter.link)
  var html = iconv.decode(res.body, 'gb2312')
  var $ = cheerio.load(html, {
    decodeEntities: false
  })
  var content = ($("div#r1c").text()).replace(/\ /g, '')
}

保存小说

写操作也需要同步操作,因此使用了同步写函数 fs.writeFileSync() 和 同步添加函数 fs.appendFileSync(),第一次写使用写函数,之后的内容都是进行 append 操作,为了改善阅读体验,每个章节前添加标题

也可以在内容前添加 拍 [TOC],作为导航链接Node.js 实现简单小说爬虫实例

var http = require("http")
var fs = require("fs")
var cheerio = require("cheerio")
var iconv = require("iconv-lite")
var path = require('path')
var urlList = JSON.parse(fs.readFileSync('list.json', 'utf8'))
function getContent(chapter) {
  console.log(chapter.link)
  http.get(chapter.link, function(res) {
    var chunks = []
    res.on('data', function(chunk) {
      chunks.push(chunk)
    })
    res.on('end', function() {
      var html = iconv.decode(Buffer.concat(chunks), 'gb2312')
      var $ = cheerio.load(html, {
        decodeEntities: false
      })
      var content = ($("div#r1c").text()).replace(/\ /g, '')
      if (fs.existsSync('美人为馅.md')) {
        fs.appendFileSync('美人为馅.md', '### ' + chapter.title)
        fs.appendFileSync('美人为馅.md', content)
      } else {
        fs.writeFileSync('美人为馅.md', '### ' + chapter.title)
        fs.appendFileSync('美人为馅.md', content)
      }
    })
  }).on('error', function() {
    console.log("爬取" + chapter.link + "链接出错!")
  })
}
for (let i = 0; i < urlList.length; i++) {
  console.log(urlList[i])
  getContent(urlList[i])
}

Markdown 转 PDF

我将小说保存在 Markdown 文件中,为了提升阅读体验,可以将 Markdown 文件转换成 PDF 文件,目前我较为喜欢的两种方式,通过 Chrome 的打印功能 以及 pandoc 转换

Chrome 打印

SublimeText 有个插件 markdown preview ,可通过 Alt + m 快捷键在 Chrome 中预览 Markdown,在 Chrome 页面中右键,选择打印,调整好参数后,选择另存为 PDF,简单,粗暴,深得我心

打印效果:Node.js 实现简单小说爬虫实例

pandoc 转换
pandoc 是十分强大的文件格式转换工具,可以将 Markdown 文件转换成多种格式,今晚在 windows10 下折腾了半天,始终检索不到 pdflatex,关于 pandoc,后面会专门写一篇总结。

PDF 已经发给老大了,现在正在看

关于python、node、爬虫

在之前很长的一段时间里,很想用 Python,很想写爬虫,更想用 Python 写爬虫,甚至成为了心里的一块执念,随着接触的知识更全面,执念也逐渐淡去,少了很多“想”,遇事想着多去动手,实践出真知。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
把JS与CSS写在同一个文件里的书写方法
Jun 02 Javascript
jquery入门——事件机制之事件中的冒泡现象示例解释
Sep 12 Javascript
html超链接打开窗口大小的方法
Mar 05 Javascript
javascript中DOM复选框选择用法实例
May 14 Javascript
最丑的时钟效果!js canvas时钟制作方法
Aug 15 Javascript
Bootstrap table的使用方法
Nov 02 Javascript
Angular.Js中过滤器filter与自定义过滤器filter实例详解
May 08 Javascript
vue.js给动态绑定的radio列表做批量编辑的方法
Feb 28 Javascript
jQuery判断自定义属性data-val用法示例
Jan 07 jQuery
Vue ElementUI实现:限制输入框只能输入正整数的问题
Jul 31 Javascript
微信小程序选择图片控件
Jan 19 Javascript
如何在JavaScript中使用localStorage详情
Feb 04 Javascript
基于jQuery的select下拉框选择触发事件实例分析
Nov 18 #Javascript
jQuery实现隔行变色的方法分析(对比原生JS)
Nov 18 #Javascript
jQuery和JavaScript节点插入元素的方法对比
Nov 18 #Javascript
js与jquery分别实现tab标签页功能的方法
Nov 18 #Javascript
jQuery实现点击某个div打开层,点击其他div关闭层实例分析(阻止冒泡)
Nov 18 #Javascript
浅谈javascript中遇到的字符串对象处理
Nov 18 #Javascript
文件上传,iframe跨域数据提交的实现
Nov 18 #Javascript
You might like
php设计模式 Visitor 访问者模式
2011/06/28 PHP
PHP中文处理 中文字符串截取(mb_substr)和获取中文字符串字数
2011/11/10 PHP
php使用mb_check_encoding检查字符串在指定的编码里是否有效
2013/11/07 PHP
兼容各大浏览器带关闭按钮的漂浮多组图片广告代码
2014/06/05 PHP
利用Homestead快速运行一个Laravel项目的方法详解
2017/11/14 PHP
什么是PHP7中的孤儿进程与僵尸进程
2019/04/14 PHP
php输出形式实例整理
2020/05/05 PHP
JS中Iframe之间传值及子页面与父页面应用
2013/03/11 Javascript
jQuery使用empty()方法删除元素及其所有子元素的方法
2015/03/26 Javascript
简单理解vue中track-by属性
2016/10/26 Javascript
详解jQuery停止动画——stop()方法的使用
2016/12/14 Javascript
微信小程序使用checkbox显示多项选择框功能【附源码下载】
2017/12/11 Javascript
在Vue中获取组件声明时的name属性方法
2018/09/12 Javascript
微信小程序实现星星评价效果
2018/11/02 Javascript
ES6的Fetch异步请求的实现方法
2018/12/07 Javascript
如何解决webpack-dev-server代理常切换问题
2019/01/09 Javascript
JavaScript数组去重的几种方法
2019/04/07 Javascript
vue项目中mock.js的使用及基本用法
2019/05/22 Javascript
JavaScript如何获取一个元素的样式信息
2019/07/29 Javascript
5分钟教你用nodeJS手写一个mock数据服务器的方法
2019/09/10 NodeJs
layui表单提交到后台自动封装到实体类的方法
2019/09/12 Javascript
原生js实现碰撞检测
2020/03/12 Javascript
vue项目接口管理,所有接口都在apis文件夹中统一管理操作
2020/08/13 Javascript
小程序实现上传视频功能
2020/08/18 Javascript
浅析vue中的nextTick
2020/12/28 Vue.js
解决Linux系统中python matplotlib画图的中文显示问题
2017/06/15 Python
Python数据可视化正态分布简单分析及实现代码
2017/12/04 Python
Python实现直播推流效果
2019/11/26 Python
python中使用you-get库批量在线下载bilibili视频的教程
2020/03/10 Python
python中round函数如何使用
2020/06/19 Python
CSS3教程(1):什么是CSS3
2009/04/02 HTML / CSS
材料采购员岗位职责
2013/12/17 职场文书
新店开张活动方案
2014/08/24 职场文书
2014年就业工作总结
2014/11/26 职场文书
小学班主任工作随笔
2015/08/15 职场文书
实操Python爬取觅知网素材图片示例
2021/11/27 Python