ClericPy / ichrome

Chrome controller for Humans, based on Chrome Devtools Protocol(CDP) and python3.7+.
https://pypi.org/project/ichrome/
MIT License
227 stars 29 forks source link

[求助]如何监听整个ChromeDaemon的所有请求 #142

Closed megachweng closed 10 months ago

megachweng commented 10 months ago

ichrome==3.2.0 查阅了文档,似乎AsyncTab.iter_fetch只对单个Tab做请求监听。 实际情况在是Chrome打开之后,如果用户新建一个tab,在新tab中的请求就监听不到了。

目前在做一个简单的GUI程序(PyQt)点击按钮打开Chrome浏览器,随后在这个浏览器中用户所有的XHR请求都记录并显示在GUI中,该如何监听整个Chrome呢

CleanShot 2023-10-10 at 21 33 07@2x
ClericPy commented 10 months ago

这个 GUI 有点意思

我说的稍微复杂点, 直接说依赖的几个东西

  1. 首先, 监听新建 Tab 的事件, 就可以拿到所有新建的 Tab. targetCreated 事件, 当然得先开启 Target.setDiscoverTargets, 然后 Target.targetDestroyed 事件代表 Tab 被关闭了.
  2. 然后把这个 Tab 丢到一个 asyncio.Task 里面异步监听它, 直到它被断开(也就是closed).
  3. 这个事件在默认开启了 flatten 模式以后, 通过 AsyncChrome 对象的 browser 属性就可以拿到浏览器的全局 Target(也是个 Tab 对象), 用它来接收 targetCreated 事件. 后面 asyncio.Task 的操作就跟操作单个 Tab 的 fetch 一样了

监听用户新建 Tab 例子

import asyncio

import traceback
from ichrome import AsyncChrome, AsyncTab

doings = {}

async def watchdog_callback(browser: AsyncTab, data: dict):
    # 这里会看到很多 Network 事件
    print(data)

async def daemon(tab_id, chrome: AsyncChrome):
    try:
        async with chrome.connect_tab(tab_id) as tab:
            # 假设我这里把每一个新建的 tab 都跳转到 bing.com
            await tab.goto('http://bing.com')
            # 然后我这里打印请求事件
            await tab.enable('Network')
            tab.default_recv_callback.append(watchdog_callback)
            await asyncio.sleep(1000)
    except Exception:
        traceback.print_exc()

async def callback(browser: AsyncTab, data: dict):
    # print(browser, data, flush=True)
    if data.get('method') == 'Target.targetCreated':
        tab_id = data['params']['targetInfo']['targetId']
        if tab_id not in doings:
            print('[标签页新建]:',
                  tab_id,
                  data['params']['targetInfo']['title'],
                  data['params']['targetInfo']['url'],
                  flush=True)
            task = asyncio.create_task(daemon(tab_id, chrome=browser.chrome))
            doings[tab_id] = task
        # 这里可以创建一个守护协程监听这个 Tab 做点事情, 比如 fetch iter
    elif data.get('method') == 'Target.targetDestroyed':
        tab_id = data['params']['targetId']
        print('[标签页关闭]:', tab_id, flush=True)
        # 这里可以把之前监听的 Task 给 cancel 掉
        task = doings.pop(tab_id, None)
        if task:
            task.cancel()
            await asyncio.sleep(0)

async def main():
    async with AsyncChrome(port=9222) as chrome:
        await chrome.browser.send('Target.setDiscoverTargets', discover=True)
        chrome.browser.default_recv_callback.append(callback)
        # print(await chrome.browser.send('Target.getTargets'))
        # await chrome.browser.wait_event('Target.targetCreated')
        await asyncio.sleep(1000)

if __name__ == "__main__":
    asyncio.run(main())

PS: 这里如果不需要篡改流量或者拦截流量, 只是要看看请求的话, 可以不使用 fetch, 直接先给 Tab 开启Network.enable, 会收到 Network.requestWillBeSent和Network.responseReceived 相关的事件, 然后要注意的是 responseReceived 事件里没有 body, 得通过 Network.getResponseBody 来获取, 我记得我也包装了这个. 如果这样的话, 直接通过 default_recv_callback 属性塞个事件监听的异步回调就可以. 这个丰俭由人

megachweng commented 10 months ago

非常感谢详细的解答!