4
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

websocket+chrome devtools protocolでスクリーンショットを取得する

Last updated at Posted at 2020-06-05

やりたいこと

  • プリインストールされたchrome(系ブラウザ)のスクショを取りたい
  • webインスペクタを開くためのURLは分かっている
  • 諸般の事情でseleniumなどの手段が使えない

要は非Androidのテレビデバイスとか、そういう特殊環境で強引にスクショを取る方法です

やったこと

webSocketDebuggerUrlを取得する

webインスペクタを開くためのURLは分かっている

このURLをまず確認する
※webSocketDebuggerUrlは同時に1セッションしか接続できないので、ブラウザでウェブインスペクタを開いている場合は閉じておく

$ curl 192.168.1.10:00000/json    # webインスペクタのURLの末尾に '/json' をつける
# ↓ブラウザを起動していないとき blankのタブが常に控えていることがわかる
[ {
   "description": "",
   "devtoolsFrontendUrl": "/devtools/inspector.html?ws=192.168.1.10:00000/devtools/page/049cc469-5488-4932-ab06-daabe219c5a2",
   "id": "049cc469-5488-4932-ab06-daabe219c5a2",
   "title": "about:blank",
   "type": "page",
   "url": "about:blank",
   "webSocketDebuggerUrl": "ws://192.168.1.10:00000/devtools/page/049cc469-5488-4932-ab06-daabe219c5a2"
} ]

# ↓ブラウザを起動したとき blankタブを押しのけて新しいタブが生成される (多分端末によって挙動違うので都度読み替えてください)
[ {
   "description": "",
   "devtoolsFrontendUrl": "/devtools/inspector.html?ws=192.168.1.10:00000/devtools/page/c4a5735e-dee8-4262-9dca-15058f7a821e",
   "id": "c4a5735e-dee8-4262-9dca-15058f7a821e",
   "title": "https://www.youtube.com",
   "type": "page",
   "url": "https://www.youtube.com",
   "webSocketDebuggerUrl": "ws://192.168.1.10:00000/devtools/page/c4a5735e-dee8-4262-9dca-15058f7a821e"
}, {
   "description": "",
   "devtoolsFrontendUrl": "/devtools/inspector.html?ws=192.168.1.10:00000/devtools/page/4fb84849-f023-4014-ba14-d76bd2d9c4bf",
   "id": "4fb84849-f023-4014-ba14-d76bd2d9c4bf",
   "title": "about:blank",
   "type": "page",
   "url": "about:blank",
   "webSocketDebuggerUrl": "ws://192.168.1.10:00000/devtools/page/4fb84849-f023-4014-ba14-d76bd2d9c4bf"
} ]

キーの説明

  • devtoolsFrontendUrl webインスペクタを開くためのURL デバイスのアドレスと合体させて http://192.168.11.11:10485/devtools/inspector.html?ws= 192.168.1.10:00000/devtools/page/c4a5735e-dee8-4262-9dca-15058f7a821e みたいにすることでアクセスできる
    今回は使わない
  • id セッションごとにランダムに生成される文字列 タブ1つずつに生成される
  • title ウェブページのタイトル
  • type page以外見たことがないので詳細は知らん
  • url 現在開いているページのURL
  • webSocketDebuggerUrl 本命 websocketでchrome devtools protocolを叩くためのURL

というわけでwebSocketDebuggerUrlを取得することができた

pythonで実装

$ pip install websocket-client requests

たまにある事例ではあるが、pipとpython内でパッケージの名前が異なるので一応注意

ws.py
import websocket
import requests
from requests.exceptions import Timeout


DEVICE_ADDRESS = '192.168.1.10:00000'
TIMEOUT = 10.0    # こういう端末は得てしてリソースカツカツなのでタイムアウト大目に見る

try:
    response = requests.get(f'http://{DEVICE_ADDRESS}/json', timeout=TIMEOUT)
    ws_url = response.json()[0]['webSocketDebuggerUrl']
    print('ws_url: ', ws_url)

except requests.exceptions.Timeout as err:
    print(err)
$ python ws.py
ws_url:  ws://192.168.1.10:00000/devtools/page/c4a5735e-dee8-4262-9dca-15058f7a821e

websocketで接続する

こんなかんじでやれそう

ws.py
import websocket
import requests
from requests.exceptions import Timeout

import json


DEVICE_ADDRESS = '192.168.1.10:00000'
TIMEOUT = 10.0    # こういう端末は得てしてリソースカツカツなのでタイムアウト大目に見る

def get_ws_url():
    try:
        response = requests.get(f'http://{DEVICE_ADDRESS}/json', timeout=TIMEOUT)
        ws_url = response.json()[0]['webSocketDebuggerUrl']
        print('ws_url: ', ws_url)

    except requests.exceptions.Timeout as err:
        print(err)


def send(message):
    ws_url = get_ws_url(DEVICE_ADDRESS)

    ws = websocket.create_connection(ws_url)        # websocketでの接続を確立する
    ws.send(json.dumps(message))                    # 何らかのメッセージを送りつける
    receive = ws.recv()                             # 何らかのメッセージが帰ってくる
    ws.close()                                      # 接続を終了する

    return receive

websocketで端末に送りつけるための書式を調べる

chrome devtoolsは単なるwebインスペクタではなく、外部から操作できるプロトコルが存在するらしい
それがコレ
chrome devtools protocol

The Chrome DevTools Protocol allows for tools to instrument, inspect, debug and profile Chromium, Chrome and other Blink-based browsers. Many existing projects currently use the protocol. The Chrome DevTools uses this protocol and the team maintains its API.

deeplによる翻訳:

Chrome DevTools Protocolは、Chromium、Chrome、その他のBlinkベースのブラウザの計測、検査、デバッグ、プロファイルを行うためのツールを提供します。現在、多くの既存のプロジェクトがこのプロトコルを使用しています。Chrome DevToolsはこのプロトコルを使用しており、チームはそのAPIを維持しています。

なるほどね
ちなみに端末内のchromeバージョンは要確認
chrome://version に端末でアクセスし、対応するchrome devtools protocolのバージョンを確認する
(こちらの環境はchrome65だったので stable RC(1.3)というバージョンまでの機能が使えました)

テンプレはこんなかんじ

message = {
    'id': 1,    # どういう意味があるのか分かってないのでとりあえず数字入れている 固定値でもなんも怒られないので謎
    'method':'chrome devtools protocolのメソッド',
    'params': {'パラメータのkey': 'パラメータのvalue'}
}
send(message)

たとえばjavascriptを実行するやつ
https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#method-evaluate

def evaluate_js(script):
    message = {
        "id": 1,
        "method": "Runtime.evaluate",        # Runtime.evaluate を指定
        "params": {"expression": script}     # パラメータexpressionにjavascriptを直接代入する
    }
    send(message)

↑コレの引数に location.reload()などを文字列として渡せば動く

使用頻度の高そうなやつを追加するとこんなかんじ

ws.py
import websocket
import requests
import json

from requests.exceptions.Timeout

import base64   # エンコードされた画像の処理

DEVICE_ADDRESS = '192.168.1.10:00000'
TIMEOUT = 10.0    # こういう端末は得てしてリソースカツカツなのでタイムアウト大目に見る

def get_ws_url():
    try:
        response = requests.get(f'http://{DEVICE_ADDRESS}/json', timeout=TIMEOUT)
        ws_url = response.json()[0]['webSocketDebuggerUrl']
        print('ws_url: ', ws_url)

    except requests.exceptions.Timeout as err:
        print(err)


def send(message):
    ws_url = get_ws_url(DEVICE_ADDRESS)

    ws = websocket.create_connection(ws_url)        # websocketでの接続を確立する
    ws.send(json.dumps(message))                    # 何らかのメッセージを送りつける
    receive = ws.recv()                             # 何らかのメッセージが帰ってくる
    ws.close()                                      # 接続を終了する

    return receive

def reload():
    """
    リロード
    """
    message = {
        'id': 1,
        'method': 'Runtime.evaluate',
        'params': {'expression': 'location.reload()'}
    }
    send(message)

def clear():
    """
    localStorageの消去
    """
    message = {
        'id': 1,
        'method': 'Runtime.evaluate',
        'params': {'expression': 'localStorage.clear()'}
    }
    send(message)

def evaluate_js(script):
    """
    javascriptの実行
    """
    message = {
        'id': 1,
        'method': 'Runtime.evaluate',
        'params': {'expression': script}
    }
    send(message)

def transition(url):
    """
    絶対URLで直遷移
    """
    message = {
        'id': 1,
        'method': 'Runtime.evaluate',
        'params': {'expression': f'location.href="{url}"'}
    }
    send(message)

def get_screenshot(image_path):
    """
    スクショ取得
    """
    message = {
        'id': 1,
        'method': 'Page.captureScreenshot',
        'params': {'format': 'png'}
    }
    receive = send(message)

    # レスポンスにbase64でエンコードされた画像が帰ってくる
    image_base64 = json.loads(receive)['result']['data']
    image = base64.b64decode(image_base64)
    with open(image_path, 'bw') as f:
        f.write(image)

def get_url():
    """
    現在のURL取得
    """
    response = requests.get(f'http://{DEVICE_ADDRESS}/json')
    url = response.json()[0]['url']
    return url

def get_element_text(xpath):
    """
    要素のテキストを取得
    """
    evaluate = f'document.evaluate("{xpath}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent;'

    message = {
        'id': 1,
        'method': 'Runtime.evaluate',
        'params': {'expression': evaluate}
    }

    receive = send(message)

    print('ws get_element: receive', receive)
    try:
        text_element = json.loads(receive)['result']['result']['value']
    except KeyError:
        text_element = ''

    return text_element
# 対話モード
>>>import ws
>>>
>>>ws.get_url()
>>>https://www.youtube.com
>>>
>>>ws.reload()    # 端末実機のページが更新される
>>>
>>>ws.get_screenshot('test.png')    # 端末のSSがカレントディレクトリに保存される

まとめ

  • websocket経由でchromeに接続し、スクショを保存することができた
  • javascriptの実行やログの取得など、webインスペクタでサポートされている一通りの機能が呼び出せることが分かった
  • ドキュメントを見る感じパフォーマンス計測なども一応できるっぽい
4
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?