基础知识:selenium元素定位和读取

发表于:2022-7-04 09:35

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:悠悠i    来源:博客园

  查找元素
  selenium提供了一系列api方便获取chrome中的元素,这些API都返回WebElement对象或其列表,如:
  find_element_by_id(id): 查找匹配id的第一个元素。
  find_element_by_class_name(): 查找匹配class的第一个元素。
  find_elements_by_xpath(): 查找匹配xpath的所有元素。
  find_elements_by_css_selector(): 查找匹配css选择器的所有元素。
  其实可以看WebDriver类里面的实现源码,其核心实现都是调用两个基本函数:
  find_element(self, by=By.ID, value=None): 查找匹配策略的第一个元素。
  find_elements(self, by=By.ID, value=None): 查找匹配策略的所有元素。
  其中by参数可以是ID, CSS_SELECTOR, CLASS_NAME, XPATH等。下面举几个简单的例子:
  ·通过xpath查询包含文本登录的第一个元素: find_element_by_xpath("//*[contains(text(),'登录')]")
  · 查询包含类名refresh的所有元素: find_elements_by_class_name('refresh')
  · 查询table表格的第二行: find_element_by_css_selector('table tbody > tr:nth(2)')
  dom元素交互
  上面介绍的元素查找结果WebElement对象,常用的api有:
  element.text: 返回元素的文本内容(包括所有后代节点的内容),注意如果元素display=none则返回为空字符串。
  element.screenshot_as_png: 元素的截图。
  element.send_keys("input"): 元素输入框输入input字符串。
  element.get_attribute('data-v'): 获取data-v名称属性值,除了自定义节点属性,还可以获取如textContent等属性。
  element.is_displayed(): 判断元素是否用户可见。
  element.clear(): 清除元素文本。
  element.click(): 点击元素,如果元素不可点击会抛出ElementNotInteractableException异常。
  element.submit(): 模拟表单提交。
  查找元素失败处理
  如果找不到指定元素,则会抛出NoSuchElementException异常,而且需要注意,display=none的元素是可以获取到的,凡是在dom节点中的元素都可以获取到。
  而且实际使用的时候要注意一些js代码动态创建的元素,可能需要轮询获取或者监控。
  一个检查是否存在指定元素的方法如下:
  def check_element_exists(xpath):
      try:
          driver.find_element_by_xpath(xpath)
      except NoSuchElementException:
          return False
      return True
  selenium交互控制
  ActionChains动作链
  webdriver通过ActionChains对象来模拟用户操作,该对象表示一个动作链路队列,所有操作会依次进入队列但不会立即执行,直到调用perform()方法时才会执行。其常用方法如下:
  ·click(on_element=None): 单击鼠标左键
  · click_and_hold(on_element=None): 点击鼠标左键,不松开
  · context_click(on_element=None): 点击鼠标右键
  · double_click(on_element=None): 双击鼠标左键
  · send_keys(*keys_to_send): 发送某个键到当前焦点的元素
  · send_keys_to_element(element, *keys_to_send): 发送某个键到指定元素
  · key_down(value, element=None): 按下某个键盘上的键
  · key_up(value, element=None): 松开某个键
  · drag_and_drop(source, target): 拖拽到某个元素然后松开
  · drag_and_drop_by_offset(source, xoffset, yoffset): 拖拽到某个坐标然后松开
  · move_by_offset(xoffset, yoffset): 鼠标从当前位置移动到某个坐标
  · move_to_element(to_element): 鼠标移动到某个元素
  · move_to_element_with_offset(to_element, xoffset, yoffset): 移动到距某个元素(左上角坐标)多少距离的位置
  · perform(): 执行链中的所有动作
  · release(on_element=None): 在某个元素位置松开鼠标左键
  模拟鼠标事件
  下面代码模拟鼠标移动,点击,拖拽等操作,注意操作时需要等待一定时间,否则页面还来不及渲染。
  from time import sleep
  from selenium import webdriver
  # 引入 ActionChains 类
  from selenium.webdriver.common.action_chains import ActionChains
  driver = webdriver.Chrome()
  driver.get("https://www.baidu.cn")
  action_chains = ActionChains(driver)
  target = driver.find_element_by_link_text("搜索")
  # 移动鼠标到指定元素然后点击
  action_chains.move_to_element(target).click(target).perform()
  time.sleep(2)
  # 也可以直接调用元素的点击方法
  target.click()
  time.sleep(2)
  # 鼠标移动到(10, 50)坐标处
  action_chains.move_by_offset(10, 50).perform()
  time.sleep(2)
  # 鼠标移动到距离元素target(10, 50)处
  action_chains.move_to_element_with_offset(target, 10, 50).perform()
  time.sleep(2)
  # 鼠标拖拽,将一个元素拖动到另一个元素
  dragger = driver.find_element_by_id('dragger')
  action.drag_and_drop(dragger, target).perform()
  time.sleep(2)
  # 也可以使用点击 -> 移动来实现拖拽
  action.click_and_hold(dragger).release(target).perform()
  time.sleep(2)
  action.click_and_hold(dragger).move_to_element(target).release().perform()
  模拟键盘输入事件
  通过send_keys模拟键盘事件,常用有:
  ·send_keys(Keys.BACK_SPACE): 删除键(BackSpace)
  · send_keys(Keys.SPACE): 空格键(Space)
  · send_keys(Keys.TAB): 制表键(Tab)
  · send_keys(Keys.ESCAPE): 回退键(Esc)
  · send_keys(Keys.ENTER): 回车键(Enter)
  · send_keys(Keys.F1): 键盘 F1
  · send_keys(Keys.CONTROL,'a'): 全选(Ctrl+A)
  · send_keys(Keys.CONTROL,'c'): 复制(Ctrl+C)
  · send_keys(Keys.CONTROL,'x'): 剪切(Ctrl+X)
  · send_keys(Keys.CONTROL,'v'): 粘贴(Ctrl+V)
  示例:定位到输入框,然后输入内容:
  # 输入框输入内容
  driver.find_element_by_id("kw").send_keys("uusamaa")
  # 模拟回车删除多输入的一个字符a
  driver.find_element_by_id("kw").send_keys(Keys.BACK_SPACE)
  警告框处理
  用于处理调用alert弹出的警告框。
  ·driver.switch_to_alert(): 切换到警告框
  · text:返回alert/confirm/prompt中的文字信息,比如js调用alert('failed')则会获取failed字符串
  · accept():接受现有警告框
  · dismiss():关闭现有警告框
  · send_keys(keysToSend):将文本发送至警告框
  selenium浏览器控制
  基本常用api
  下面列出一些非常实用的浏览器控制api:
  · driver.current_url: 获取当前活动窗口的url
  · driver.switch_to_window("windowName"): 移动到指定的标签窗口
  · driver.switch_to_frame("frameName"): 移动到指定名称的iframe
  · driver.switch_to_default_content(): 移动到默认文本内容区
  · driver.maximize_window(): 将浏览器最大化显示
  · driver.set_window_size(480, 800): 设置浏览器宽480、高800显示
  · driver.forword(), driver.back(): 浏览器前进和后退
  · driver.refresh(): 刷新页面
  · driver.close(): 关闭当前标签页
  · driver.quiit(): 关闭整个浏览器
  · driver.save_screenshot('screen.png'): 保存页面截图
  · driver.maximize_window(): 将浏览器最大化显示
  · browser.execute_script('return document.readyState;'): 执行js脚本
  selenium读取和加载cookie
  使用get_cookies和add_cookie可以实现将cookie缓存到本地,然后启动时加载,这样可以保留登录态。实现如下:
  import os
  import json
  from selenium import webdriver
  driver = webdriver.Chrome()
  driver.get("https://www.baidu.cn")
  # 读取所有cookie并保存到文件
  cookies = driver.get_cookies()
  cookie_save_path = 'cookie.json'
  with open(cookie_save_path, 'w', encoding='utf-8') as file_handle:
      json.dump(cookies, file_handle, ensure_ascii=False, indent=4)
  # 从文件读取cookie并加载到浏览器
  with open(cookie_save_path, 'r', encoding='utf-8') as file_handle:
      cookies = json.load(file_handle)
      for cookie in cookies:
          driver.add_cookie(cookie)
  selenium打开新的标签页窗口
  使用driver.get(url)会默认在第一个标签窗口打开指定连接,点击页面中的_blank的链接时也会打开一个新的标签窗口。
  还可以用下面的方式手动打开一个指定页面的标签窗口,需要注意打开新窗口或者关闭以后,还需要手动调用switch_to.window切换当前活动的标签窗口,否则会抛出NoSuchWindowException异常。
  from selenium import webdriver
  driver = webdriver.Chrome()
  driver.get("https://www.baidu.cn")
  new_tab_url = 'http://uusama.com'
  driver.execute_script(f'window.open("{new_tab_url}", "_blank");')
  time.sleep(1)
  # 注意:必须调用switch_to.window手动切换window,否则会找不到tab view
  # 聚焦到新打开的tab页面,然后关闭
  driver.switch_to.window(driver.window_handles[1])
  time.sleep(2)
  driver.close()   # 关闭当前窗口
  # 手动回到原来的tab页面
  driver.switch_to.window(driver.window_handles[0])
  time.sleep(1)
  除了使用execute_script外,还可以使用模拟打开新tab页按键的方式新建一个标签页窗口:
  ·driver.find_element_by_tag_name('body').send_keys(Keys.CONTROL + 't')
  · ActionChains(driver).key_down(Keys.CONTROL).send_keys('t').key_up(Keys.CONTROL).perform()
  selenium一些问题记录
  获取隐藏元素的文本内容
  如果一个元素是隐藏的,即display=none,虽然可以通过find_element查找到该元素,但是用element.text属性是获取不到该元素文本内容的,其值是空字符串,这时可以用下面的方式获取:
  element = driver.find_element_by_id('uusama')
  driver.execute_script("return arguments[0].textContent", element)
  driver.execute_script("return arguments[0].innerHTML", element)
  # 相应的也可以把隐藏的元素设置为非隐藏
  driver.execute_script("arguments[0].style.display = 'block';", element)
  浏览器崩溃WebDriverException异常处理
  比如在Chrome中长时间运行一个页面会出现Out Of Memory内存不足的错误,此时WebDriver会抛出WebDriverException异常,基本所有api都会抛出这个异常,这个时候需要捕获并进行特殊处理。
  我的处理方式是记录页面的一些基本信息,比如url,cookie等,并定期写入到文件中,如果检测到该异常,则重启浏览器并且加载url和cookie等数据。
  selenium抓取页面请求数据
  网上有通过driver.requests或者通过解析日志来获取页面请求的方式,但是我感觉都不是很好使。最后使用mitmproxy代理进行抓包处理,然后启动selenium时填入代理来实现。
  proxy.py为在mitmproxy基础上封装的自定义代理请求处理,其代码如下:
  import os
  import gzip
  from mitmproxy.options import Options
  from mitmproxy.proxy.config import ProxyConfig
  from mitmproxy.proxy.server import ProxyServer
  from mitmproxy.tools.dump import DumpMaster
  from mitmproxy.http import HTTPFlow
  from mitmproxy.websocket import WebSocketFlow
  class ProxyMaster(DumpMaster):
      def __init__(self, *args, **kwargs):
          super().__init__(*args, **kwargs)
      def run(self, func=None):
          try:
              DumpMaster.run(self, func)
          except KeyboardInterrupt:
              self.shutdown()
  def process(url: str, request_body: str, response_content: str):
      # 抓包请求处理,可以在这儿转存和解析数据
      pass
  class Addon(object):
      def websocket_message(self, flow: WebSocketFlow):
          # 监听websockt请求
          pass
      def response(self, flow: HTTPFlow):
          # 避免一直保存flow流,导致内存占用飙升
          # flow.request.headers["Connection"] = "close"
          # 监听http请求响应,并获取请求体和响应内容
          url = flow.request.url
          request_body = flow.request
          response_content = flow.response
          # 如果返回值是压缩的内容需要进行解压缩
          if response_content.data.content.startswith(b'\x1f\x8b\x08'):
              response_content = gzip.decompress(response_content.data.content).decode('utf-8')
          Addon.EXECUTOR.submit(process, url, request_body, response_content)
  def run_proxy_server():
      options = Options(listen_host='0.0.0.0', listen_port=16666)
      config = ProxyConfig(options)
      master = ProxyMaster(options, with_termlog=False, with_dumper=False)
      master.server = ProxyServer(config)
      master.addons.add(Addon())
      master.run()
  if __name__ == '__main__':
      with open('proxy.pid', mode='w') as fin:
          fin.write(os.getpid().__str__())
      run_proxy_server()
  在使用mitmproxy过程中,随着时间推移proxy.py会出现占用内存飙升的问题,在github的issue区有人也遇到过,有说是因为http连接keep-alive=true请求会一直保存不会释放,导致请求越多越占用内存,然后通过添加flow.request.headers["Connection"] = "close"来手动关闭连接,我加了以后有一定缓解,但还是不能从根本上解决。
  最后通过写入proxy.pid记录代理程序进程,然后用另外一个程序定时重启proxy.py来解决内存泄漏的问题。
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号