Web自动化之Headless Chrome编码实战

API 概览 && 编码Tips

文档地址

常用API

  • Network 网络请求、Cookie、缓存、证书等相关内容
  • Page 页面的加载、资源内容、弹层、截图、打印等相关内容
  • DOM 文档DOM的获取、修改、删除、查询等相关内容
  • Runtime JavaScript代码的执行,这里面我们可以搞事情~~

编码Tips

  • 我们这里不会直接调用Websocket相关的内容来调用chrome的调试命令,而是用chrome-remote-interface 这个封装的库来做,它是基于Promise风格的
  • 每一个功能块成为一个单独的domain,像Network,Page,DOM等都是不同的domain
  • 几乎每一个个头大的domain都有enable方法,需要先调用这个方法启用之后再使用
  • 各个domain的接口方法参数都是第一个对象或者说一个Map,不用考虑参数的位置了
  • 各个domain的接口返回值也是一个对象,取对应的key就行
  • 参数值和返回值经常是meta信息,经常是各种对象的id信息,而不是具体的对象内容(这里可能需要切一下风格)

编码实例

首先做一个简单的封装,准备API的执行环境,具体可参考前一篇关于工具库的。

const chromeLauncher = require('chrome-launcher');
const chromeRemoteInterface = require('chrome-remote-interface');

const prepareAPI = (config = {}) => {
    const {host = 'localhost', port = 9222, autoSelectChrome = true, headless = true} = config;
    const wrapperEntry = chromeLauncher.launch({
        host,
        port,
        autoSelectChrome,
        additionalFlags: [
            '--disable-gpu',
            headless ? '--headless' : ''
        ]
    }).then(chromeInstance => {
        const remoteInterface = chromeRemoteInterface(config).then(chromeAPI => chromeAPI).catch(err => {
            throw err;
        });
        return Promise.all([chromeInstance, remoteInterface])
    }).catch(err => {
        throw err
    });

    return wrapperEntry
};

打开百度,获取页面性能数据,参考 Navigation Timing W3C规范

const wrapper = require('the-wrapper-module');

const performanceParser = (perforceTiming) => {
    let timingGather = {};
    perforceTiming = perforceTiming || {};
    timingGather.redirect = perforceTiming.redirectEnd - perforceTiming.redirectEnd-perforceTiming.redirectStart;
    timingGather.dns = perforceTiming.domainLookupEnd - perforceTiming.domainLookupStart;
    timingGather.tcp = perforceTiming.connectEnd - perforceTiming.connectStart;
    timingGather.request = perforceTiming.responseStart - perforceTiming.requestStart;
    timingGather.response = perforceTiming.responseEnd - perforceTiming.responseStart;
    timingGather.domReady = perforceTiming.domContentLoadedEventStart - perforceTiming.navigationStart;
    timingGather.load = perforceTiming.loadEventStart - perforceTiming.navigationStart;
    return timingGather;
};

const showPerformanceInfo = (performanceInfo) => {
    performanceInfo = performanceInfo || {};
    console.log(`页面重定向耗时:${performanceInfo.redirect}`);
    console.log(`DNS查找耗时:${performanceInfo.dns}`);
    console.log(`TCP连接耗时:${performanceInfo.tcp}`);
    console.log(`请求发送耗时:${performanceInfo.request}`);
    console.log(`响应接收耗时:${performanceInfo.response}`);
    console.log(`DOMReady耗时:${performanceInfo.domReady}`);
    console.log(`页面加载耗时:${performanceInfo.load}`);
};

wrapper.prepareAPI().then(([chromeInstance, remoteInterface]) => {
    const {Runtime,Page} = remoteInterface;

    Page.loadEventFired(() => {
        Runtime.evaluate({
            expression:'window.performance.timing.toJSON()',
            returnByValue:true  //不加这个参数,拿到的是一个对象的meta信息,还需要getProperties
        }).then((resultObj) => {
            let {result,exceptionDetails} = resultObj;
            if(!exceptionDetails){
                showPerformanceInfo(performanceParser(result.value))
            }else{
                throw exceptionDetails;
            }
        });
    });

    Page.enable().then(() => {
        Page.navigate({
            url:'http://www.baidu.com'
        })
    });
});

打开百度 搜索Web自动化 headless chrome,并爬取首屏结果链接

const wrapper = require('the-wrapper-module');
//有this的地方写成箭头函数要注意,这里会有问题
const buttonClick = function () {
    this.click();
};

const setInputValue = () => {
    var input = document.getElementById('kw');
    input.value = 'Web自动化 headless chrome';
};

const parseSearchResult = () => {
    let resultList = [];
    const linkBlocks = document.querySelectorAll('div.result.c-container');
    for (let block of Array.from(linkBlocks)) {
        let targetObj = block.querySelector('h3');
        resultList.push({
            title: targetObj.textContent,
            link: targetObj.querySelector('a').getAttribute('href')
        });
    }
    return resultList;
};


wrapper.prepareAPI({
    // headless: false  //加上这行代码可以查看浏览器的变化
}).then(([chromeInstance, remoteInterface]) => {
    const {Runtime, DOM, Page, Network} = remoteInterface;
    let framePointer;
    Promise.all([Page.enable(), Network.enable(), DOM.enable(),Page.setAutoAttachToCreatedPages({autoAttach:true})]).then(() => {
        Page.domContentEventFired(() => {
            console.log('Page.domContentEventFired')
            Runtime.evaluate({
                expression:`window.location.href`,
                returnByValue:true
            }).then(result => {
                console.log(result)
            })
        });
        Page.frameNavigated(() => {
            console.log('Page.frameNavigated')
            Runtime.evaluate({
                expression:`window.location.href`,
                returnByValue:true
            }).then(result => {
                console.log(result)
            })
        })
        Page.loadEventFired(() => {
            console.log('Page.loadEventFired')
            Runtime.evaluate({
                expression:`window.location.href`,
                returnByValue:true
            }).then(result => {
                console.log(result)
            })
            DOM.getDocument().then(({root}) => {
                //百度首页表单
                DOM.querySelector({
                    nodeId: root.nodeId,
                    selector: '#form'
                }).then(({nodeId}) => {
                    Promise.all([
                        //找到 搜索框填入值
                        DOM.querySelector({
                            nodeId: nodeId,
                            selector: '#kw'
                        }).then((inputNode) => {

                            Runtime.evaluate({
                                // 两种写法
                                // expression:'document.getElementById("kw").value = "Web自动化 headless chrome"',
                                expression: `(${setInputValue})()`
                            });


                            //这段代码不起作用 日狗
                            // DOM.setNodeValue({
                            //     nodeId:inputNode.nodeId,
                            //     value:'Web自动化 headless chrome'
                            // });

                            //上面的代码需求要这么写
                            // DOM.setAttributeValue({
                            //     nodeId:inputNode.nodeId,
                            //     name:'value',
                            //     value:'headless chrome'
                            // });
                        })
                        //找到 提交按钮setInputValue
                        , DOM.querySelector({
                            nodeId,
                            selector: '#su'
                        })
                    ]).then(([inputNode, buttonNode]) => {

                        Runtime.evaluate({
                            expression: 'document.getElementById("kw").value',
                        }).then(({result}) => {
                            console.log(result)
                        });

                        return DOM.resolveNode({
                            nodeId: buttonNode.nodeId
                        }).then(({object}) => {
                            const {objectId} = object;
                            return Runtime.callFunctionOn({
                                objectId,
                                functionDeclaration: `${buttonClick}`
                            })
                        });
                    }).then(() => {
                        setTimeout(() => {
                            Runtime.evaluate({
                                expression: `(${parseSearchResult})()`,
                                returnByValue: true
                            }).then(({result}) => {
                                console.log(result.value)
                                //百度的URL有加密,需要再请求一次拿到真实URL
                            })
                        },3e3)
                    });
                })

            });
        });
        Page.navigate({
            url: 'http://www.baidu.com'
        }).then((frameObj) => {
            framePointer = frameObj
        });
    })

});
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,426评论 25 707
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,567评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 【本我自我超我】 大象和骑象人的心理模型其实是潜意识和自我的博弈。本我总是想要让身体更舒服、更安逸,让身体的欲望得...
    蝎子小猫咪阅读 2,102评论 3 8
  • 年少未尝把吴钩,踏马雄心非无有。 古文哲士旅学游,积沉勃发天下求。 愚生数载校园寄,何曾锻磨心力筹。 今日轻别致牵...
    村客阅读 259评论 0 8