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

2020-07-01 15:23:40 +08:00
 smallgoogle

对不起了,我是标题党了

事情是酱紫的,我在使用 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 坐标,所以没办法回归原来,我就是想让他每次鼠标在左上角,就那么难吗?

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

2647 次点击
所在节点    Python
13 条回复
gdtdpt
2020-07-01 15:41:11 +08:00
reset_actions
nullboy
2020-07-01 15:46:48 +08:00
这玩意很少用,几乎没用过
smallgoogle
2020-07-01 16:00:59 +08:00
@gdtdpt 试过这个,好像并不能让鼠标回归到原来的 xy 坐标,这只是能让他不再执行一次之前数组里已有的。
gdtdpt
2020-07-01 17:36:02 +08:00
@smallgoogle 用 move_to_element_with_offset 吧,好像没什么好的方法,要不就每次 preform 后再 move_to_element 移动到某个元素来重置鼠标位置吧,不知道能不能 move_to_element('body')。
jiejiss
2020-07-01 22:28:24 +08:00
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
2020-07-01 23:08:49 +08:00
如果是想直接移动鼠标到指定位置的话, chrome devtools protocol 里有 dispatch 鼠标移动和点击的事件, 不过有点好奇 selenium 里的操作鼠标移动时候是和 cdp 一样发事件, 还是从驱动层面控制鼠标动的?
jiejiss
2020-07-01 23:19:15 +08:00
@ClericPy #6 Selenium 是一层封装 用 HTTP 和 browser 通信,控制鼠标移动是 browser 的事情,和 Selenium 无关
ClericPy
2020-07-01 23:22:35 +08:00
@jiejiss 好奇的是它操作 webdriver 的时候, 鼠标行为是事件, 还是其他操作, 因为之前用 cdp 发鼠标事件被反爬识别到了, 用 pyautogui 操作鼠标就没被识别到
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
2020-07-01 23:29:07 +08:00
@ClericPy #8 Status status = web_view->DispatchMouseEvents(
events, session->GetCurrentFrameId(), false);

是 dispatch
ClericPy
2020-07-01 23:32:50 +08:00
@jiejiss ok, 那我实在没有用它的理由了... 多谢
smallgoogle
2020-07-02 10:06:34 +08:00
@jiejiss 我也追到了你看的这些资料,发现真的是一坨。。。 我以为大家有什么骚操作。
smallgoogle
2020-07-02 11:37:16 +08:00
@freakxx 你这算曲线救国了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/686263

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX