前言
前幾周做了個(gè)使用Selenium的項(xiàng)目,踩了好多好多好多的Selenium的坑,越來(lái)越感覺(jué)他作為一個(gè)第三方庫(kù),對(duì)于Chrome的操作實(shí)在是有局限。另外,推薦大家一個(gè)Selenium之外的操作瀏覽器的選擇:puppeteer(https://github.com/GoogleChrome/puppeteer),是來(lái)自谷歌的庫(kù)。它解決了很多在Selenium里很難解決的問(wèn)題,比如手機(jī)頁(yè)面截全屏。
好了,收回來(lái),Selenium很多難解決的問(wèn)題,我們要首先想到從JS腳本出發(fā),畢竟Selenium還是支持驅(qū)動(dòng)瀏覽器運(yùn)行JS腳本的。
這篇文章的內(nèi)容主要是Selenium日常開(kāi)發(fā)中會(huì)遇到的坑,以Java代碼為主,當(dāng)然Python的小伙伴不用擔(dān)心,這里所有的解決方案都是可以在Python中通用的。
Selenium
主要參考
Selenium使用總結(jié)(Java版本):
https://juejin.im/post/5c13880ef265da610f639c3c
Selenium準(zhǔn)備
chromedriver各版本鏡像:
https://npm.taobao.org/mirrors/chromedriver/
chromedriver版本與chrome客戶端對(duì)應(yīng)支持關(guān)系:
https://npm.taobao.org/mirrors/chromedriver/2.46/notes.txt
最新版本截圖:
----------ChromeDriver v2.46 (2019-02-01)----------
Supports Chrome v71-73
Resolved issue 2728: Is Element Displayed command does not work correctly with v0 shadow DOM inserts [[Pri-1]]
Resolved issue 755: /session/:sessionId/doubleclick only generates one set of mousedown/mouseup/click events [[Pri-2]]
Resolved issue 2744: Execute Script returns wrong error code when JavaScript returns a cyclic data structure [[Pri-2]]
Resolved issue 1529: OnResponse behavior can lead to port exhaustion [[Pri-2]]
Resolved issue 2736: Close Window command should handle user prompts based on session capabilities [[Pri-2]]
Resolved issue 1963: Sending keys to disabled element should throw Element Not interactable error [[Pri-2]]
Resolved issue 2679: Timeout value handling is not spec compliant [[Pri-2]]
Resolved issue 2002: Add Cookie is not spec compliant [[Pri-2]]
Resolved issue 2749: Update Switch To Frame error checks to match latest W3C spec [[Pri-3]]
Resolved issue 2716: Clearing Text Boxes [[Pri-3]]
Resolved issue 2714: ConnectException: Failed to connect to localhost/0:0:0:0:0:0:0:1:15756. Could not start driver. [[Pri-3]]
Resolved issue 2722: Execute Script does not correctly convert document.all into JSON format [[Pri-3]]
Resolved issue 2681: ChromeDriver doesn't differentiate "no such element" and "stale element reference" [[Pri-3]]
----------ChromeDriver v2.45 (2018-12-10)----------
Supports Chrome v70-72
Resolved issue 1997: New Session is not spec compliant [[Pri-1]]
Resolved issue 2685: Should Assert that the chrome version is compatible [[Pri-2]]
Resolved issue 2677: Find Element command returns wrong error code when an invalid locator is used [[Pri-2]]
Resolved issue 2676: Some ChromeDriver status codes are wrong [[Pri-2]]
Resolved issue 2665: compile error in JS inside of WebViewImpl::DispatchTouchEventsForMouseEvents [[Pri-2]]
Resolved issue 2658: Window size commands should handle user prompts [[Pri-2]]
Resolved issue 2684: ChromeDriver doesn't start Chrome correctly with options.addArguments("user-data-dir=") [[Pri-3]]
Resolved issue 2688: Status command is not spec compliant [[Pri-3]]
Resolved issue 2654: Add support for strictFileInteractability [[Pri-]]
Selenium 滾動(dòng)至元素
滾動(dòng)至元素參考:
https://blog.csdn.net/sinat_28734889/article/details/77933401
實(shí)現(xiàn)代碼片段:
// 獲取元素
WebElement element = webDriver.findElement(By.cssSelector(elementsCss));
// 獲取元素左上坐標(biāo)值
Point elementPoint = element.getLocation();
int documentScrollTop = elementPoint.getY();
// 將頁(yè)面根據(jù)元素滾動(dòng)至合適位置
jsExecutor.executeScript("window.scrollTo(0," + documentScrollTop + ")");
Selenium等待:顯示,隱式
參考:
https://huilansame.github.io/huilansame.github.io/archivers/sleep-implicitlywait-wait
強(qiáng)制等待
sleep(3) # 強(qiáng)制等待3秒再執(zhí)行下一步
隱性等待
隱形等待是設(shè)置了一個(gè)最長(zhǎng)等待時(shí)間,如果在規(guī)定時(shí)間內(nèi)網(wǎng)頁(yè)加載完成,則執(zhí)行下一步,否則一直等到時(shí)間截止,然后執(zhí)行下一步。注意這里有一個(gè)弊端,那就是程序會(huì)一直等待整個(gè)頁(yè)面加載完成,也就是一般情況下你看到瀏覽器標(biāo)簽欄那個(gè)小圈不再轉(zhuǎn),才會(huì)執(zhí)行下一步。
# -*- coding: utf-8 -*-
from selenium import webdriver
driver = webdriver.Firefox()
driver.implicitly_wait(30) # 隱性等待,最長(zhǎng)等30秒
driver.get('https://huilansame.github.io')
print driver.current_url
driver.quit()
需要特別說(shuō)明的是:隱性等待對(duì)整個(gè)driver的周期都起作用,所以只要設(shè)置一次即可,我曾看到有人把隱性等待當(dāng)成了sleep在用,走哪兒都來(lái)一下…
顯性等待
顯性等待,WebDriverWait,配合該類的until()和until_not()方法,就能夠根據(jù)判斷條件而進(jìn)行靈活地等待了。它主要的意思就是:程序每隔xx秒看一眼,如果條件成立了,則執(zhí)行下一步,否則繼續(xù)等待,直到超過(guò)設(shè)置的最長(zhǎng)時(shí)間,然后拋出TimeoutException。
# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
driver = webdriver.Firefox()
driver.implicitly_wait(10) # 隱性等待和顯性等待可以同時(shí)用,但要注意:等待的最長(zhǎng)時(shí)間取兩者之中的大者
driver.get('https://huilansame.github.io')
locator = (By.LINK_TEXT, 'CSDN')
try:
WebDriverWait(driver, 20, 0.5).until(EC.presence_of_element_located(locator))
print driver.find_element_by_link_text('CSDN').get_attribute('href')
finally:
driver.close()
Selenium定位元素后偏差
這是一個(gè)奇怪的問(wèn)題,之所以會(huì)出現(xiàn)這個(gè)坐標(biāo)偏差是因?yàn)閣indows系統(tǒng)下電腦設(shè)置的顯示縮放比例造成的,location獲取的坐標(biāo)是按顯示100%時(shí)得到的坐標(biāo),而截圖所使用的坐標(biāo)卻是需要根據(jù)顯示縮放比例縮放后對(duì)應(yīng)的圖片所確定的,因此就出現(xiàn)了偏差。
解決這個(gè)問(wèn)題有三種方法:
1.修改電腦顯示設(shè)置為100%。這是最簡(jiǎn)單的方法;
2.縮放截取到的頁(yè)面圖片,即將截圖的size縮放為寬和高都除以縮放比例后的大小;
3.修改Image.crop的參數(shù),將參數(shù)元組的四個(gè)值都乘以縮放比例。
Selenium加載Flash
看服務(wù)報(bào)告pc端截圖重構(gòu)內(nèi)ChromeUtil.java如何使用
問(wèn)題答案里提供了很多解決思路:
網(wǎng)上方案:
prefs.put("profile.default_content_setting_values.plugins", 1);
prefs.put("profile.content_settings.plugin_whitelist.adobe-flash-player", 1);
prefs.put("profile.content_settings.exceptions.plugins.*,*.per_resource.adobe-flash-player", 1);
經(jīng)測(cè)試Chrome65+無(wú)法使用,無(wú)效。
方法一
基本思路:通過(guò)Selenium自動(dòng)訪問(wèn)chrome單個(gè)網(wǎng)頁(yè)的設(shè)置頁(yè),操作元素,始終允許加載flash。

讓Selenium自動(dòng)選擇下面的按鈕

這個(gè)操作的Demo代碼:
package util;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.support.ui.Select;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ChromeUtil {
/**
* 格式化url進(jìn)入該url設(shè)置頁(yè)
* @param url
* @return
*/
private static String _base_url(String url){
if (url.isEmpty()){
return url;
}
try {
URL urls = new URL(url);
return String.format("%s://%s",urls.getProtocol(),urls.getHost());
}catch (Exception e){
return url;
}
}
/**
* 元素選擇
* @param driver
* @param element
* @return
*/
private static WebElement _shadow_root(WebDriver driver, WebElement element){
return (WebElement)((JavascriptExecutor) driver).executeScript("return arguments[0].shadowRoot", element);
}
/**
* 允許網(wǎng)頁(yè)的flash運(yùn)行,chrome67版本可行,75版本提示升級(jí)flash
* @param driver
* @param url
*/
public static void allow_flash(WebDriver driver, String url) {
url = _base_url(url);
driver.get(String.format("chrome://settings/content/siteDetails?site=%s",url));
WebElement webele_settings = _shadow_root(driver,(((ChromeDriver)driver).findElementByTagName("settings-ui")));
WebElement webele_container = webele_settings.findElement(By.id("container"));
WebElement webele_main = _shadow_root(driver,webele_container.findElement(By.id("main")));
WebElement showing_subpage = _shadow_root(driver,webele_main.findElement(By.className("showing-subpage")));
WebElement advancedPage = showing_subpage.findElement(By.id("advancedPage"));
WebElement settings_privacy_page = _shadow_root(driver,advancedPage.findElement(By.tagName("settings-privacy-page")));
WebElement pages = settings_privacy_page.findElement(By.id("pages"));
WebElement settings_subpage = pages.findElement(By.tagName("settings-subpage"));
WebElement site_details = _shadow_root(driver,settings_subpage.findElement(By.tagName("site-details")));
WebElement plugins = _shadow_root(driver,site_details.findElement(By.id("plugins")));
WebElement permission = plugins.findElement(By.id("permission"));
Select sel = new Select(permission);
sel.selectByValue("allow");
}
/**
* @param args
*/
public static void main(String[] args) {
System.setProperty("webdriver.chrome.driver", Constants.PATH_Dict.DRIVER_PATH.getValue());
WebDriver webDriver = null;
try {
// 初始化webDriver
ChromeOptions options = new ChromeOptions();
// options.addArguments("--headless"); // 無(wú)頭模式
// options.addArguments("--no-sandbox"); // Linux關(guān)閉沙盒模式
// options.addArguments("--disable-gpu"); // 禁用顯卡
webDriver = new ChromeDriver(options);
webDriver.manage().window().setSize(new Dimension(1300, 800));
String url = "https://shanghai.fang.anjuke.com/";
// 獲取重定向后網(wǎng)址再打開(kāi)Flash權(quán)限
webDriver.get(url);
allow_flash(webDriver,webDriver.getCurrentUrl());
webDriver.get(url);
Thread.sleep(1 * 60 * 1000);
} catch(Exception e) {
e.printStackTrace();
} finally {
if(webDriver != null) {
webDriver.quit();
}
}
}
}
方法二
在chrome設(shè)置里將所有網(wǎng)站加入flash白名單,但實(shí)測(cè)selenium會(huì)打開(kāi)新的chrome,不讀取通用設(shè)置,類似無(wú)痕窗口,有空再試試。
總結(jié)
- 全局flash加載的設(shè)置按鈕在selenium不起作用
- 使用pref加載也沒(méi)有用
禁止javascript
禁止運(yùn)行javascript還是可以通過(guò)pref的:
HashMap<String, Object> chromePrefs = new HashMap<>(2);
chromePrefs.put("profile.managed_default_content_settings.javascript", 2);
options.setExperimentalOption("prefs", chromePrefs);
Selenium調(diào)整網(wǎng)頁(yè)縮放大小
運(yùn)行js
document.body.style.zoom='0.5'