我正在参加「创意开发 投稿大赛」详情请看:掘金创意开发大赛来了!
前言
喜欢看辰东的小说,有段时间想看《圣墟》,但是找了一圈都没有 txt 资源,于是就萌生了一个自己想办法搞一个 txt 出来的想法。
仅作为学习使用,支持正版。
加一句,喜欢叶凡的霸气无匹,心疼石昊的独断万古。
需求拆解
需求很简单,就是找到一个可以在线看小说的平台,最好是免登陆就也可以看的,然后把看到的小说想办法保存到本地,并整理成一个 txt。
具体拆解:
- 本地启动一个 node 服务器,向目标地址发起请求
- 分析目标页面,解析 html 并保存文本
- 按照目录顺序,逐个下载小说文本
- 扩展: a- 提高效率,多本下载 . . . b- 抓取图片等其他素材
代码实现
代码分为两部分:
第一部分是启动一个服务,并基于服务请求目标 html
注意这个目标地址可能会变,需要根据实际情况进行修改。
第二部分是 解析 html
依赖说明
http/https: node模块用来发起请求
fs: node 文件模块
colors-console: 一个可以在控制台打印五颜六色信息的小工具,需要安装
path: node path模块
jsdom:可以将html文本解析成jQuery对象
具体代码如下:
// loadTxt.js
"use script"
const http = require('http')
const https = require('https')
const fs = require('fs')
const colors = require('colors-console')
const path = require('path')
const { JSDOM } = require('jsdom')
// 启动一个本地Node服务
const server = http.createServer((req, res) => { }).listen(50082)
console.log('http start at port 50082!')
const BaseHostname = 'https://www.xxxx.com/'
// 抓取目标地址
const options = {
hostname: BaseHostname,
path: '/book/23488/'
}
// 保存地址
const txtName = '圣墟-' + new Date().getTime() + '.txt'
const file = path.resolve(__dirname, '../txt/' + txtName)
// 变量记录递归的次数
let idx = 0
/**
* 爬取网页内容,并返回经过 jquery 处理后端结果,可以直接执行 jquery 逻辑
* @param {hostname, path as object} param0
* @returns jQuery Object by http result
*/
const html2jq = ({ hostname, path }) => {
return new Promise((resolve, reject) => {
let html = ''
https.get(hostname + path, res => {
res
.on('data', chunk => {
html += chunk.toString()
})
.on('end', () => {
const document = (new JSDOM(html, { pretendToBeVisual: true })).window
const $ = require('jquery')(document)
return resolve($)
})
.on('error', (err) => {
console.log(colors('red', err));
return reject(err)
})
})
})
}
/**
* 递归下载章节,保证下载内容的准确性
* @param {index} i
* @param {data} arr
*/
const saveByList = (i, arr) => {
// console.log(`第${i}章下载开始!`)
let options = {
hostname: BaseHostname,
path: arr[i].path
}
html2jq(options).then($ => {
let title = arr[i].title
let content = $("#content").text().split(' ').join('\r\n ').replace(/ /g, '')
fs.appendFile(file, title + '\r\n\r\n' + content + '\r\n\r\n\r\n', err => {
if (err) console.log(err);
server.close()
})
console.log(`第${i}章 ${title} - 下载完成!`)
idx++
if (idx <= arr.length) {
saveByList(idx, arr)
}
}).catch(err => {
console.log('[IN SAVEBYLIST] ', err);
server.close()
})
}
/**
* 执行逻辑
* 1- 获取目录
* 2- 根据目录递归获取章节内容
*/
html2jq(options).then($ => {
let data = []
$('#list dl dd').each(function () {
data.push({
path: $(this).find('a').attr('href'),
title: $(this).find('a').text()
})
})
saveByList(idx, data)
}).catch(err => {
console.log(colors('red', `[ERROR==] ${err}`))
})
执行结果如下:
我把 options 里面的两个值改掉了,直接执行的话域名是错的,需要自己找到心仪的地址去试一下。
后记
其实后来因为工作的原因也没看到大结局,听朋友说结局有点草率,唉!
这个工具只针对前后端不分离的项目有用,因为只有 服务端 渲染才会给一个完完整整的 html,现在前后端分离的项目这种思路就有点不好使了。但是也有其他的解决方案。
至于工具的用处除了炫技之外,貌似也没有啥实际用途了。