Python Selenium 抓取Shadow Dom内部元素方法更新

By Mejias

背景:

应团队的PMP的要求,为自己的Team开发了一个内部网站信息抓取的工具(整体代码展示见文章末尾,可能稍微有点长)。上上周周写完测试后推给了大家,没有什么问题。今天Team的一个小伙伴突然告诉我报错,显示是Chrome Driver与Chrome版本不对,搜索Chrome://version,才发现是Chrome自动升级了。这样原来版本的Chrome driver就不支持了,导致程序报错。

 

在Chrome Driver官网here重新搜索了版本匹配的Chrome Driver并下载安装好之后,再次运行程序,发现前面都运行的很好直到后面首尾部分报错如下:

报错显示的是下面这里出了问题:

def control_in_shadow(driver,js):

    shadow = driver.execute_script(js)

    return shadow #返回的对象在这里

js = 'return document.querySelector("#ra-shadow-root").shadowRoot'

shadow= control_in_shadow(driver,js)

shadow.find_element(By.ID,'ra-asin-list-count-input').clear()

shadow.find_element(By.ID,'ra-asin-list-count-input').send_keys('1000')

shadow.find_element(By.ID,'ra-asin-list-load-btn').click()

上面的代码访问下列Shadow Doml里的元素。

 

采用的方法就是常说的三步法:

定位到Shadow Dom的Host节点 =》 使用.shadowRoot属性定位到根节点 =》

直接通过页面Element的方法访问Shadow Dom内部的元素。这种方法在未更新chrome driver 的版本之前一直用的很好的。

但是在更新了Chrome版本和chrome driver版本之后就会报错了。原来的方法在新的chrome driver并不适用。于是在收到小伙伴的反馈后,就需要测试代码问题以及修改和Refine了。

测试&发现问题:

首先根据上面的报错可以定位到是下面的代码出了问题。

​
def control_in_shadow(driver,js):

    shadow = driver.execute_script(js)

    return shadow #返回的对象在这里

js = 'return document.querySelector("#ra-shadow-root").shadowRoot'

shadow= control_in_shadow(driver,js)

shadow.find_element(By.ID,'ra-asin-list-count-input').clear()

shadow.find_element(By.ID,'ra-asin-list-count-input').send_keys('1000')

shadow.find_element(By.ID,'ra-asin-list-load-btn').click()

​

因为.execute_script(js)是driver对象自带的方法,这一段执行JS语句也没有报错,所以肯定不是定义的新方法的问题。而且看代码报错是说函数返回的对象是一个dict,而不是原来应该是的remote controlled web element元素,导致web element的查找元素的方法.find_element(By.)使用报错。所以我这里定位到这个返回对象“shadow”是否问题。

为了测试它是否只是一个dict还是说代表了shadowRoot这个节点,我们可以使用shadowRoot的一些属性和方法去测试他是否是一个shadowRoot。

shadowDom的shadowRoot有许多的属性,例如:

shadowRoot.host(返回根节点的宿主节点的引用);

shadowRoot.innerHTML(返回对shadowRoot内的DOM树的引用)

shadowRoot.mode(返回shadowRoot的模式 -open或 -closed)

测试代码如下:

def control_in_shadow(driver,js):

    shadow = driver.execute_script(js)

    return shadow #返回的对象在这里

 js = 'return document.querySelector("#ra-shadow-root").shadowRoot'

 shadow= control_in_shadow(driver,js)

 shadow.host

 shadow.mode

 shadow.innerHTML

测试结果如下:

可以看出返回的shadow对象已经不是一个shadowRoot元素,所以也不能使用他的一些属性,包括上述代码的.find_element(By.Id)方法了。

解决bug的尝试:

这里我们可以看到直接用driver.execute_script()返回值不能再进行页面的一些操作了,但是在代码里运行js语言依然没有问题,所以我们想到的解决办法就是直接书写JS语言在Python代码中运行。比如上述的几个属性可以通过下面的代码得到返回值。

​def control_in_shadow(driver,js):

    shadow = driver.execute_script(js)

    return shadow #返回的对象在这里

js = 'return document.querySelector("#ra-shadow-root").shadowRoot'

shadow= control_in_shadow(driver,js)

js1 = 'return document.querySelector("#ra-shadow-root").shadowRoot.host'

js2 = 'return document.querySelector("#ra-shadow-root").shadowRoot.mode'

js3 = 'return document.querySelector("#ra-shadow-root").shadowRoot.innerHTML'

host_res =  control_in_shadow(driver,js1)

mode_res = control_in_shadow(driver,js2)

inner_HTML_res = control_in_shadow(driver,js3)

print(host_res)

print(mode_res)

print(inner_HTML_res)

​

解决测试的运行结果:

可以看出通过直接运行JS可以成功的找到所有的属性返回值,以及访问到宿主节点的IP为一个web element对象。

代码修复:

根据以上的探索,我们可以知道可以通过直接在Python中运行JS语句实现成功的访问shadow Dom里面的元素。基于此我们可以把原始代码修改如下(原始代码不需要的行已经注释起来了):

​
def control_in_shadow(driver,js):

    shadow = driver.execute_script(js)

    return shadow #返回的对象在这里

js1 = 'return document.querySelector("#ra-shadow-root").shadowRoot.getElementById("ra-asin-list-count-input")'

#shadow= control_in_shadow(driver,js)

input_id = control_in_shadow(driver,js1)

input_id.clear()

input_id.send_keys('1000')

       

#shadow.find_element(By.ID,'ra-asin-list-count-input').clear()

#shadow.find_element(By.ID,'ra-asin-list-count-input').send_keys('1000')

#shadow.find_element(By.ID,'ra-asin-list-load-btn').click()

js2 = 'document.querySelector("#ra-shadow-root").shadowRoot.getElementById("ra-asin-list-load-btn").click()'

load_id = control_in_shadow(driver,js2)

load_id

js3 = 'return document.querySelector("#ra-shadow-root").shadowRoot.getElementById("ra-asin-list-csv-btn")'

  save_id = control_in_shadow(driver,js3)

​

上面的代码运行起来就没有问题了。而且这是通过直接操作JS语句的,速度上也是可以的。

初始代码展示:

以下为整段代码(可能稍微有点长)。后续有机会可以和大家分享下面代码的编写的逻辑。

import pandas as pd

import numpy as np

import os



from selenium import webdriver

from selenium.webdriver.chrome.service import Service

from selenium.webdriver.chrome.options import Options

from selenium.webdriver.common.by import By

from selenium.webdriver.common.action_chains import ActionChains

from selenium.webdriver.support.ui import WebDriverWait

from selenium.webdriver.support.wait import WebDriverWait

from selenium.webdriver.support import expected_conditions as EC



import selenium.common.exceptions

import requests

import time

import json

import mitmproxy

import pyautogui



def open_chrome_driver(option):

    driver = webdriver.Chrome(options = option)

    return driver



def open_new_window(driver,url):

    new = 'window.open(%s)'%url

    driver.execute_script(new)

    handles = driver.window_handles

    driver.switch_to.window(handles[-1])

    return driver

   

def open_url(driver,url):

    driver.get(url)



def wait_by_clickable(driver,wait_time,ID):

    wait = WebDriverWait(driver,wait_time)

    wait_name = wait.until(EC.element_to_be_clickable(driver.find_element(By.ID,ID)))

    return wait_name



def wait_by_presence(driver,wait_time,ID):

    wait = WebDriverWait(driver,wait_time)

    wait_name = wait.until(EC.presence_of_element_located((By.ID,ID)))

    return wait_name

   

def keyboard_perform(driver,ID):

    logo = driver.find_element(By.ID,ID)

    rightClick = ActionChains(driver)

    rightClick.context_click(logo).perform()

    time.sleep(4)

   

    pyautogui.typewrite(['down','down','down','down','down','down','down','down','down'])

    time.sleep(4)

   

    pyautogui.typewrite(['enter'])

    time.sleep(5)

    pyautogui.typewrite(['enter'])

    time.sleep(5)

   

def control_in_shadow(driver,js):

    shadow = driver.execute_script(js)

    return shadow



def find_key_words(file_name):

    pecos = pd.read_excel(file_name)

    key_words = list(pecos.loc[:,'title'])

    print(key_words)

    return key_words



def find_index(list,element):

    for i in range (0,len(list)):

        if list[i] == element:

            return i

        else:

            pass

   

def concatenate_file(path):

    os.chdir(r'%s./Peco_File_Download'%path)

    filelist = []

    list_link=[]

    filelist2 = []

    key_content_list = []



    for root, dirs,files in os.walk(".",topdown = False):

        for name in files:

            str = os.path.join(root,name)

            if str.split('.')[-1] == 'csv':

                filelist.append(str)

   

    for each_file in filelist:

        name = each_file.split('\\')[1]

        filelist2.append(name)

        key = name.split('-ASIN')[0]

        key_content_list.append(key)



    for each_range in range(len(filelist2)):

        list_count = pd.read_csv(r'%s'%filelist2[each_range])

        list_link.append(list_count)

       

    for each_file in filelist:

        inx = find_index(filelist,each_file)

        current_list = list_link[inx]

        current_list['key_words'] = key_content_list[inx]

       

    df1 = pd.concat(list_link,ignore_index = True)

    df2 = df1[['asin','key_words']]

    df2.to_csv('合并后的表格.csv')

   

def find_one_B_one(keywords,final_sleep_time):

    #open_existed_chrome_option

    option = webdriver.ChromeOptions()

    option.add_experimental_option("debuggerAddress","127.0.0.1:9999")

    driver = open_chrome_driver(option)

    for i in keywords:

        url = "https://www.amazon.com/"

        open_url(driver,url)

       

        #search&submit keywords

        search_box = wait_by_clickable(driver,1200,'twotabsearchtextbox')

        search_box

       

        search_box.send_keys(i)

   

        submit_button = wait_by_clickable(driver,1200,'nav-search-submit-button')

        submit_button

        submit_button.click()

        time.sleep(0.01)



        #retail_assistance

        keyboard_perform(driver,'nav-search-submit-button')

        shadow_root = wait_by_presence(driver,1200,'ra-shadow-root')

        shadow_root

        time.sleep(4)



        js = 'return document.querySelector("#ra-shadow-root").shadowRoot'

        shadow= control_in_shadow(driver,js)



        shadow.find_element(By.ID,'ra-asin-list-count-input').clear()

        shadow.find_element(By.ID,'ra-asin-list-count-input').send_keys('1000')

        shadow.find_element(By.ID,'ra-asin-list-load-btn').click()

        time.sleep(final_sleep_time)

       

        wait = WebDriverWait(driver,4000)

        tag = shadow.find_element(By.ID,'ra-asin-list-csv-btn')

        save_button = wait.until(EC.element_to_be_clickable(tag))

        save_button

        time.sleep(4)

        save_button.click()

        time.sleep(4)



if __name__ == "__main__":

    path = os.getcwd()

    wait_sleep_time = int(input('请输入您需要等待pecos load的时长/second:'))

    keywords = find_key_words('PECO Keywords.xlsx')

    find_one_B_one(keywords,wait_sleep_time)

    #concatenate_file(path)

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
在使用Selenium定位shadow DOM时,可以通过执行JavaScript语句来获取shadow DOM元素。首先需要使用execute_script()方法执行JavaScript代码来获取shadow DOM根节点。例如,可以使用以下代码来获取shadow DOM根节点: ```python js = 'return document.querySelector("#ra-shadow-root").shadowRoot' shadow = driver.execute_script(js) ``` 然后,可以使用返回的shadow DOM对象来定位其中的元素。例如,可以使用以下代码来定位并操作shadow DOM中的输入框元素: ```python input_id = shadow.find_element(By.ID, 'ra-asin-list-count-input') input_id.clear() input_id.send_keys('1000') ``` 同样地,可以使用类似的方法来定位和操作其他的shadow DOM元素。需要注意的是,使用execute_script()方法获取shadow DOM根节点后,可以使用普通的Selenium定位方法来定位其中的元素。 以上是一种常用的方法来定位和操作shadow DOM元素。如果需要更多关于Selenium定位shadow DOM方法,还可以参考其他资料或者自行搜索相关内容。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Python Selenium 抓取Shadow Dom内部元素方法更新](https://blog.csdn.net/m0_55341949/article/details/121500244)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [使用Selenium访问shadow dom](https://blog.csdn.net/LeonLee85/article/details/101566770)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值