V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
smallgoogle
V2EX  ›  Python

我猜你们 selenium 使用 ActionChains 都非常脑袋疼吧?

  •  
  •   smallgoogle · 2020-07-01 15:23:40 +08:00 · 2510 次点击
    这是一个创建于 1393 天前的主题,其中的信息可能已经有所发展或是发生改变。

    对不起了,我是标题党了

    事情是酱紫的,我在使用 selenium 的鼠标事件,用了 ActionChains 这个库; 该说不说,我有些时候不能理解 ActionChains 是咋回事; 比如:

    from selenium.webdriver.common.action_chains import ActionChains
    actions = ActionChains(browser)
    actions.move_by_offset(10, 20).click().perform()
    

    比如这样的代码,就是鼠标的链式操作嘛,移动到指定 xy 坐标,然后点击,然后执行链式操作; 那么问题来了,这个鼠标的移动啊,它不能回归到默认的 0,0 的位置啊。 就是我再次执行 move_by_offset 的时候,xy 坐标是根据上次鼠标所在的位置继续叠加的,这分分钟就超出浏览器范围了; 根据各大网资料,得知了这个链式操作底层实际上是 self._actions.append
    就是他的链式操作就是把所有要执行的东西依次添加到一个数组里,然后执行;

    那么问题来了,如何可以清空鼠标位置到 0 的位置呢?我不想每次都在上次移动的位置叠加执行。 这个时候有人就说了,你干嘛不试试 move_by_offset(-10, -20) 这样让他回归呢? 嗯,因为我不知道 xy 坐标,所以没办法回归原来,我就是想让他每次鼠标在左上角,就那么难吗?

    大佬们,你们遇到了吗?有解决办法嘛?

    13 条回复    2020-07-02 11:37:16 +08:00
    gdtdpt
        1
    gdtdpt  
       2020-07-01 15:41:11 +08:00
    reset_actions
    nullboy
        2
    nullboy  
       2020-07-01 15:46:48 +08:00
    这玩意很少用,几乎没用过
    smallgoogle
        3
    smallgoogle  
    OP
       2020-07-01 16:00:59 +08:00
    @gdtdpt 试过这个,好像并不能让鼠标回归到原来的 xy 坐标,这只是能让他不再执行一次之前数组里已有的。
    gdtdpt
        4
    gdtdpt  
       2020-07-01 17:36:02 +08:00
    @smallgoogle 用 move_to_element_with_offset 吧,好像没什么好的方法,要不就每次 preform 后再 move_to_element 移动到某个元素来重置鼠标位置吧,不知道能不能 move_to_element('body')。
    jiejiss
        5
    jiejiss  
       2020-07-01 22:28:24 +08:00   ❤️ 1
    TL;DR 用 move_by_offset 的话只能通过先移过去再移回来的操作来复位。

    看了看 selenium 方法源码
    https://www.selenium.dev/selenium/docs/api/py/_modules/selenium/webdriver/common/action_chains.html#ActionChains.move_by_offset

    多追一层,发现是 webdriver 层的实现,直接 POST 到 remote 去了
    https://github.com/SeleniumHQ/selenium/blob/trunk/py/selenium/webdriver/remote/remote_connection.py#L279

    看了看别的 webdriver implement,发现都是用的 xoffset 和 yoffset,没有直接让指定 x 和 y 的
    https://webdriver.io/docs/api/element/moveTo.html
    https://api.flutter.dev/flutter/webdriver.sync_io/Mouse/moveTo.html

    遂怀疑是标准定义的问题,于是去翻了 w3c 标准,发现 w3c 根本就没有在 webdriver 标准里给出 moveTo 的定义,只有
    https://www.w3.org/TR/webdriver/#h-note-28 这里提了一嘴。顺着找下去就发现了
    https://www.w3.org/TR/cssom-view-1/#dom-window-moveto
    但显然这个 moveTo 不是 webdriver 规范的一部分,而且它是基于页面左上角的绝对坐标,下面的 moveBy 才是基于 offset 的相对坐标。

    百思不得其解,联想到别的 webdriver implement 和 Selenium 的一致性,就又去看了 Google ChromeDriver 的文档:
    https://chromium.googlesource.com/chromium/src/+/master/docs/chromedriver_status.md
    里面根本就没提 moveTo,和 w3c 标准保持了一致,所以 moveTo 可能是类似于一个非标准的灰色 API

    于是又去读 ChromeDriver 源码,发现了这个:
    https://github.com/bayandin/chromedriver/blob/master/server/http_handler.cc#L574
    https://github.com/bayandin/chromedriver/blob/master/window_commands.cc#L1029

    所以你看最后的方法实现,也就是 window_commands.cc 1045 行附近,先判断有没有指定 element ( Selenium 吃掉了这个配置,所以无法用 move_by_offset 指定 element ),如果没有就 location = session->mouse_position; location.Offset(x_offset, y_offset); 并在最后又 session->mouse_position = location;

    考虑到 Offset 的实现是
    void WebPoint::Offset(int x_, int y_) {
    x += x_;
    y += y_;
    }

    总结:我觉得没啥整出 dirty hack 的希望,老老实实吃这坨 bad design 造出来的翔吧。
    ClericPy
        6
    ClericPy  
       2020-07-01 23:08:49 +08:00
    如果是想直接移动鼠标到指定位置的话, chrome devtools protocol 里有 dispatch 鼠标移动和点击的事件, 不过有点好奇 selenium 里的操作鼠标移动时候是和 cdp 一样发事件, 还是从驱动层面控制鼠标动的?
    jiejiss
        7
    jiejiss  
       2020-07-01 23:19:15 +08:00
    @ClericPy #6 Selenium 是一层封装 用 HTTP 和 browser 通信,控制鼠标移动是 browser 的事情,和 Selenium 无关
    ClericPy
        8
    ClericPy  
       2020-07-01 23:22:35 +08:00
    @jiejiss 好奇的是它操作 webdriver 的时候, 鼠标行为是事件, 还是其他操作, 因为之前用 cdp 发鼠标事件被反爬识别到了, 用 pyautogui 操作鼠标就没被识别到
    freakxx
        9
    freakxx  
       2020-07-01 23:28:35 +08:00
    要不就 继承 ActionChains
    定义多 2 个 变量

    _x = 0
    _y = 0

    复写下 move_by_offset, 叠加 self._x self._y

    再写个

    def move_to_start()

    x = - _x
    y = - _y
    jiejiss
        10
    jiejiss  
       2020-07-01 23:29:07 +08:00
    @ClericPy #8 Status status = web_view->DispatchMouseEvents(
    events, session->GetCurrentFrameId(), false);

    是 dispatch
    ClericPy
        11
    ClericPy  
       2020-07-01 23:32:50 +08:00
    @jiejiss ok, 那我实在没有用它的理由了... 多谢
    smallgoogle
        12
    smallgoogle  
    OP
       2020-07-02 10:06:34 +08:00
    @jiejiss 我也追到了你看的这些资料,发现真的是一坨。。。 我以为大家有什么骚操作。
    smallgoogle
        13
    smallgoogle  
    OP
       2020-07-02 11:37:16 +08:00
    @freakxx 你这算曲线救国了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1217 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 18:15 · PVG 02:15 · LAX 11:15 · JFK 14:15
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.