利用NodeJs进行简单数据采集(微爬虫)

一直听说爬虫爬虫的,认为很无聊,没去研究过。偶尔的一次机会接触了爬虫,还不错。本文学习如何用nodeJs来爬虫获取小说的内容(我觉得叫抓取页面数据更适合,毕竟不是正儿八经的爬虫).
目标: 采集《斗破苍穹》小说的所有章节目录及URL

一、环境搭建

编码环境 具体安装就不说了,网上一堆安装介绍

  • node 必不可少的环境
  • npm or yarn 我更喜欢yarn,故本文将采用yarn来安装模块,不了解的童鞋可以去官网了解学习下,和npm差不多
  • 终端工具,本文展示的是iTerm2

二、搭建项目&安装必要模块

打开终端 or iTerm or cmd, 创建项目文件夹crawler,进入文件夹使用yarn初始化一个项目

$ md crawler
$ cd crawler
$ yarn init -y

新建一个index.js文件用来项目入口编码

$ touch index.js

至此项目搭建完毕,此时项目目录为:

.
├── index.js
└── package.json

三、安装所需模块

安装所需模块列表

$ yarn add superagent superagent-charset cheerio

安装完后,打开package.json看下:

{
    "name": "crawler",
    "version": "1.0.0",
    "main": "index.js",
    "license": "MIT",
    "dependencies": {
        "cheerio": "^1.0.0-rc.3",
        "superagent": "^5.0.5",
        "superagent-charset": "^1.2.0"
    }
}

四、开始编码

使用你喜欢的编辑器打开项目,因为喜欢,所以寡人使用 VS Code,由Atom转到VS Code的黑粉。不扯了,打开index.js编入如下代码:(解释直接写到注释中)

// 导入需要用到的模块包
// 导入需要用到的模块包
const cheerio = require('cheerio');
const superagent = require('superagent');
require('superagent-charset')(superagent);

// 这里采用异步的方式抓取
async function getList() {
    // arr数组用来存放抓取到的数据
    let arr = []
    // 要抓取的页面url链接
    let url = 'https://www.biquge5.com/0_84/'
    // 使用superagent获取该页面的数据
    // 具体的配置参数可以去官网查看 这里是方便使用async/awit,采用的同步调用方式
    let result = await superagent.get(url).set({
        "Connection": "keep-alive"  // 不是必须,纯粹展示superagent可以配置
    }).charset('gbk').buffer(true)  // charset为目标页面的编码,可以审查元素中head标签中查看
    // 使用cheerio将result转换为类似于jQuery的对象,如果会jq的话这里就很简单了
    // 具体的配置参数可以去官网查看
    let $ = cheerio.load(result.text, {
        decodeEntities: false
    })
    // 分析dom结构,获取需要的数据,可以在目标网页上审查看元素的dom结构
    // 看到没,和jq的语法一样
    $('._chapter li a').each((index, element) => {
        // 把想要的数据放入到arr数组中
        arr.push({
            id: index,
            title: $(element).text(),
            url: $(element).attr('href')
        })
    })
    return arr
}
// 调用getList()获取所有小说目录及链接
async function main() {
    let result = await getList()  // 注意await必须用在async修饰的函数中
    console.log(result)
}
// 调用主函数
main()

四、运行

打开终端iTerm2, 运行下列命令

$ node index.js

不出意外的话,终端中会打印出结果

$ node index.js

[ { id: 0,
    title: '第一章 陨落的天才',
    url: 'https://www.biquge5.com/0_84/65397.html' },
  { id: 1,
    title: '第二章 斗气大陆',
    url: 'https://www.biquge5.com/0_84/65398.html' },
  { id: 2,
    title: '第三章 客人',
    url: 'https://www.biquge5.com/0_84/65399.html' },
    ...
  { id: 98,
    title: '第一百章 威胁',
    url: 'https://www.biquge5.com/0_84/65495.html' },
  { id: 99,
    title: '第一百零一章 潜力值的分级',
    url: 'https://www.biquge5.com/0_84/65496.html' },
  ... 1560 more items ]

至此,采集列表完毕,数据采集完毕后你可以保存到数据库,也可以保存到本地文件系统中,node都有对应的模块包 mysql2 & fs

五、结语

其实,这样看数据采集不难,就是分析下目标页面的数据结构。难的是采集数据一般是大批量的操作,要注意并发控制反爬虫机制等。

六、最后的最后

抛出采集 mzitu 站点的图片,下载到项目dist文件夹(需要手动创建)

const cheerio = require('cheerio')
const superagent = require('superagent')
const request = require('request')  // 注意要  yarn add request
const fs = require('fs')  // 要 yarn add fs
require('superagent-charset')(superagent)

// 辅助类
class Util {
    static downImg(opts = {}, path = '') {
        return new Promise((resolve, reject) => {
            request.get(opts).on('reponse', reponse => {
                console.log('img type: ', response.headers['content-type']);
            }).pipe(fs.createWriteStream(path)).on('error', e => {
                resolve('')
            }).on('finish', () => {
                resolve('download completed')
            }).on('close', () => {

            })
        })
    }
}

async function getContent(url) {
    let res = await superagent.get(url).set({
        "Connection": 'keep-alive'
    }).charset('utf-8').buffer(true)

    let $ = cheerio.load(res.text, {
        decodeEntities: false
    })
    return $;
}
async function main() {
    let arr = [];
    // 抓取的首页的图片
    const url = 'https://www.mzitu.com/'

    let $ = await getContent(url)

    $('#pins li').each((index, element) => {
        arr.push($(element).find('img').attr('data-original'))
    })
    console.log(arr);

    for (let u of arr) {
        let opts = {
            url: u,
            headers: {
                'Referer': 'https://www.mzitu.com/'
            }
        }
        let path = './dist/' + u.substring(u.lastIndexOf('/'))
        let r1 = await Util.downImg(opts, path)
        console.log(r1);
    }

}
main()

项目目录结构

.
├── dist
├── index.js
├── mz.js
├── node_modules
├── package.json
└── yarn.lock

2 directories, 4 files

可以看到有很多重复的代码,其实我们可以提取到一个模块中,这里因为懒癌犯了就不提取了,有兴趣的就自己提取吧。

登录后进行讨论