V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Get Google Chrome
Vimium · 在 Chrome 里使用 vim 快捷键
yesvods
V2EX  ›  Chrome

技能树升级——Chrome Headless 模式

  •  
  •   yesvods · 2017-04-15 01:41:43 +08:00 · 3594 次点击
    这是一个创建于 2787 天前的主题,其中的信息可能已经有所发展或是发生改变。

    作者: Jogis

    原文链接: https://github.com/yesvods/Blog/issues/10

    也许最近已经听说 Chrome59 将支持headless模式,PhantomJS核心开发者Vitaly表示自己将会失业了。

    Headless 模式解决了什么问题

    3 年前,无头浏览器PhantomJS已经如火如荼出现了,紧跟着NightmareJS也成为一名巨星。无头浏览器带来巨大便利性:页面爬虫、自动化测试、 WebAutomation...

    为啥 Chrome 又插了一脚

    用过 PhantomJS 的都知道,它的环境是运行在一个封闭的沙盒里面,在环境内外完全不可通信,包括 API 、变量、全局方法调用等。一个之前写的微信页面爬虫,实现内外通信的方式极其 Hack ,为了达到目的,不择手段,令人发指,看过的哥们都会蛋疼。

    So, 很自然的, Chrome59 版支持的特性,全部可以利用,简直不要太爽:

    • ES2017
    • ServiceWork(PWA 测试随便耍)
    • 无沙盒环境
    • 无痛通讯&API 调用
    • 无与伦比的速度
    • ...

    技能树启动点

    为了点亮技能树,我们需要以下配置:

    大致来说,有那么个过程:

    启动 Headless 模式

    有各种脚本启动方式,本次我们使用 termial 参数方式来打开:

    $ /Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --headless --remote-debugging-port=9222
    

    在 Dock 中,一个黄色的东西就会被启动,但是他不会跳出来。

    操控无头浏览器

    依旧有各种方式,我们先安装一个工具帮助我们来对黄色浏览器做点事情:

    $ tnpm i -S chrome-remote-interface 
    

    燥起来

    捕获所有请求

    Pretty Simple ,写一个 index.js :

    const CDP = require("chrome-remote-interface");
     
    CDP(client => {
      // extract domains
      const { Network, Page } = client;
      // setup handlers
      Network.requestWillBeSent(params => {
        console.log(params.request.url);
      });
      Page.loadEventFired(() => {
        client.close();
      });
      // enable events then start!
      Promise.all([Network.enable(), Page.enable()])
        .then(() => {
          return Page.navigate({ url: "https://github.com" });
        })
        .catch(err => {
          console.error(err);
          client.close();
        });
    }).on("error", err => {
      // cannot connect to the remote endpoint
      console.error(err);
    });
    

    AND run it :

    $ node index.js
    

    结果会展示一堆 url :

    https://github.com/
    https://assets-cdn.github.com/assets/frameworks-12d63ce1986bd7fdb5a3f4d944c920cfb75982c70bc7f75672f75dc7b0a5d7c3.css
    https://assets-cdn.github.com/assets/github-2826bd4c6eb7572d3a3e9774d7efe010d8de09ea7e2a559fa4019baeacf43f83.css
    https://assets-cdn.github.com/assets/site-f4fa6ace91e5f0fabb47e8405e5ecf6a9815949cd3958338f6578e626cd443d7.css
    https://assets-cdn.github.com/images/modules/site/home-illo-conversation.svg
    https://assets-cdn.github.com/images/modules/site/home-illo-chaos.svg
    https://assets-cdn.github.com/images/modules/site/home-illo-business.svg
    https://assets-cdn.github.com/images/modules/site/integrators/slackhq.png
    https://assets-cdn.github.com/images/modules/site/integrators/zenhubio.png
    https://assets-cdn.github.com/assets/compat-8a4318ffea09a0cdb8214b76cf2926b9f6a0ced318a317bed419db19214c690d.js
    https://assets-cdn.github.com/static/fonts/roboto/roboto-medium.woff
    ...
    

    捕获 DOM 内所有图片

    这次轮到演示一下如何操控 DOM :

    const CDP = require("chrome-remote-interface");
     
    CDP(chrome => {
      chrome.Page
        .enable()
        .then(() => {
          return chrome.Page.navigate({ url: "https://github.com" });
        })
        .then(() => {
          chrome.DOM.getDocument((error, params) => {
            if (error) {
              console.error(params);
              return;
            }
            const options = {
              nodeId: params.root.nodeId,
              selector: "img"
            };
            chrome.DOM.querySelectorAll(options, (error, params) => {
              if (error) {
                console.error(params);
                return;
              }
              params.nodeIds.forEach(nodeId => {
                const options = {
                  nodeId: nodeId
                };
                chrome.DOM.getAttributes(options, (error, params) => {
                  if (error) {
                    console.error(params);
                    return;
                  }
                  console.log(params.attributes);
                });
              });
            });
          });
        });
    }).on("error", err => {
      console.error(err);
    });
    

    最后会返回数组,看起来像酱紫:

    [
      [ 'src',
        'https://assets-cdn.github.com/images/modules/site/home-illo-conversation.svg',
        'alt',
        '',
        'width',
        '360',
        'class',
        'd-block width-fit mx-auto' ]
      [ 'src',
        'https://assets-cdn.github.com/images/modules/site/home-illo-chaos.svg',
        'alt',
        '',
        'class',
        'd-block width-fit mx-auto' ]
      [ 'src',
        'https://assets-cdn.github.com/images/modules/site/home-illo-business.svg',
        'alt',
        '',
        'class',
        'd-block width-fit mx-auto mb-4' ]
        ...
    ]
    

    chrome-remote-interface 提供一套完整的 API 用于利用全量 Chrome 特性,更多使用方法参考: https://github.com/cyrus-and/chrome-remote-interface

    总结

    Chrome Headless 特性,不仅仅革新了原有格局,而且提高开发效率,降低使用门槛,对于经常使用爬虫、自动化测试前端童鞋来说简直是巨大福音,对于新童鞋来说也是一个新潮的玩具。

    3 条回复    2017-04-15 12:15:20 +08:00
    HaEx
        1
    HaEx  
       2017-04-15 03:13:44 +08:00
    tnpm -> cnpm ,同时后面的链接仅供内部访问
    binux
        2
    binux  
       2017-04-15 04:19:02 +08:00 via Android
    其实并不是什么新鲜玩意, chrome webview 的 headless 通过 electron 很早就实现了。

    这东西直接用还太早,生产中还需要一层封装。
    而且 debugger API 可靠性还没准,但是就 chrome 来说,分分钟搞死一个标签页的方法不计其数,那时候 debugger 是否还活着真不好说。最后还是需要进程级别的控制的
    iyaozhen
        3
    iyaozhen  
       2017-04-15 12:15:20 +08:00 via Android
    额,还是不太明白,这个优势在哪儿了?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2717 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 09:34 · PVG 17:34 · LAX 01:34 · JFK 04:34
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.