Node.js 种子下载器

庆祝 2018 国庆,制作了一个 Node.js 的种子下载器。爬取页面,根据页面的链接,破解另外一个网站,下载种子文件。项目比较简单,爬取页面没有使用任何爬虫框架。项目源码

Node.js 的安装请看我的另外一篇文章,Node.js 的多版本安装

项目初始化

新建一个文件夹 FBIWarning,在该文件夹下打开命令行 CMD 或者 git bash。运行 npm init -y,该文件夹会生成一个 package.json 文件。

安装依赖包

安装依赖包 cnpm install --save cheerio iconv-lite request socks5-http-client。每个依赖包的功能如下:

  • cheerio // 解析 DOM
  • iconv-lite // 解决中文乱码的问题
  • request // http 请求,图片和种子的下载
  • socks5-http-client // socks 代理

爬取网页策略

网页之间,是靠链接联系在一起的,符合数据结构里面的图状结构。所以,对应有如下两种爬取策略。

  1. 爬取所有列表页面的链接后,再去爬取所有详情页面,对应图算法的广度优先遍历。
  2. 爬取一部分列表页面,就去爬取详情页面。然后再去爬取列表页面,爬取详情页面,循环进行,对应图算法的深度优先遍历。

因为是国外网站,网络可能随时断开,所以采用第二种策略比较好。同时,也能很快得到种子文件。为了防止重复爬取页面,可以将爬取页面的链接作为索引。

请求代理

网站是国外网站,需要使用梯子,否则不能爬取。代理传送门socks5-http-client 配合 reqeust 使用,可以解决代理的问题。但是,该代理只支持 socks 代理, http(s) 代理暂不支持。

解决中文乱码的问题

目标网站的页面编码是 gbk ,而 request 依赖包的默认编码是 UTF-8,使用默认编码解码方式,会导致页面的中文变成乱码。所以得到返回数据前,去掉默认编码,就是设置编码为 encoding: null,然后使用 iconv-lite 使用 gbk 方式解码,这样就可以解决中文编码为乱码的问题,代码如下:

const request = require("request")
// 解析 dom
const cheerio = require("cheerio")
// 中文编码
const iconv = require("iconv-lite")
// 代理
const Agent = require("socks5-http-client/lib/Agent")
const COMMON_CONFIG = require("./config")
/**
* 请求页面
* @param {String} requestUrl 请求页面
*/
function requestPage(requestUrl) {
try {
return new Promise((resolve, reject) => {
if (!requestUrl) {
resolve(false)
}
request.get(
{
url: requestUrl,
agentClass: Agent,
agentOptions: {
socksPort: 13838, // 代理端口
socksHost: "127.0.0.1" // 代理 Host
},
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36"
},
// 去掉默认 utf-8 解码,否则解码会乱码
encoding: null
},
function(err, response, body) {
// 防止解析报错
try {
// 统一解决中文乱码的问题
let content = iconv.decode(body, "gbk")
let $ = cheerio.load(content)
resolve($, err, response, body, content)
} catch (error) {
resolve(null)
}
}
)
})
} catch (error) {
//如果连续发出多个请求,即使某个请求失败,也不影响后面的其他请求
Promise.resolve(null)
}
}

并发请求

分页请求有很多个,可以使用递归来一个一个请求,但是写法不太好看。所以,可以使用 ES7+ 里面的 async 函数,将同步过程变为异步过程。async 要配合 await 使用,就可以将同步过程变为异步过程。详细了解 async 请看阮一峰 ES async

async function innerRecursion() {
for (let i = 1; i <= 100; i++) {
let requestUrl = "http://www.baidu.com?page=" + i // 事例网站,非爬取网站
let result = await this.requestPage(url)
}
}

一个一个请求比较慢,可以使用 Promise.all 实现并发请求。当然,也可以使用 async 模块 提高下载的并发量,有需要的可以自己去了解。这个 async 模块并非上面的 async 函数。

function innerRecursion() {
let requestUrls = []
for (let i = 1; i <= 100; i++) {
let requestUrl = "http://www.baidu.com?page=" + i // 事例网站,非爬取网站
requestUrls.push(requestUrl)
}
let promises = requestUrls.map(url => this.requestPage(url))
Promise.all(promises)
.then(results => {
// results 是一个数组,对应上面每个请求的结果
})
.catch(error => {
// 捕获请求中可能发生的错误
console.log(error)
})
}

图片下载

图片的下载非常简单,代码如下:

/**
* 下载文件
* @param {String} url 请求链接
* @param {String} filePath 文件路径
*/
function downloadFile(url, filePath) {
// try...catch 防止一个请求出错,导致程序终止 种子的下载相同
try {
if (!url || !filePath) {
return false
}
request
.get({
url,
agentClass: Agent,
agentOptions: {
socksPort: 13838, // 代理端口
socksHost: "127.0.0.1" // 代理 Host
},
headers: {
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36"
}
}
})
.pipe(fs.createWriteStream(filePath))
} catch (error) {
console.log(error)
}
}

破解网站种子下载

解析详情页面,只能得到类似 http://www.jandown.com?ref=VENU794 的链接,需要破解该网站的种子下载。查看网站的种子下载方式,就是一个 post 请求,后端就会返回种子文件。刚开始的时候,不熟悉服务端的表单提交方式,导致文件一直得不到,后来详细查看了 request 的官文文档,发现是自己写错了。结合上面的图片下载,种子的下载方式自然就有了,代码如下:

/**
* 下载种子链接
* @param {String} childDir // 子目录
* @param {String} downloadUrl // 下载种子地址
*/
function downloadTorrent(childDir, downloadUrl) {
try {
// 解析出链接的 code 值
let code = querystring.parse(downloadUrl.split("?").pop()).ref
if (!code || !childDir) {
return false
}
// 发出 post 请求,然后接受文件即可
request
.post({
url: "http://www.jandown.com/fetch.php",
agentClass: Agent,
agentOptions: {
socksPort: 13838, // 代理端口
socksHost: "127.0.0.1" // 代理 Host
},
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36"
},
formData: {
code
}
})
.pipe(fs.createWriteStream(childDir + "/" + code + ".torrent"))
} catch (error) {
console.log(error)
}
}

面向对象

刚开始是使用面向过程的方式写的,后来发现代码太重复了,所以采用 OOP 改写了整个代码。详细了解 javaScript Class 请看阮一峰 ES class

总结

  1. 学习中文编码为乱码的解决方法
  2. 学习了 request 的代理以及文件下载功能
  3. 破解种子网站的种子下载功能
  4. js 面向对象开发
  5. 爬虫并发量解决

感谢阅读!

Node.js 种子下载器的更多相关文章

  1. Node.js包管理器Yarn的入门介绍与安装

    FAST, RELIABLE, AND SECURE DEPENDENCY MANAGEMENT. 就在昨天, Facebook 发布了新的 node.js 包管理器 Yarn 用以替代 npm .咱 ...

  2. Node.js包管理器:

    Node.js包管理器: 当我们要把某个包作为工程运行的一部分时,通过本地模式获取,如果要在命令行下使用,则使用全局模式安装 使用全局模式安装的包并不能直接在JavaScript文件中用require ...

  3. node.js之调试器

    node.js之调试器 1.在命令行窗口中,可以使用"node debug" 命令来启用调试器,代码如下: node debug<需要被执行的脚本文件名> 接下来根据一 ...

  4. Node.js的下载、安装、配置、Hello World、文档阅读

    Node.js的下载.安装.配置.Hello World.文档阅读

  5. Folx种子下载器怎么管理下载任务

    对于喜欢追剧的用户来说,同时下载好几部剧是司空见惯的事情.但有时候,有些剧比较好看或者热度比较高时,就会希望优先将其下载下来. 对于使用Folx种子下载器的用户来说,可以结合使用下载列表+最大活动数的 ...

  6. npm --- Node.js包管理器

    目录 1. 安装Node.js 2. 运行npm 3. npm介绍 3.1 安装插件 3.2 更新插件 3.3 卸载插件 3.4 查看当前目录中的插件列表 4. 使用cnpm 4.1 安装 npm( ...

  7. Node.js 包管理器 NPM 讲解

    包管理器又称软件包管理系统,它是在电脑中自动安装.配制.卸载和升级软件包的工具组合,在各种系统软件和应用软件的安装管理中均有广泛应用.对于我们业务开发也很受益,相同的东西不必重复去造轮子. 每个工具或 ...

  8. 9.Node.js 包管理器npm

    npm 是 Node.js  官方提供的包管理工具, 用于 Node.js包的发布.传播.依赖控制 安装 express ==> 流行的基于Node.js的Web开发框架,可以快速地搭建一个完整 ...

  9. node.js服务器端下载、上传文件

    使用request 下载文件: 安装依赖: npm i requestsourceUrl下载源,targetUrl保存路径 async function downLoadFile(sourceUrl, ...

随机推荐

  1. !!!css如何让img图片居中?css的display属性实现图片居中(代码实例)

    在我们开发前端页面的时候,为了让页面效果美观,会让图片呈现居中效果.那么css怎么让img图片居中显示呢?本篇文章给大家带来css如何让img图片居中?css的display属性实现图片居中(代码实例 ...

  2. android---EditText的多行输入框

    <EditText android:id="@+id/edt_order_note_text" android:layout_width="match_parent ...

  3. ELK配置

    安装logstash docker pull logstash docker run -it --rm logstash -e 'input { stdin { } } output { stdout ...

  4. 给JS包写TypeScript用的类型申明文件

    TS (TypeScript)区别于JS (JavaScript)一个最大的不同是TS增加了类型.当一些TS代码要使用JS包的时候,最好这些JS包都有类型介绍,比如这个变量是什么类型,那个函数参数的什 ...

  5. 怎么给easyui中的datagrid加水平滚动条

    注意如下几个点就行: 1.数据网格(DataGrid)所在的table属性上级div无需设置width: 2..datagrid属性:fitColumns为false 或者不填 3.在style中给. ...

  6. Influxdb+Grafana+Telegraf及docker中运行

    目录 参考资料 1. InfluxDB 1. 特征: 2. 特点: 3. 功能及默认 4. 主要概念 1) 与SQL的名词做比较 2) InfluxDB的独有概念 5. 常用命令 1. 用户管理: 6 ...

  7. Android中的数据持久化机制

    Android中几种最简单但是却最通用的数据持久化技术:SharedPreference.实例状态Bundle和本地文件. Android的非确定性Activity和应用程序生存期使在会话间保留UI状 ...

  8. CentOS7安装及简单配置(一)

    CentOS7是RHEL的社区版,摘抄维基百科的一段话如下: CentOS(Community Enterprise Operating System)是Linux发行版之一,它是来自于Red Hat ...

  9. Javascript 标识符及同名标识符的优先级

    一.定义 标识符(Identifier)就是一个名字,用来对变量.函数.属性.参数进行命名,或者用做某些循环语句中的跳转位置的标记. //变量 var Identifier = 123; //属性 ( ...

  10. ZOJ4043 : Virtual Singers

    将所有$A$和$B$混在一起排序,那么每个$B$要匹配一个$A$,从左往右依次考虑每个数: 如果是一个$B$: 如果左边没有多余的$A$,那么将其放入堆$q_C$中,表示这个$B$还未匹配. 否则选择 ...