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)
|