问题:
n站上的本子,虽然每一本都有下载按钮,但是实测通过其提供的种子文件,下载速度并不理想,甚至有时候会卡在一个地方下不动。可是本子毕竟不同于其他东西,它在实质上只是许多图片的合集。用户打开每一个带有大图的网页时,大图都能顺利加载,那么我们能不能自己写一个脚本,来模拟用户对于每一个网页的点击呢?
一开始我的想法非常简单,就是通过在 Chrome 的开发者工具中运行一段简单的 JavaScript,搜寻每张略缩图的链接,然后再通过某种方式把图片下载下来。当我在一开始就遇到了一个问题:怎么用 JavaScript 来把网页上的资源自动下载到电脑中?一开始我用的是这样的方法:
var link = document.createElement('a');
link.href = 'images.jpg';
link.download = 'Download.jpg';
document.body.appendChild(link);
link.click();
复制代码
但马上我就发现这样会使浏览器弹出一个下载窗口,这可不是我想要的。经过一番搜索之后,我认为只有通过一个正经的爬虫脚本才能把网页上的图片批量下载到本地。说到爬虫,很多人可能立刻就想到了 Python,但是由于我并不是要做什么大项目,更不用爬取多大的数据量,加上对于 Node.js 解析网页的方式相对来说更为熟悉(Cheerio 用起来和 jQuery 很像),于是就选择了使用 Node.js。随后我发现了这个东西,在阅读并使用之后决定以它作为蓝本,来实现我们所需要的脚本。
爬虫这个东西就像变色龙,总是需要适应网站的变化而变化。链接中所提供的 Node.js 脚本虽然好用,但是它毕竟只针对那一个网站而已,换一个就不奏效了。针对n站,我们需要对症下药。通过观察我发现每个本子的首页网址中都带有一个ID,它在略缩图的href
中也有出现,然而它并不是通往每张大图的ID,通往大图的本子ID藏在<img>
的src
中。
<div class="thumb-container">
<a class="gallerythumb" href="/g/网址中的ID/1/" rel="nofollow">
<img is="lazyload-image" class="" width="200" height="288" data-src="https://t.nhentai.net/galleries/通往大图的ID/1t.jpg" src="https://t.nhentai.net/galleries/通往大图的ID/1t.jpg">
<noscript><img src="https://t.nhentai.net/galleries/通往大图的ID/1t.jpg" width="200" height="288" /></noscript>
</a>
</div>
复制代码
我并不确定为何n站要为同一个本子赋与两个不同的ID,但无疑我们需要能够拿到大图的那个ID。这个时候我犯了一个错误,由于每张大图的地址都非常有规律(例如:https://i.nhentai.net/galleries/通往大图的ID/1.jpg
),我一开始选择在获取总页面数和大图ID后自动生成所有大图的网址。这在我的第一次测试中没有出现问题,但在第二次测试时无法下载除前两张外的其他图片。排查后发现我默认所有的图片都是 jpg 格式,然而事实并非如此。于是我更改了代码结构,获取每一个大图的网页,进而再获取每一张大图。这无疑降低了效率,却是不得已而为之。之后又加入了从命令行直接传参的功能。
脚本改好了,然而我并不敢将成品放到 GitHub ?,那么就直接放在这篇文章下面吧!(反正一共也没有多少行)
// 'use strict'
const request = require('superagent')
const cheerio = require('cheerio')
const fs = require('fs-extra')
const path = require('path')
let dir = ''
/**
* 生成[n, m]随机数
* @param {number} min
* @param {number} max
*/
function random(min, max) {
let range = max - min
let rand = Math.random()
let num = min + Math.round(rand * range)
return num
}
/**
* 获取图集的URL
*/
async function getImageUrl(url) {
let linkArr = []
const res = await request.get(url)
let $ = cheerio.load(res.text)
dir = $('#info h2').text()
dir = dir.split('/').join(' ')
dir = path.join(__dirname, '/mm', dir)
const exists = await fs.pathExists(dir)
if (exists) {
await fs.remove(dir)
}
await fs.mkdir(dir)
const pageID = url.split('/')[4]
const pageNum = parseInt($('#tags + div').text().split(' ')[0])
for (let i = 1; i <= pageNum; i++) {
const imagePage = await request.get('https://nhentai.net/g/'+pageID+'/'+i)
$ = cheerio.load(imagePage.text)
linkArr.push($('#image-container').find('img').attr('src'))
}
return linkArr
}
// 下载图片
function download(dir, imgUrl) {
console.log(`正在下载 ${imgUrl}`)
const filename = imgUrl.split('/').pop()
const req = request.get(imgUrl).set({ 'Referer': 'https://nhentai.net' })
req.pipe(fs.createWriteStream(path.join(dir, filename)))
}
// sleep函数
function sleep(time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve()
}, time)
})
};
async function init(url) {
let urls = await getImageUrl(url)
for (let url of urls) {
download(dir, url)
await sleep(random(1000, 5000))
}
}
process.argv.slice(2).forEach(function (val) {
init(val);
});
复制代码
用法示例:
node n-site.js https://nhentai.net/g/xxxxx/(即本子首页网址)
复制代码
node n-site.js https://nhentai.net/g/xxxxx/ https://nhentai.net/g/xxxxx/(可以传多个网址作为参数)
复制代码
以上。
此致 敬礼