從零開始開發一個Node交互式命令行應用

導言:對於大多數前端開發者而言,談到命令行工具,你們確定都用過。可是談到開發命令行工具,估計就沒幾人有了解了。本文旨在用最短的時間內,幫您開發一個實用(斜眼笑)的圖片爬蟲命令行應用。

想追求更好的閱讀體驗,請移步拓跋的前端客棧。同時把項目地址放在顯眼的位置javascript

Puppeteer 簡介

什麼是 Puppeteer?

Puppeteer 是 Google Chrome 團隊官方的無界面(Headless)Chrome 工具。Chrome 做爲瀏覽器市場的領頭羊,Chrome Headless  將成爲 web 應用   自動化測試   的行業標杆。因此咱們頗有必要來了解一下它。css

puppeteer

Puppeteer 能夠作什麼?

Puppeteer 能夠作的事情有不少,包括但不限於:前端

  • 利用網頁生成 PDF、圖片
  • 能夠從網站抓取內容
  • 自動化表單提交、UI 測試、鍵盤輸入等
  • 幫你建立一個最新的自動化測試環境(chrome),能夠直接在此運行測試用例
  • 捕獲站點的時間線,以便追蹤你的網站,幫助分析網站性能問題

Puppeteer 有什麼優點?

  • 相對於真實瀏覽器而言,少了加載 css,js 以及渲染頁面的工做。無頭瀏覽器要比真實瀏覽器快的多。
  • 能夠在無界面的服務器或 CI 上運行,減小了外界的干擾,更穩定。
  • 在一臺機器上能夠模擬運行多個無頭瀏覽器,方便進行併發運行。

如何安裝 Puppeteer?

安裝  Puppeteer  很簡單,以下:
npm i --save puppeteer
or
yarn add puppeteerjava

須要注意的是,因爲用到了 ES7 的  async/await  語法 ,node  版本最好是 v7.6.0 或以上。node

如何使用 Puppeteer?

因爲本文不是專門講 Puppeteer 的文章,故這部分暫且略過,你們能夠去看下面的連接學習。git

Puppeteer Githubgithub

Puppeteer Api Docweb

Puppeteer 中文 Api Docchrome

說了這麼多,Puppeteer 與咱們要開發的命令行應用有什麼關係呢?咱們準備製做一個抓圖命令行工具,不使用傳統的請求式爬蟲,咱們使用 Puppeteer 這種無頭瀏覽器,從 DOM 裏抓圖,這樣能有效規避部分爬蟲防護手段。shell

Puppeteer 簡單應用

case 1. 屏幕截圖

直接上代碼,很好理解:

const puppeteer = require("puppeteer");

const getScreenShot = async () => {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();
  await page.goto("https://baidu.com");
  await page.screenshot({ path: "baidu.png" });

  await browser.close();
};

getScreenShot();

這段代碼的意思就是以 headless(無頭)模式打開瀏覽器,而後打開一個新標籤頁,跳轉到百度網址, 而且進行屏幕截圖,保存爲 baidu.png 爲名的圖片,最後關閉瀏覽器。

執行結果以下。

baidu

case 2. 抓取網站信息

接下來學習如何用 Puppeteer 抓取網站信息了。

此次咱們來抓取 jd 書單信息。

// book info spider
const puppeteer = require("puppeteer");
const fs = require("fs");

const spider = async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  await page.goto("https://search.jd.com/Search?keyword=javascript");

  const result = await page.evaluate(() => {
    let elements = document.querySelectorAll(".gl-item");

    const data = [...elements].map(i => {
      return {
        name: i.querySelector(".p-name em").innerText,
        description: i.querySelector(".p-name i").innerText,
        price: i.querySelector(".p-price").innerText,
        shop: i.querySelector(".p-shopnum").innerText,
        url: i.querySelector(".p-img a").href
      };
    });
    return data; // 返回數據
  });

  browser.close();
  return result;
};

spider().then(value => {
  fs.writeFile(`${__dirname}/javascript.json`, JSON.stringify(value), err => {
    if (err) {
      throw err;
    }
    console.log("file saved!");
  });
  console.log(value); // Success!
});

咱們作的就是跳轉到關鍵字是 javascript 的頁面,而後對頁面的 dom 結構進行分析,找到圖書列表所對應的書名、描述、價格、出版社、網頁連接信息,而後把數據寫入到 javascript.json 文件裏去,方便咱們保存瀏覽。

邏輯很簡單。這已是一個爬蟲的雛形了,最後獲得以下圖所示的 json 文件,很是給力。

javascript.json

case 3. 圖片爬蟲

圖片爬蟲,這就是咱們要作的命令行應用的主題了。

一個最基本的思路是這樣的:

打開瀏覽器 —> 跳轉到百度圖片 —> 獲取 input 框的焦點 —> 輸入 keywords —> 點擊搜索按鈕 —> 跳轉至結果列表頁 —> 下拉到底部 —> 操做 dom,獲取全部圖片的 src 備用 —> 根據 src 將對應圖片保存到本地 —> 關閉瀏覽器

代碼實現之:

首先是瀏覽器操做部分

const browser = await puppeteer.launch(); // 打開瀏覽器
const page = await browser.newPage(); // 打開新tab頁
await page.goto("https://image.baidu.com"); // 跳轉到百度圖片
console.log("go to https://image.baidu.com"); // 獲取input框的焦點

await page.focus("#kw"); // 把焦點定位到搜索input框
await page.keyboard.sendCharacter("貓咪"); // 輸入關鍵字
await page.click(".s_search"); // 點擊搜索按鈕
console.log("go to search list"); // 提示跳轉到搜索列表頁

而後是圖片處理部分

page.on("load", async () => {
  await autoScroll(page); // 向下滾動加載圖片
  console.log("page loading done, start fetch...");
  const srcs = await page.evaluate(() => {
    const images = document.querySelectorAll("img.main_img");
    return Array.prototype.map.call(images, img => img.src);
  }); // 獲取全部img的src
  console.log(`get ${srcs.length} images, start download`);
  for (let i = 0; i < srcs.length; i++) {
    await convert2Img(srcs[i], target);
    console.log(`finished ${i + 1}/${srcs.length} images`);
  } // 保存圖片
  console.log(`job finished!`);
  await browser.close();
});

由於百度圖片是往下滾動就能夠繼續懶加載。所以,咱們想要加載更多圖片,能夠先往下滾動一下子。而後經過分析 dom 結構來獲取列表裏全部圖片的 src,最後進行下載。

執行如下,就能獲得一系列貓咪的圖片:

cat

圖片下載的地方只寫了主函數,更詳細的代碼能夠去參見github.

至此,咱們用 Node 和 Puppeteer 開發出了一個最基本的圖片爬蟲工具。

如何優化?

這個圖片爬蟲工具目前還有點 low 啊,咱們的目標是要開發一個交互式的命令行應用,確定不能止於此。有哪些能夠進一步優化的點呢?通過簡單的思考,我列了一下:

  • 下載圖片的內容能夠自定義
  • 能夠支持用戶選擇圖片下載張數
  • 支持命令行傳參
  • 支持命令行交互
  • 交互界面美觀
  • 支持雙擊直接運行
  • 支持全局命令行調用

使用 commander.js 支持命令行傳參

Commander 是一款重量輕,表現力和強大的命令行框架。提供了用戶命令行輸入和參數解析強大功能。

const program = require("commander");

program
  .version("0.0.1")
  .description("a test cli program")
  .option("-n, --name <name>", "your name", "zhl")
  .option("-a, --age <age>", "your age", "22")
  .option("-e, --enjoy [enjoy]")
  .action(option => {
    console.log('name: ', option.name);
    console.log('age: ', option.age);
    console.log('enjoy: ', option.enjoy);
  });

program.parse(process.argv);

Commander十分容易上手,上面這一段代碼僅用了寥寥數行,就定義了一個命令行的輸入與輸出。其中:

  • version 定義版本號
  • description 定義描述信息
  • option 定義輸入選項,傳3個參數,如option("-n, --name <name>", "your name", "GK"),第一項是傳參的值,-n是簡寫形式,--name是全稱形式, <name>表示輸入的參數,<>是必填項,若是是[],則是選填項。第二項「your name"是求助help時的提示信息,告訴用戶應該輸入的內容,最後一項"GK"是默認值。
  • action 定義執行的操做,是一個回調函數,入參是前文輸入的option選項,若是沒有輸入option,則使用定義的默認值。

要查詢更詳細的api,請參考Commander Api文檔

執行一下上述腳本,能夠獲得:

commander.png

這樣命令行就能夠作到簡單的交互效果了。可是有沒有以爲不夠好看呢,別急,繼續往下看。

使用inquirer製做可交互命令行應用

inquirer能夠爲Node製做可嵌入式的美觀的命令行界面。

能夠提供問答式的命令輸入:

inquirer1

能夠提供多種形式的選擇界面:

inquirer2
inquirer3

能夠對輸入信息進行校驗:
inquirer4

最後能夠對輸入信息進行處理:
inquirer5

上面的例子是inquirer的官方例子,能夠參考pizza.js

inquirer的文檔能夠查看inquirer documents

有了inquirer,咱們就能夠製做更爲精美的交互式命令行工具了。

使用 chalk.js來讓交互界面更美觀

chalk.js

chalk的語法很是簡單:

const chalk = require('chalk');
const log = console.log;

// Combine styled and normal strings
log(chalk.blue('Hello') + ' World' + chalk.red('!'));
// Compose multiple styles using the chainable API
log(chalk.blue.bgRed.bold('Hello world!'));
// Pass in multiple arguments
log(chalk.blue('Hello', 'World!', 'Foo', 'bar', 'biz', 'baz'));
// Nest styles
log(chalk.red('Hello', chalk.underline.bgBlue('world') + '!'));
// Nest styles of the same type even (color, underline, background)
log(chalk.green(
  'I am a green line ' +
  chalk.blue.underline.bold('with a blue substring') +
  ' that becomes green again!'
));

能夠輸出以下信息,一看便懂:

chalk2

再讓咱們作點有意思的事情...

想必有人看到過下面知乎的控制檯效果,既然要作點有意思的事情,今天咱們不妨也把這種效果加到命令行程序裏面,提高一下逼格。

zhihu

首先咱們準備一副ASCII碼用來打印,各位能夠自行搜索text轉ASCII,網上的轉化方案不要太多。咱們準備製做的命令行image spider就製做一個IMG SPD的ASCII碼字符串吧~

通過挑選,效果如圖:

imgspd

這種複雜的字符串怎麼打印出來呢?直接保存爲string必定是不行的,格式會亂的一塌糊塗。

想要能完整的打印出格式來,有一個取巧的方法,以註釋的形式打印出來。什麼能保存註釋呢?~~ function。

因此事情就簡單到了打印一個function。可是直接打印函數仍是不行的,這時候就用到了能夠懟天懟地的toString()方法,咱們只須要把註釋中間的部分用正則匹配出來就好了,easy~

imgspd2

最後看一看效果,鐺鐺鐺鐺~

imgspd3

支持雙擊運行

這裏使用一種叫作Shebang的技術。

Shebang(也稱爲 Hashbang )是一個由#和!構成的字符序列 #! ,其出如今文本文件的第一行的前兩個字符。 在文件中存在 Shebang 的狀況下,類 Unix 操做系統的程序加載器會分析 Shebang 後的內容,將這些內容做爲解釋器指令,並調用該指令,並將載有 Shebang 的文件路徑做爲該解釋器的參數。

node下咱們使用#! /usr/bin/env node便可

這時候咱們即可以取消文件的擴展名.js了。

加入環境變量,支持全局調用

package.json裏面配置

"bin": {
  "img-spd": "app"
},

執行npm link,它將會把img-spd這個字段複製到npm的全局模塊安裝文件夾node_modules內,並建立符號連接(symbolic link,軟連接),也就是將 app 的路徑加入環境變量 PATH。

這時,在任意目錄下,直接命令行輸入img-spd便可運行此命令行

尾聲

至此,要改進的地方已經所有修改完畢,快來看看咱們的成品吧~

imgspd4

看着一整個文件夾的gakki,感受滿滿的幸福要溢出來了

gakki

最後用動圖來展現一下:

img-spd

附錄

項目地址

項目地址

Install

npm install -g img-spd

Usage

img-spd

or

Usage: img-spd [options]

img-spd is a spider get images from image.baidu.com

Options:
  -v --version               output the version number
  -k, --key [key]            input the image keywords to download
  -i, --interval [interval]  input the operation interval(ms,default 200)
  -n, --number [number]      input the operation interval(ms,default 200)
  -m, --headless [headless]  choose whether the program is running in headless mode
  -h, --help                 output usage information
相關文章
相關標籤/搜索