Node 实现精准高效爬虫

197 阅读21分钟

§ 1. 爬虫的策略

§ 1.1 初识爬虫

§ 1.2 爬虫分类

§ 1.3 爬虫策略

§ 2. 爬虫的流程

§ 2.1 应用开发的流程

§ 2.2 代码执行的流程

§ 3. Superagent 请求页面

§ 3.1 明确需求

§ 3.2 创建项目

§ 3.3 使用 Superagent 发送请求

§ 4. Cheerio 加载并分析文档

§ 4.1 什么是 Cheerio

§ 4.2 Cheerio 特点是什么

§ 4.3 分析查询结果页文档

§ 5. Nightmare 模拟浏览器请求

§ 5.1 什么是 Nightmare

§ 5.2 常用功能介绍

§ 5.3 自动搜索任意主题素材

§ 6. 嵌套数据请求处理

§ 6.1 功能需求描述

§ 6.2 promise 对象

§ 6.3 完成代码

§ 7. 写入 MySQL 数据库

§ 7.1 数据库和表的构建

§ 7.2 Node 操作表

§ 7.3 将抓取记录写入数据库

  1. 爬虫的策略

1.1 初识爬虫

​爬虫,因为它用于网络中,故又名网络爬虫、网络机器人,在执行时,动作像蜘蛛,又被称为网络蜘蛛;虽然称呼很多,但它的本质是一段程序或脚本代码,通过程序或代码,按照一定的既定规则,自动去抓取网络中的数据信息,因此,也是搜索引擎的组成部分。

​爬虫在抓取数据时,首先从一个或多个 URL 地址入手,获取页面的初始数据,然后再根据这些既定的数据组合成新的条件,不断再生成新的 URL 地址,加入到现有的队列中,直到抓取的数据完全满足需求为止,而这一切的操作,在编写好代码之后,都是自动进行的。 image.png

1.2 爬虫分类

根据爬虫获取数据的范围和深度不同,可以分为下列 4 种类型。

1. 全网爬虫

​全网是指整个万维网,先从几个指定的 URL 入手,后扩展到全部的万维网,通常是获取全网的分类数据,这种方式用于为大型的搜索公司提供数据支持,也可以为巨头公司提交数据采集。

2. 主题爬虫

​与全网爬虫不同,主题爬虫有明确的抓取目标和既定的抓取规则,在有限的范围内进行数据的采集和过滤,这种方式常用于某一主题的数据收集,如股票、新闻、疫情数据等。

3. 增量爬虫

​与前两种都不相同,所谓的增量爬虫是指对已获取的数据保存至数据库,并通过某些约束机制,如 id 值不能相同方式,抓取更新后和最新的数据,用于确保每一次获取的都是增量数据,

4. 深度爬虫

​与前面三种类型都不同,深度爬虫需要提供用户的交互信息,如用户登录或输入关键字以后,依据相关的已知信息,进行深度页面数据的抓取和采集,如用户信息、历史数据等。

1.3 爬虫策略

​不同类型的爬虫,基于不同的爬虫策略,全网爬虫,通常基于广度和深度;主题爬虫,基于内容的相关性;增加爬虫,基于更新的方法,深度爬虫,则更多基于表单中数据的提交策略,以主题爬虫为例,它的策略更多是基于内容在 DOM 中的节点分析,如下图 1-1 所示: image.png

​在上述示意图 1-1 中,无论是 URL 地址还是节点,都是基于主题内容的,当通过 URL 获取到页面后,根据内容定位到控制节点,通过控制节点,获取真正的内容数据,即爬虫节点;在完成首次 URL 数据抓取后,带将根据抓取到的节点内容,形成第二次 URL 地址,再将定位控制节点,获取二次 URL 地址中的爬虫数据,以此类推。

​当然,在真正通过爬虫的方式获取业务数据时,使用的策略通常是综合性的,即既会考虑内容确定的控制点,也要考虑到抓取站点的负载,同时,为了提升抓取的效率,会启用多台电脑分担一台机器的负载,并实现并行抓取的效果,最后,为了更新抓取的需要,还会考虑再次抓取周期的策略,它是一套组合拳。

  1. 爬虫的流程

2.1 应用开发的流程

​如果要制作一个具有爬虫功能的应用,通常在开发前期会有如下图 1-2 所示的流程:

image.png

​在上述示意图 1-2 中,爬虫应用的开发与其他应用开发相同,前期需要明确需求,而这个需求就是要抓取什么内容,因为大部分的爬虫应用都属于主题型开发,确定抓取内容后,再确定使用什么开发工具进行抓取,因为现在有很多工具都可以实现爬虫的功能,一个好的开发工具,可以使用应用开发事半功倍。

​在明确了需求和确定了工具之后,接下来的流程就是编码了,无论使用何种的开发工具,在编码阶段都要通过 URL 获取到 DOM,再分析 DOM 结构,抓取数据,并同时写入数据库,完成一种抓取的流程。

​当代码经历了调试和测试之后,就可以正式部署到服务器上,项目开发宣告完成,后期将会做代码的性能调优和新版本的不断迭代,优化和丰富项目的功能。

2.2 代码执行的流程

​爬虫的流程除了应用开发流程之外,对于程序员来说,更为重要的是,如何编写爬虫的代码,它的执行流程又是如何进行的,下面以 Node 为例,一个基于 Node 框架的爬虫代码执行流程如下图 1-3 所示:

image.png

​在上述示意图 1-3 中:

· 第 1 步将初始的 URL 作为种子 URL,进入到待抓取 URL 队列中,并分别开始按队列的 URL 发送请求,获取数据,分析 DOM 节点;

· 第 2 步中将获取的主题节点数据写数据库中,完成一种抓取的操作过程;

· 与此同时,第 3 步将写入已获取到的数据的 URL 作为已抓 URL,放入队列中,以备使用;

· 当需要再次抓取时,执行第 4 步,从队列中抽出新的 URL 到待抓取 URL 中,再次进行抓取操作。

​需要说明的是,上述示意图 1-3 是指 URL 较多时使用,如果就是一或二个 URL 地址,可以省略第 3 步和第 4 步,即第 1 步执行时,直接分析页面结构,获取有用的主题数据,进入第 2 步直接写入数据库。

  1. Superagent 请求页面

3.1 明确需求

​有许多专业从事三维动画制作的人员,希望能快速寻找到已经完成的三维动画效果视频作为参考一个素材,因此,想通过爬虫的形式获取到网络中三维动画的视频文件资源,完整的需求如下:

· 以列表的形式展示抓取的视频文件资源

· 点击列表项进入详细的内容页

· 抓取的数据先保存在数据库中

· 页面发送请求获取数据库中的记录

3.2 创建项目

​根据需求,使用 HTML5 + Node + MySQL 的方式来完成,其中,HTML5 负责静态页的制作和请求数据的展示,Node 负责爬虫功能的实现和数据请求 API 的处理,MySQL 负责数据的写入和保存,结构如下图 1-4 所示:

image.png

​明确项目的整体结构之后,按照如下的步骤创建项目。

1. 添加一个名称为 Code 的空文件夹,并使用 npm init 初始化该文件夹

文件夹初始化成功之后,自动添加了一个名称为 package.json 的文件,包含的代码如下所示:

{

     "name": "code",

     "version": "1.0.0",

     "description": "",

     "main": "index.js",

     "scripts": {

       "test": "echo \"Error: no test specified\" && exit 1"

     },

     "author": "tgrong",

     "license": "ISC"

   }

2. 分别添加两个静态页面和一个 JavaScript 文件,文件夹的整体结构如下图 1-5 所示:

image.png

在项目文件夹中,添加了两个静态的页面文件,其中名为 index.htm 的文件用于以列表的方式显示获取到的爬虫记录,另一个名为 disp.html 的文件用于显示点击列表的记录后进入的详细内容;此外,名为 api.js 文件用于实现页面爬虫功能和数据请求时的 API 支持。

3. 在项目文件夹中分别安装项目依赖功能模块

当项目文件夹中的功能文件创建完成后,接下来分别使用 yarn 安装开发时需要的依赖模块。在文件夹中,打开终端,分别执行下面的指令,安装相应的依赖模块:

   $ yarn **add** cheerio

   $ yarn **add** express

   $ yarn **add** mysql

   $ yarn **add** superagent

   $ yarn **add** superagent-charset

   

各模块的功能将在后续内容中详细介绍,当依赖模块全部安装成功后,项目中的 package.json 文件的内容也会发生变化,新增的依赖项的内容如下所示:

   ...

    "dependencies": {

       "cheerio": "^1.0.0-rc.3",

       "express": "^4.17.1",

       "mysql": "^2.18.1",

       "superagent": "^5.2.2",

       "superagent-charset": "^1.2.0"

     }

在上述的依赖模块中,除了安装“Superagent”外,还安装了“superagent-charset”,该模块是解决抓取的网站内容不是 UTF-8 编码时,将返回乱码的问题。

3.3 使用 Superagent 发送请求

​接下来,通过 URL 地址访问的方式,调用 Superagent 模块,向指定的网址发送请求,并设置请求的编码格式,为了实现上述功能,首先,调用 express 模块,设置一个 URL 访问的地址,代码如下:

var express = require('express');

var app = express();

app.get("/get", (req, res) => {

    //设置页面内容编码的格式

    res.header("Content-Type", "text/html; charset=utf-8");

    //设置允许跨域的域名,*代表允许任意域名跨域

    res.header("Access-Control-Allow-Origin", "*");

    res.write("全部数据生成完成!");

    res.end();

})

app.listen(3000);

console.log("http://localhost:3000/");

​Superagent 是一个轻量级的客户端请求代理模块,用于 Node 环境中,使用时,导入到文件中,为了解决在抓取页面过程中的乱码问题,还要导入 superagent-charset 模块,在该模块的基础之上发送请求,代码如下:

const superagent = require('superagent');

let charset = require('superagent-charset');

let request = charset(superagent);

​然后,设置请求的 URL 地址,定义一个发送请求的函数,调用 Superagent 模块,发送请求,代码如下:

let url_soh = "so.vjshi.com/";

let url_mp4 = "mp4.vjshi.com/";

function searchImgs(){

    let url = url_soh + 'Search?wd=%E4%B8%89%E7%BB%B4%E5%8A%A8%E7%94%BB&st=y&type=v';

    request.get(url)

        .charset('utf-8')

        .buffer(true)

        .end((err, res) => {

            if (err) {

                console.log(抓取失败 - ${err})

            } else {

                console.log(res);

            }

        });

    }

​上述代码中,为了后续的复用性,先定义了一个变量 url_soh,保存请求网址的主域名,这是一个素材资源管理的网站,在这个域名的基础之上,获取“三维动画效果”的素材,因此,它的查询地址是:

so.vjshi.com/Search?wd=%…

​使用请求模块对象 request 中的 get 方法,向上述地址发送请求,获取想要的查询数据结果,在请求的过程中,使用 charset 方法设置获取页面的编码格式,并使用 buffer 方法强制缓存响应的内容,最后调用 end 方法结束请求,并返回请求返回的内容,代码执行的格式说明如下:

request.get('请求地址').charset('编码格式').buffer('是否强制缓存').end((err, res) => {})

​最后,将自定义的函数 searchImgs 放入 "/get" 请求中,当请求 "http://localhost:3000/get" 地址时,执行函数中的代码,返回查询地址页面中抓取到的内容,并输出在控制台,效果如下图 1-6 所示:

image.png

  1. Cheerio 加载并分析文档

4.1 什么是 Cheerio

Cheerio 又称为解析器,专门用于服务端中 DOM 结构的解析,它的核心是一个精简版的 jQuery 框架,因此,它广泛地应用于各服务端中页面元素的查找和分析,同时,还提供了遍历元素、操作遍历结构的功能 API,常用于 Node 中,加载并分析抓取到的页面文档结构,与其他模块相同,它也必须先下载,后安装,再导入文件中使用。

4.2 Cheerio 特点是什么

Cheerio 有三个非常明显的特点,具体如下:

1. 熟悉的语法:因为它是精简版的 jQuery,因此,它的语法结构与 jQuery 相似。简单代码如下:

   var cheerio = require('cheerio'),

   $ = cheerio.load('

...
');

   $('div.title').text('Hello world!');

   $('div').addClass('welcome');

   $.html();

在上述代码的第 1 行,导入安装好的模块,第 2 行,使用 load 方法构建页面元素,并保存到名称为 $ 的对象变量中,第 3 行,在对象中设置元素显示的文本内容,第 4 行添加元素的类别样式,最后第 5 行,输出重置后的新内容,因此,经过第 3、4 行的操作后,页面中最终显示的元素是:

   <div class="title welcome">Hello world!

2. 操作的迅速:Cheerio 是简化版的 jQuery 框架,语法使用非常简单,相同的 DOM 模型,使得它在解析、操作时非常的高效和流畅,有相关的测试也表明,使用 Cheerio 操作 DOM,比使用 JavaScript 要快至少 8 倍。

3. 惊人的灵活:Cheerio 内置强大的文档解析 API,不仅可以完成各种 HTML 文档的解析,而且还能解析各类的 XML 格式的文档,强大的灵活性,也是它深受喜爱的一个原因。

4.3 分析查询结果页文档

在初步理解了 Cheerio 的功能和基本特点之后,接下来,将使用 Superagent 请求获取的页面数据,加载到 Cheerio 容器中,分析 DOM 结构,抓取想要的内容,操作步骤如下所示。

1. 页面效果与代码结构分析

查询结果是一个样式名为 'card-body' 的 div 元素包裹的列表页,在 div 元素中包裹 a 元素和 img 元素,a 元素的 href 属性中包含 id 值,img 元素的 alt 和 data-original 属性分别包含图片的名称和图片的 URL 地址,详细的页面效果和结构如下图 1-7 所示:

image.png

2. 抓取想要的主题内容

定义一个名称为 'getImgs' 的函数,根据页面代码结构的分析,调用 Cheerio 模块加载抓取的页面内容,再分析内容的元素结构,代码如下所示:

   function getImgs(res) {

       let $ = cheerio.load(res.text);

       let arrData = [];

       $('.card-body').each((idx, ele) => {

           // 主体元素

       });

   }

在上述代码的第 2 行中,先通过变量名为 $ 的对象保存 Cheerio 模块抓取的整个页面内容,获取后,在代码的第 3 行中,定义一个数组对象,用于保存各项抓取到的数据,此外,在代码的第 4 行,选取样式类别名为 'card-body' 的 div 元素,因为它是列表的最外围元素,所以当调用 each 方法遍历该元素集合时,则可以获取页面中每一个外围元素,为下一步的子类元素筛选打下基础。

3. 筛选写入的字段

为了获取到图片的 id、name、imgurl 和 videourl 字段值,需要在遍历的外围元素中,再次查找它的子类 a 元素 和 img 元素,通过取这两个元素的中的 href 和 alt 属性值来获取,代码如下所示:

       let  **href =href** = (ele).children("a").attr("href");

       let  pos =pos** = **href.indexOf('.', 28);

       let id = href.substring(28,href**.substring(28, **pos);

       let name = $(ele).children("a").children(".card-inner-top-wrap")

                  .children("img").attr('alt');

       let imgurl = $(ele).children("a").children(".card-inner-top-wrap")

                     .children("img").attr('data-original');

       let videurl = url_mp4 + imgurl.substr(22, 43) + ".mp4";

       arrData.push({ "id": id, "name": name, "imgurl": imgurl, "videurl": videurl });

       console.log(idx,id,name,imgurl,videurl);

​在上述代码中,$(ele) 表示遍历时的每一个外围的 div 元素,在这个元素中,通过第 1~3 行代码,找到它的子类 a 元素,并获取到该元素的 href 属性值,同时使用 substring 方法来截取需要的 id 字段的内容;以此类推,根据页面元素的结构,获取到子类 img 元素,并通过元素的属性,分别取到 name 和 imgurl 以及 videurl 字段的值,最终被保存到数组后,在控制台输出,效果如下图 1-8 所示:

image.png

需要说明的是:取什么字段的值,源于抓取的需求和数据库的字段结构,确定后,再返回页面中寻找这些数据所在元素的位置,并调用方法获取它。

  1. Nightmare 模拟浏览器请求

5.1 什么是 Nightmare

​Nightmare 是一个自动化测试的框架,它基于 Electron 框架,而 Electron 又是专注于使用 JavaScript 开发针对浏览器的桌面应用,这使 Nightmare 框架在处理基于浏览器的用户数据交互时,更加方便和高效,也正因为这点,在测试时的响应速度上,是基于 PlantomJS 框架的两倍,而且语法更前沿。

5.2 常用功能介绍

​虽然 Nightmare 是一个测试的框架,但它在测试时,模拟用户的操作也非常适合爬虫的需求,常用于异步数据的请求,模拟用户操作,结合 Node 开发环境,相同的语法,既简单又高效。

1. 模块安装

与其它在 Node 中使用的模块相同,Nightmare 在使用前也必须先安装,然后,再导入到需要的文件中,在项目文件夹的终端使用如下指令,完成 Nightmare 的安装,指令如下:

   $ yarn add nightmare --save

如果成功安装,在项目中的 package.json 文件中,项目运行依赖项最终的内容如下代码所示:

   {

     "name": "code",

     "version": "1.0.0",

     "description": "",

     "main": "index.js",

     "scripts": {

       "test": "echo "Error: no test specified" && exit 1"

     },

     "author": "tgrong",

     "license": "ISC",

     "dependencies": {

       "cheerio": "^1.0.0-rc.3",

       "express": "^4.17.1",

       "mysql": "^2.18.1",

       "nightmare": "^3.0.2",

       "superagent": "^5.2.2",

       "superagent-charset": "^1.2.0"

     }

   }

在上述代码中,第 15 行就是新安装成功的 Nightmare 模块。

2. 常用 API

安装 Nightmare 模块是使用它的前提,在需求使用的文件中,导入该模块,就可以调用它的 API 了,众多的 API 中,如下几个方法最用常用,代码如下:

   var Nightmare = require('nightmare');

   var nightmare = Nightmare({

           pollInterval: 50,

           waitTimeout : 5000

       });

       nightmare

           .goto(url,[headers]) // 要访问的网

           .inject(type,url) // 访问时注入的文件类型和名称

           .type(selector,[text]) // 向某个 selector 元素输入 text 内容

           .click(selector) // 模拟单击某个 selector 元素

           .wait(selector) // 等待某个 selector 元素出现,返回 true 结束,否则一直等待

           .evaluate(fn) //    fn 为回调函数,在函数中可以执行操作的功能代码

           .end() // 执行完成,等待数据处理

           .then(fn) // 请求和处理完成后,通过 fn 回调函数获取相关数据

           .catch(fn) // 如果请求出现异步,通过 fn 回调函数返回异常信息

在上述代码中,第 1 行,先导入 Nightmare 模块以备使用,第 2 行,实例化模块,在此过程中,可以配置相关参数,如 pollInterval 用于设置轮询的时间,默认为 250ms,waitTimeout 用于设置请求超时的时间,默认值为 3000ms,这些值都可以在实例化模块对象是进行重置。

完成实例化对象之后,就可以调用对象的方法,在上述代码中,第 7~15 行都是 Nightmare 模块常用的方法,方法对应的功能,见附加的注释所示。

3. 简单示例

下面通过一个简单的示例来进一步说明 Nightmare 模块对象的使用,需要是:在一个页面中,添加一个 button 和 div 元素,当点击 button 时,div 延时 2 秒显示 "hello,world!" 内容,页面代码如下所示:

   

   

   

       

       <meta name="viewport" content="width=device-width, initial-scale=1.0">

       <meta http-equiv="X-UA-Compatible" content="ie=edge">

       Document

   

   

       

...

       请求

       

           let tip = document.getElementById("tip");

           let btn = document.getElementById("btn");

           btn.onclick = function(e){

               setTimeout(function(){

                   tip.innerHTML = "hello,world!"

               },2000)

           }

       

   

   

使用 Nightmare 模块对象访问页面执行时的地址,模拟用户点击按钮的操作,获取 div 元素显示的内容,并将它输出在控制台,操作代码如下所示:

   var nightmare = Nightmare({

           pollInterval: 50,

           waitTimeout : 3000

       });

       nightmare

           .goto('http://127.0.0.1:5500/test.html')

           .click("#btn")

           .wait(function(){

              return document.querySelector("#tip").innerText.length>3;

           })

           .evaluate(function(){

              return document.querySelector("#tip").innerText;

           })

           .end()

           .then(function(res){

               console.log(res);

           })

           .catch(function (error) {

             console.error('failed:', error);

           });

在上述代码中,wait() 方法内的 return 返回的是一个布尔值,如果为 true 则结束,否则一直等待,直到超时,evaluate() 方法中,return 返回的是 then 方法中的 res 对象值,经过上述代码的执行,2 秒后,将在控制台面板输出 "hello,world!" 字样,表示模拟用户操作成功,并获取到了返回的数据。

5.3 自动搜索任意主题素材

​在掌握了 Nightmare 模块的基本用法之后,接下来将它运用到之前的素材抓取中,在上面的小节中,通过在网站中搜索"三维动画",并在结果页中抓取与“三维动画”主题相关的素材,但如果要修改搜索的主题,则又要改变对应的 URL 链接,比较麻烦,如果改用 Nightmare 模块,自动填写任意内容,模拟用法查询,则过程要简化很多,具体来讲,分下面三个步骤,实现自动搜索并获取素材数据的效果。

1. 分析网站搜索的页面结构

在网站任意页中,都有搜索的导航栏,在导航栏的文本框中,输入任意的主题,再点击搜索按钮,即完成了搜索的工作,效果与元素代码如下图 1-9 所示:

image.png

从页面结构分析可以知道,先在类别名称为“search-input”的元素中输入需要搜索的主题内容,然后,再点击类别名称为“search-submit”的按钮,则完成了一次主题的搜索。

2. 根据结构执行自动搜索主题

依据页面的结构,调用 Nightmare 模块,在实例化的对象中,请求网站首页,并向类别名为“search-input”的元素中输入任意的搜索内容,再单击类别名称为“search-submit”的按钮,则完成了自动搜索主题内容的功能,创建一个自定义的函数 autoSearch,加入如下代码:

   function autoSearch(v) {

       var nightmare = Nightmare({

           pollInterval: 50,

           waitTimeout: 5000

       });

       nightmare

           .goto('vjshi.com/')

           .type(".search-input", v)

           .click(".search-submit")

           .wait(function () {

               return document.querySelector("button.next") != null;

           })

           .evaluate(function () {

               return { "text": document.querySelector("body").innerHTML };

           })

           .end()

           .then(function (res) {

               console.log(res.text);

           })

           .catch(function (error) {

               console.error('failed:', error);

           });

   }

需说明的是: 参数 v 表示要查询的关键字内容,而返回的是整个 body 元素的的 HTML 格式内容,因此,当调用该函数时,则在控制台输出获取的全部页面元素内容,例如:如下代码调用函数:

   autoSearch('中国人');

执行上述代码后,将在控制台输出查询到主题为"中国人"的素材记录,效果如下图 1-10 所示:

image.png

3. 筛选结果页面的数据信息

为了过滤并取到有用的数据,必须在输出 then 方法中,调用之前构建好的 getImgs 函数,则实现整个有用数据的筛选,为下一步写入数据库中作好准备,代码修改如下:

   ...

          .then(function (res) {

              //console.log(res.text);

              getImgs(res)

          })

   ...

执行上述代码的修改之后,在控制台输出的内容如下图 1-11 所示:

image.png

  1. 嵌套数据请求处理

6.1 功能需求描述

在搜索结果页面中,调用自定义的 getImgs 函数,分析页面的 DOM 结构,获取并保存了需要写入的字段值,为了确保数据写入时的稳定性,必须先将全部需要的数据保存完成后,才能写入数据库中,因此,这是一个嵌套的步骤,第 1 步,获取全部写入的数据,在完成第 1 步之后,再执行第 2 步,执行写入操作。

6.2 promise 对象

1. 什么是 promise 对象

promise 对象是解决异步编程的一种非常不错的方案,在目前的应用中,大部分都使用异步方式进行数据的请求和处理,它的最大核心就是解决了多个异步请求时,嵌套参数传递的问题,同时,该对象的功能和代码比传统的 JavaScript 更高效和简洁,因此,基于这此,使得这种方案深受开发者喜爱 。

2. 简单 promise 对象应用

为了进一步说明它的回调功能强大,接下来通过一个简单的示例来进行演示,比如,要在控制台输出三个字符,"1010, tgrong,18",它分别代码一个用户的编号、昵称和年龄,而这三个字符又是分别通过嵌套回调传参的方式实现的,即先取到编号,才能获取昵称,取到昵称后,才能能得到年龄,实现代码如下所示:

   new Promise(function (success) {

       setTimeout(function () {

           success('1010');

       }, 2000)

   }).then(v1 => {

       return v1 + ',tgrong';

   }).then(v2 => {

       console.log(v2 + ",18")

   })

在上述代码中,每一个次调用 then() 方法时,都可以接收到上一次处理返回的值,从而实现嵌套请求。

6.3 完成代码

​在掌握了 promise 对象的基本使用功能之后,接下来使用它来修改之前编写的 getImgs 函数,实现嵌套传值的效果,修改后的代码如下所示:

function getImgs(res) {

    new Promise(function (success) {

        //... 省略原有代码

        success(arrData)

    }).then(arrData => {

        arrData.forEach(item => {

            console.log(item)

        });

    })

}

​在上述代码的第 2 行,先实例化了一个 promise 对象,第 3 行将原有的代码放置在成功回调函数中,第 4 行返回获取的数组对象,并作为下一步操作的传入值,第 5 行调用对象的 then 方法,接收传入的数组对象值,执行下一步操作,第 6~9 行,遍历接收到数组对象,并在控制台输出每个成员,效果如下图 1-12 所示:

image.png

  1. 写入 MySQL 数据库

7.1 数据库和表的构建

​为了将数据永久保存,必须创建数据库,通过数据库中的表保存每一条记录,Node 框架可以非常方便地连接许多关系和非关系型的数据库,如 MySQL、MongoDB,接下来以 MySQL 为例,创建一个名称为 getimgs 的数据库,并在该数据库下,添加一个名称为 imginfo 的表,结构如下图 1-13 所示:

image.png

7.2 Node 操作表

​创建好数据库和表之后,在 Node 中可以导入 MySQL 模块,使用该模块连接数据库,并操作表中的记录,如增加、编辑和删除记录,具体操作如下列步骤。

1. 连接数据库

在 api.js 文件中,为了连接创建好的数据库,加入如下代码:

   const mysql = require("mysql");

   const db = mysql.createConnection({

       host: "localhost",

       user: "root",

       password: "12345678",

       database: "getimgs"

   })

   db.connect(err => {

       if (err) throw err;

       console.log("数据库连接成功!")

   })

在上述代码中,第 1 行导入安装好的 MySQL 模块,第 27 行,创建一个名称为 db 的新连接,用于连接名称为 getimgs 的数据库,第 811 行,执行数据库的连接操作,如果连接成功,则在控制台输出“数据库连接成功!”的字样,否则,抛出异常信息。

2. 操作表内容

接下来,调用 DB 对象的 query 方法,执行相关的 SQL 语句,如:向表 imginfo 增加一条新记录,如果增加成功,则查询记录,并将查询结果输出至控制台,加入如下的代码:

   function addRows(data) {

       let sql = "insert into imginfo set ?"

       db.query(sql, data, (err, result) => {

           if (err) {

               console.log(err.sqlMessage);

           } else {

               console.log(result.affectedRows);

           }

       })

   }

   function seleRows() {

       let sql = "select * from imginfo"

       db.query(sql, (err, rows) => {

           if (err) {

               console.log(err.sqlMessage);

           } else {

               rows.forEach(item => {

                   console.log(item);

               })

           }

       })

   }

   addRows({ "id": 1011,

             "name": '张三',

             "imgurl": "http://imgurl",

             "videurl": "http://videurl

           });

   seleRows();

在上述代码的第 1~10 行中,自定义了一个名称为 addRows 的函数,用于向表中增加指定内容的记录,如果增加失败,则输出异常信息,否则,在控制台输出成功增加的记录总数。

在上述代码的第 11~22 行中,自定义了另一个名称为 seleRows 的函数,用于获取表中的记录,如果获取失败,则输出异常信息,否则,在控制台输出获取的每条记录对象。

在上述代码的第 23~27 行中,执行 addRows 函数,增加一条指定内容的记录,第 28 行中,执行 seleRows 函数,在控制台显示获取的内容,执行后,最终在控制台输出的内容如下图 1-14 所示:

image.png

7.3 将抓取记录写入数据库

​在掌握了数据库的基本操作之后,接下来,将抓取到的记录,增加到表 imginfo 中,再次改造自定义的 getImgs 函数,遍历每条记录,并增加到表中,修改代码如下所示:

function getImgs(res) {

    new Promise(function (success) {

        //... 省略原有代码

        success(arrData)

    }).then(arrData => {

        arrData.forEach(item => {

            addRows(item);

        });

        seleRows();

    })

}

​在上述代码中,第 7 行代码,将抓取的每一个数据对象作为自定义函数 addRows 的参数,添加到表中,第 9 行代码中,执行自定义的函数 seleRows,将增加后的数据全部显示在控制台中,输出效果如下图 1-15 所示:

image.png

需要说明的是: 现在抓取并保存的记录全是主题为 "中国人"的内容,如果需要修改主题,则只需要在调用自定义的 autoSearch 函数时,传入不同的参数,如:抓取 "美女"的内容,代码执行如下:

autoSearch('美女');

​此时,数据库中的 imginfo 表示中,将保存了与 "美女"相关的内容,效果如下图 1-16 所示:

image.png

最后说明: 写到这里,我的分享暂告一段落,也衷心希望大家能通过我的分享,能在使用 Node 开发爬虫业时,思路上受到启发,技术上受到拓展,谢谢订阅我分享内容的每一位朋友,分享内容中的代码比较分散,完整的项目代码,大家点击全部源码获取,感谢大家!