前言
- 上一次用selenium还是在21年。
- 前天要开发web自动化的脚本,这玩意其实用按键精灵比较适合。
- 但是按键精灵PC版我基本没怎么用过,那就只能用selenium了。
- 当然,selenium和按键精灵各有优劣,selenium也挺好的。
- 用selenium踩坑点太多了,一些流程写起来很不好处理,全代码贼多的捕获异常语句。
- 本文记录一些踩坑点...(有机会还得去熟悉下PC版按键,技多不压身)
关于selenium的安装
截至2024.04.12,不指定版本安装的话,selenium最新版是4.19.0
pip install selenium
- 目前的最新版是不需要指定chromedriver驱动
- 但是,如果下载的是第三方谷歌浏览器,比如说在虚拟机里可能无法直接安装官方谷歌浏览器
- 那么下载第三方的浏览器可能就得指定chromedriver驱动
- 根据浏览器版本下载指定的驱动,然后将驱动解压后放在浏览器的安装目录下
- 但是的话如果要指定驱动路径就需要降低下selenium版本了,推荐3.14.0
pip install selenium==3.14.0
chrome_options = Options()
port = "1234"
user_data_dir = f"C:\\selenum\\AutomationProfile_{port}"
chrome_options.add_experimental_option("debuggerAddress", f"127.0.0.1:{port}")
driver_path = "C:/Users/daowuya/AppData/Local/MyChrome/Chrome/Application/chromedriver.exe"
os.popen(f'"{chrome_path}" --remote-debugging-port={port} --user-data-dir="{user_data_dir}"')
driver = webdriver.Chrome(executable_path=driver_path, options=chrome_options)
注:此处的chrome_options和最新版的引用不一样,最新版的引用见下文的启用调试模式
启用调试模式
当浏览器环境不够模拟的时候,就得启用调试模式了。
浏览器路径的指定
- 通过读取本地里的文本来指定路径会有问题,这问题排查了很久。
- 后面直接在代码里指定就解决了
chrome_path = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
浏览器运行端口和用户数据的指定
- 如果程序不需要多开的话就没必要去指定,否则就必须指定。
- 用户数据不能用同一个路径,不然就无法多开,用户数据可以和端口相绑定。
- 端口不建议随机生成,这是踩坑点。
- 随机生成的端口会导致每次打开的浏览器都是全新的,全新的浏览器“初始设置”没设置,会导致打开url时上面的网页栏丢失,最终会造成后面的driver.close()或者driver.quit()命令无效。
- 端口应该指定,或者读取本地文本。在指定端口后,先用指定的端口运行一遍,这样一个浏览器就有了“初始设置”,就不会出现上面的命令失效的问题。
chrome_options = webdriver.ChromeOptions()
port = "1314"
uer_data_dir = f"C:\\selenum\\AutomationProfile_{port}"
chrome_options.add_experimental_option("debuggerAddress", f"127.0.0.1:{port}")
os.popen(f'"{chrome_path}" --remote-debugging-port={port} --user-data-dir="{user_data_dir}"')
driver = webdriver.Chrome(options=chrome_options)
清除浏览器缓存
下面的跳转没啥问题,主要是弹窗的点击按钮用selenium根本无法定位到,于是使用了execute_script运行js代码命令去定位到
- 设置-隐私和安全-清除浏览数据-清除数据
driver.get('chrome://settings/clearBrowserData')
driver.implicitly_wait(60)
time.sleep(1)
clearButton = driver.execute_script("return document.querySelector('settings-ui').shadowRoot.querySelector('settings-main').shadowRoot.querySelector('settings-basic-page').shadowRoot.querySelector('settings-section > settings-privacy-page').shadowRoot.querySelector('settings-clear-browsing-data-dialog').shadowRoot.querySelector('#clearBrowsingDataDialog').querySelector('#clearBrowsingDataConfirm')")
advancedArea.click()
time.sleep(2)
- 设置-重置设置-将设置还原为原始默认设置-重置设置
driver.get("chrome://settings/resetProfileSettings?origin=userclick")
driver.implicitly_wait(60)
time.sleep(1)
clearButton = driver.execute_script("return document.querySelector('settings-ui').shadowRoot.querySelector('settings-main').shadowRoot.querySelector('settings-basic-page').shadowRoot.querySelector('settings-section > settings-reset-page').shadowRoot.querySelector('settings-reset-profile-dialog').shadowRoot.querySelector('#dialog').querySelector('#reset')")
clearButton.click()
time.sleep(2)
判断元素的存在性
使用隐式等待,再配合捕获异常,看起来是比较丑陋了点,不过能实现就好!踩坑点,换了两三种写法...
while True:
try:
element = driver.find_element(By.XPATH, "//div[contains(@class, 'ui-form-explain') and contains(text(), '发送次数超过限制')]")
if element.is_displayed():
print("存在发送次数超过限制")
break
except Exception as e:
print("不存在发送次数超过限制!")
要点就是if element 和 if element.is_displayed()的区别:
- if element
主要检查element对象是否存在,即使这个对象代表的元素在页面上不可见或者位置为空,这个检查不关心元素是否可见,只关心元素是否被成功找到并引用。 - if element.is_displayed()
具体检查页面上的元素是否对用户可见。一个元素可能在DOM(文档对象模型)中存在,但由于各种原因(例如,CSS设置为display: none或visibility: hidden),它可能不会显示在页面上。
因此,如果只用if element去判断一个元素后执行点击,往往可能卡住,即使加了隐式等待去用捕获异常判断,因为元素对象存在,但元素不可见,就可能导致了元素不可交互,也就是不能执行点击操作。所以稳妥的用法就是嵌套使用,先判断是否存在,再判断是否可见!
一些元素在页面但无法被定位到
检查下是否这个元素被iframe所包裹
如果被iframe所包裹,那么切换后再执行定位的命令,基本能解决
iframe = driver.find_element(By.ID, "iframe1")
driver.switch_to.frame(iframe)
切换回主文档:最外层的HTML文档,也就是没有被任何iframes或frames嵌套的部分
driver.switch_to.default_content()
使用代理去启动浏览器
有时候单ip就会导致被检测到或者限制了,此时就需要用到代理了。
- 如果不是用调试模式启动的,那么就直接在chrome_options增加就行了
proxy=""
chrome_options.add_argument(f'--proxy-server={proxy}')
- 如果是在调试模式下启动的,那么加在chrome_options是无效的,需要在启动的时候直接加
proxy=""
os.popen(f'"{chrome_path}" --remote-debugging-port={port} --user-data-dir="{user_data_dir}" --proxy-server={proxy}')
关于过滑块方面
下面给的例子毫无轨迹可言,可应付没有轨迹检测的滑块。轨迹滑块在这不作分享...
slider = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, "/html/body/div/div[2]/div/div[1]/div[2]/center/div[1]/div/div/div/span")))
ActionChains(driver).click_and_hold(slider).move_by_offset(300, 0).release().perform()
其他
遇到了再补充,暂时没用到其他的...