用代码操作浏览器的方法: Puppeteer/Chromedp/Selenium

Tags: spider 

本篇目录

说明

几种用代码操作浏览器的方法,基于 CDP 协议的 Puppeteer 和 Chromedp 只能操作 Chrome 系列的浏览器,Selenium 可以驱动大部分主流浏览器。

Chrome DevTools Protocol (CDP协议)

Chrome DevTools Protocol 是 Chromium 提供的调试协议协议,可以通过该协议控制 Chrome 或 Chromium 浏览器。Contributing to Chrome DevTools Protocol 介绍了协议的整体轮廓。

CDP 协议中包含 clienttargetsessionhandler 四个概念。 client 是要发起浏览器操作的客户端,target 是可以被操作的浏览器内实体(例如 page、service worker),client 和 target 建立链接后得到 session,session 上挂载有执行动作 handler。

CDP 将支持操作划分成 dom、debugger、network 等多个 domain,每个 domain 有命令、事件和定义组成,Chrome DevTools Protocol 左下脚边栏列出了 CDP 的所有 domain,譬如 Target DomainPage Domain

CDP Domains

CDP 已经有多种语言的 library,java/python/go/ruby/js等等:Protocol Driver Libraries

启动 Chrome DevTools Server

在命令行执行 Chrome 命令,指定参数 –remote-debugging-port=0 时,chrome 开启一个 websocket 接口接收 CDP 指令:

# 执行之前先把 chrome 浏览器关闭,关闭所有页面
$ /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome  --remote-debugging-port=0

DevTools listening on ws://127.0.0.1:60076/devtools/browser/6c6433c6-f7fe-417e-8716-4ee5cf633666

建立 websocket 链接后就可以发送 CDP 执行,javascript 示意代码如下:

//获取支持的 Target :
// Get list of all targets and find a "page" target.
const targetsResponse = await SEND(ws, {
  id: 1,
  method: 'Target.getTargets',
});

// 绑定 PageTarget,建立 Session:
const pageTarget = targetsResponse.result.targetInfos.find(info => info.type === 'page');
// Attach to the page target.
const sessionId = (await SEND(ws, {
  id: 2,
  method: 'Target.attachToTarget',
  params: {
    targetId: pageTarget.targetId,
    flatten: true,
  },
})).result.sessionId;


// 通过指定 SessionId 向绑定的 Target 发起操作:
// Navigate the page using the session.
await SEND(ws, {
  sessionId,
  id: 1, // Note that IDs are independent between sessions.
  method: 'Page.navigate',
  params: {
    url: 'https://pptr.dev',
  },
});

发出的指令需要有一个在 Session 内唯一的 ID。

HTTP Endpoint

chrome 还提供了少量的 http 接口,指定非零端口,然后用另一个浏览器打开对应地址即可:

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

HTTP Endpoints 介绍了这些 http 接口的情况,注意这些 http 接口使用的不是 CDP 协议。

浏览器内查看 CDP 通信

在开发者工具的 Setting 面板中,勾选 Protocol monitor,即可查看 CDP 通信过程:

open proto monitor

Puppeteer 操作 Chrome

Puppeteer 是 Chrome DevTools team 团队维护的一个基于 CDP 的 api 库,如果没有需要特别考虑的因素,建议直接用 puppeteer。

Stable vs Experimental methods

The Chrome DevTools team maintains Puppeteer as a reliable high-level API to control a browser. Internally, Puppeteer does use experimental CDP methods, but the team makes sure to update the library as the underlying protocol changes.

puppeteer 已经了 python、rust、net、ruby 等语言的实现:other languages port

Chomedp 操作 Chrome

Chromedp 是一套实现 CDP 协议的 Go Pacakge,主要由 cdproto 和 chromdp 两部分组成:cdproto 和 CDP 协议一一对应,包含每个 domain 中的 target 和函数定义,详情见 cdproto docchromedp 用于启动浏览器、发送 CDP 指令等,详情见 chromedp doc

package main

import (
    "context"
    "fmt"
    "log"
    "reflect"
    "time"

    "github.com/chromedp/cdproto/network"
    "github.com/chromedp/cdproto/page"

    "github.com/chromedp/chromedp"
)

func main() {
    // 手动设置浏览器参数
    opts := []chromedp.ExecAllocatorOption{
        chromedp.DisableGPU,
    }
    allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
    defer cancel()
    taskCtx, cancel := chromedp.NewContext(allocCtx)
    defer cancel()

    ////  默认无头模式
    //taskCtx, cancel := chromedp.NewContext(context.Background())
    //defer cancel()

    var idSuValue string
    var idKwValue string = "lijiaocn"

    // ensure that the browser process is started
    if err := chromedp.Run(taskCtx); err != nil {
        panic(err)
    }

    chromedp.ListenBrowser(taskCtx, browerEvent)
    chromedp.ListenTarget(taskCtx, targetEvent)

    if err := chromedp.Run(taskCtx, tasks(idKwValue, &idSuValue)); err != nil {
        log.Fatal(err)
    }

    fmt.Printf("idSuValue: %s\n", idSuValue)
    time.Sleep(30 * time.Second)
}

func browerEvent(ev interface{}) {
    fmt.Printf("Receive browerEvent TypeName:%v\n", reflect.TypeOf(ev).String())
}

func targetEvent(ev interface{}) {
    fmt.Printf("Receive targetEvent TypeName:%v\n", reflect.TypeOf(ev).String())
}

func tasks(idKwValue string, idSuValue *string) chromedp.Tasks {
    return chromedp.Tasks{
        page.Enable(),    // 开启 page 事件通知
        network.Enable(), // 开启 network 事件通知
        chromedp.Navigate("https://www.baidu.com"),
        chromedp.Value("su", idSuValue, chromedp.ByID),    //页面元素读取
        chromedp.SendKeys("kw", idKwValue, chromedp.ByID), //页面文本框输入
        chromedp.Sleep(2 * time.Second),
        chromedp.Click("su", chromedp.ByID), //页面元素点击
        chromedp.Sleep(10 * time.Second),
    }
}

Selenium 的 Python SDK

安装 Python 包:

pip install selenium

Install browser drivers 给出的链接中下载某一版本的 ChromeDriver。

打开网页并模拟操作

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

if __name__ == '__main__':
    driver = webdriver.Chrome('/Users/Work/Bin/selenuim/chromedriver')
    driver.get('https://www.baidu.com')
    driver.implicitly_wait(1)
    print("title: %s" % driver.title)
    print("cookies: %s" % driver.get_cookies())

    searchBoxInput = driver.find_element(By.ID, "kw")
    searchBoxButton = driver.find_element(By.ID, "su")

    searchBoxInput.send_keys("lijiaocn.com")
    time.sleep(1)

    searchBoxButton.click()

    searchBoxInput = driver.find_element(By.ID, "kw")
    print("value: %s" % searchBoxInput.get_attribute("value"))

    time.sleep(10)

    driver.quit()

执行网页中的js函数

import time
from selenium import webdriver

if __name__ == '__main__':
    driver = webdriver.Chrome('/Users/Work/Bin/selenuim/chromedriver')
    driver.get('file:///tmp/demo.html')
    driver.implicitly_wait(1)
    # 执行页面 js 代码并返回结果
    result=driver.execute_script('return window.getPageInfo()')
    print("%s" % result)

    time.sleep(10)

    driver.quit()

网页截图

import time
from selenium import webdriver
from selenium.webdriver.common.by import By

if __name__ == '__main__':
    driver = webdriver.Chrome('/Users/Work/Bin/selenuim/chromedriver')
    driver.implicitly_wait(1)
    driver.set_window_size(1920, 1080)
    driver.get('file:///tmp/demo.html')

    succ = driver.save_screenshot("/tmp/window.png")
    if not succ:
        print("fail")

    succ = driver.find_element(By.ID, 'snapshot_wrapper').screenshot("/tmp/body.png")
    if not succ:
        print("fail")

    time.sleep(10)

    driver.quit()

参考

  1. 李佶澳的博客
  2. Selenium overview
  3. Write your first Selenium script
  4. Install a Selenium library
  5. Install browser drivers
  6. html - How are pixels defined?
  7. Chrome DevTools Protocol
  8. Selenium with Python
  9. Contributing to Chrome DevTools Protocol
  10. Puppeteer
  11. HTTP Endpoints
  12. Target Domain
  13. Page Domain
  14. Chromedp
  15. cdproto doc
  16. chromedp doc

推荐阅读

Copyright @2011-2019 All rights reserved. 转载请添加原文连接,合作请加微信lijiaocn或者发送邮件: [email protected],备注网站合作

友情链接:  系统软件  程序语言  运营经验  水库文集  网络课程  微信网文  发现知识星球