Nightmare.js实践

800 阅读5分钟

前言

前两天公司的测试同事问我了一个关于生成网页快照的技术问题,经过一番调研发现了nightmarejs这个库

阅读文档和简单实践后对其有了基本的认知,大体可以将其理解为运行在node Electron环境中的无头浏览器,类似python的selenium,通常用于网页的UI测试和一些自动化操作

虽然不是什么新奇的东西,但刻在一个程序员DNA内的折腾本能还是被激活了...

没有需求创造需求也要搞(不是)

所以本文也算抛砖引玉,带领大家实现一个命令行里的知乎,具有查看热门话题和搜索快捷跳转的功能

或许你在阅读完本篇文章后,会萌生更多有趣(但没用)的想法,都可以使用nightmare.js去实现,甚至可以自己搭建一个快捷指令集

那么我们就开始吧!

1.确定需求

当我们使用nightmare启动了无头浏览器后访问知乎页面 我们可以获取到页面上的任何内容 只需定位到要使用的元素在命令行里进行IO操作即可实现

所以整理一下思路就是

知乎热榜

启动无头浏览器-->访问知乎热榜-->获取列表数据在命令行中展示-->选择某条话题-->自动打开浏览器并跳转

搜索功能

在命令行中键入搜索内容-->自动打开浏览器并搜索

2.准备工作

首先我们先安装今天的主角

npm install --save nightmare

其次安装inquirer.jsopen.js

npm install --save inquirer

npm install --save open

inquirer.js 有CLI搭建经历的同学一定不会陌生,它主要用于node环境下命令行的GUI交互,说白了就是命令行里的表单

open.js 是执行node环境里打开文件、浏览器网址、app等操作的库

关于这两个库使用方法都比较简单,可以自行去看文档了解,这里不做赘述

3.开整!

首先新建一个zhihu.js的文件 键入以下代码


import Nightmare from "nightmare";
import inquirer from "inquirer";
import open from 'open'

const nightmare = Nightmare({ show: true });

引入用到的包并实例化一个Nightmare对象

更多可选参数

这里设置show:true 意思是启动后显示无头浏览器界面 如果为false则只是在后台运行

在开发阶段我们要看模拟效果所以先将其设为true,等大功告成后就不需要了

这样我们便启动了一个无头浏览器

然后我们先来把最核心的功能搞定

实现热门功能

第一步 启动无头浏览器-->访问知乎热榜


const hotTopic = () => {
  nightmare
    .goto("https://www.zhihu.com/knowledge-plan/hot-question/hot/0/hour")//跳转知乎热榜
    .then(() => {
        console.log('已成功打开页面')
    });
}

很简单 调用.goto方法即可

可以看到 nightmare的方法调用方式有些像远古时期的jquery 链式调用的方式 很符合工作流的使用直觉


第二步 访问知乎热榜-->获取列表数据在命令行中展示

const hotTopic = () => {
  nightmare
    .goto("https://www.zhihu.com/knowledge-plan/hot-question/hot/0/hour")//跳转知乎热榜
    .wait("div[role=list]>.css-vurnku")//等待热榜话题列表元素出现
    .evaluate(() => {
      let arr = [];
      let doms = document.querySelectorAll(
        "div[role=list]>.css-vurnku>div>div>a"
      );
      doms.forEach((item, index) => {
        arr.push({ name: doms[index].innerText, href: doms[index].href });
      });
      return arr;
    })
    .then((arr) => {
        console.log(arr)
    });
}

打开页面后 像一般爬虫操作一样 我们先.wait等待列表元素在页面上渲染完成

目标元素类名已经帮大家找好了 直接用即可

找到目标元素后调用.evaluate进行工作流的操作 将其对应子元素的列表项一个个循环保存 输出在本地


第三步 选择某条话题-->自动打开浏览器并跳转

const hotTopic = () => {
  nightmare
    .goto("https://www.zhihu.com/knowledge-plan/hot-question/hot/0/hour")//跳转知乎热榜
    .wait("div[role=list]>.css-vurnku")//等待热榜话题列表元素出现
    .evaluate(() => {
      let arr = [];
      let doms = document.querySelectorAll(
        "div[role=list]>.css-vurnku>div>div>a"
      );
      doms.forEach((item, index) => {
        arr.push({ name: doms[index].innerText, href: doms[index].href });
      });
      return arr;
    })
    .then((arr) => {
      const promptList = [
        {
          type: "rawlist",
          message: "topics:",
          name: "topic",
          choices: arr.map((i) => i.name),
          filter: (val) => arr.findIndex((i) => i.name == val),
        },
      ];
      inquirer.prompt(promptList).then((answers) => {
        open(arr[answers.topic].href, {
          app: {
            name: open.apps.chrome,
          },
        });
      });
    })
    .then((arr) => {
        console.log(arr)
    });
}

在我们拿到列表数据后 就要着手实现命令行的交互

首先定义了用于构造inquirer.js 的rawlist类型表单项的配置 其实就相当于配置antd中Select的options和onChange

当选中某条话题后调用open方法打开chrome浏览器并跳转对应的页面即可 当然open.js还可以打开很多其他的app 前提是要官方支持 目前支持的浏览器为chrome firefox edge

第四步 处理入口操作

const promptList = [
  {
    type: "rawlist",
    message: "热门还是搜索?",
    name: "type",
    choices: ["热门", "搜索"],
  },
];

inquirer.prompt(promptList).then((answers) => {
  answers.type == "热门" && hotTopic();
  answers.type == "搜索" && search();
});

至此 知乎热榜的功能已经完成 你可以直接执行

node zhihu.js

来查看效果



也可以进一步在package.json里配置命令

...
"scripts": {
    "zhihu":"node ./zhihu.js"
},
...

这样以后如果你写了别的快捷功能都可以加在这里进行统一管理

实现搜索功能

const search = () => {
  const promptList = [
    {
      type: "input",
      message: "请输入搜索内容:",
      name: "topic",
    },
  ];
  inquirer.prompt(promptList).then((answers) => {
    https: open(
      "https://www.zhihu.com/search?type=content&q=" + answers.topic,
      {
        app: {
          name: open.apps.chrome,
        },
      }
    );
  });
};

搜索功能更加简单 只需通过inquirer的.prompt方法 提示并获取输入值 拼接到跳转链接即可

完整代码

//zhihu.js

import Nightmare from "nightmare";
import inquirer from "inquirer";
import open from 'open'

const nightmare = Nightmare({ show: false });

const search = () => {
  const promptList = [
    {
      type: "input",
      message: "请输入搜索内容:",
      name: "topic",
    },
  ];
  inquirer.prompt(promptList).then((answers) => {
    https: open(
      "https://www.zhihu.com/search?type=content&q=" + answers.topic,
      {
        app: {
          name: open.apps.chrome,
        },
      }
    );
  });
};
const hotTopic = () => {
  nightmare
    .goto("https://www.zhihu.com/knowledge-plan/hot-question/hot/0/hour")
    .wait("div[role=list]>.css-vurnku")
    .evaluate(() => {
      let arr = [];
      let doms = document.querySelectorAll(
        "div[role=list]>.css-vurnku>div>div>a"
      );
      doms.forEach((item, index) => {
        arr.push({ name: doms[index].innerText, href: doms[index].href });
      });
      return arr;
    })
    .then((arr) => {
      const promptList = [
        {
          type: "rawlist",
          message: "topics:",
          name: "topic",
          choices: arr.map((i) => i.name),
          filter: (val) => arr.findIndex((i) => i.name == val),
        },
      ];
      inquirer.prompt(promptList).then((answers) => {
        open(arr[answers.topic].href, {
          app: {
            name: open.apps.chrome,
          },
        });
      });
    })
    .then((arr) => {});
};

const promptList = [
  {
    type: "rawlist",
    message: "热门还是搜索?",
    name: "type",
    choices: ["热门", "搜索"],
  },
];
inquirer.prompt(promptList).then((answers) => {
  answers.type == "热门" && hotTopic();
  answers.type == "搜索" && search();
});

本文举例的功能比较简单 有手就行 主要目的还是文章开头说的 抛砖引玉

nightmare.js为我们提供了一个完全模拟真实用户操作的浏览器环境

一旦你熟悉了它的使用方法 我相信你会有更多 更实用 更具想象力的点子去实现

理论上来说只要能在浏览器中展示和操作的内容 你都可以使用nightmare.js 在命令行中搞定

以上就是本篇文章的全部内容

如果你觉得本文对你有所帮助的话 不妨关注点赞支持我哦

(完)