1. 程式人生 > 實用技巧 >Selenium使用總結:載入Flash、禁用JS、滾動頁面至元素、縮放頁面

Selenium使用總結:載入Flash、禁用JS、滾動頁面至元素、縮放頁面

前言

前幾周做了個使用Selenium的專案,踩了好多好多好多的Selenium的坑,越來越感覺他作為一個第三方庫,對於Chrome的操作實在是有侷限。另外,推薦大家一個Selenium之外的操作瀏覽器的選擇:puppeteer(https://github.com/GoogleChrome/puppeteer),是來自谷歌的庫。它解決了很多在Selenium裡很難解決的問題,比如手機頁面截全屏。

好了,收回來,Selenium很多難解決的問題,我們要首先想到從JS指令碼出發,畢竟Selenium還是支援驅動瀏覽器執行JS指令碼的。

這篇文章的內容主要是Selenium日常開發中會遇到的坑,以Java程式碼為主,當然Python的小夥伴不用擔心,這裡所有的解決方案都是可以在Python中通用的。

Selenium

主要參考

Selenium使用總結(Java版本):

https://juejin.im/post/6844903737744424973

Selenium準備

chromedriver各版本映象:

https://npm.taobao.org/mirrors/chromedriver/

chromedriver版本與chrome客戶端對應支援關係:

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 滾動至元素

滾動至元素參考:

https://blog.csdn.net/sinat_28734889/article/details/77933401

實現程式碼片段:

// 獲取元素
WebElement element = webDriver.findElement(By.cssSelector(elementsCss));

// 獲取元素左上座標值
Point elementPoint = element.getLocation();
int documentScrollTop = elementPoint.getY();

// 將頁面根據元素滾動至合適位置
jsExecutor.executeScript("window.scrollTo(0," + documentScrollTop + ")");

Selenium等待:顯示,隱式

參考:

https://huilansame.github.io/huilansame.github.io/archivers/sleep-implicitlywait-wait

強制等待

sleep(3)  # 強制等待3秒再執行下一步

隱性等待

隱形等待是設定了一個最長等待時間,如果在規定時間內網頁載入完成,則執行下一步,否則一直等到時間截止,然後執行下一步。注意這裡有一個弊端,那就是程式會一直等待整個頁面載入完成,也就是一般情況下你看到瀏覽器標籤欄那個小圈不再轉,才會執行下一步。

# -*- coding: utf-8 -*-
from selenium import webdriver

driver = webdriver.Firefox()
driver.implicitly_wait(30)  # 隱性等待,最長等30秒
driver.get('https://huilansame.github.io')

print driver.current_url
driver.quit()

需要特別說明的是:隱性等待對整個driver的週期都起作用,所以只要設定一次即可,我曾看到有人把隱性等待當成了sleep在用,走哪兒都來一下…

顯性等待

顯性等待,WebDriverWait,配合該類的until()和until_not()方法,就能夠根據判斷條件而進行靈活地等待了。它主要的意思就是:程式每隔xx秒看一眼,如果條件成立了,則執行下一步,否則繼續等待,直到超過設定的最長時間,然後丟擲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)  # 隱性等待和顯性等待可以同時用,但要注意:等待的最長時間取兩者之中的大者
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定位元素後偏差

這是一個奇怪的問題,之所以會出現這個座標偏差是因為windows系統下電腦設定的顯示縮放比例造成的,location獲取的座標是按顯示100%時得到的座標,而截圖所使用的座標卻是需要根據顯示縮放比例縮放後對應的圖片所確定的,因此就出現了偏差。

解決這個問題有三種方法:

1.修改電腦顯示設定為100%。這是最簡單的方法

2.縮放擷取到的頁面圖片,即將截圖的size縮放為寬和高都除以縮放比例後的大小;

3.修改Image.crop的引數,將引數元組的四個值都乘以縮放比例。

Selenium載入Flash

看服務報告pc端截圖重構內ChromeUtil.java如何使用

問題答案裡提供了很多解決思路:

https://stackoverflow.com/questions/52185371/allow-flash-content-in-chrome-69-running-via-chromedriver

網上方案:

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

經測試Chrome65+無法使用,無效。

方法一

基本思路:通過Selenium自動訪問chrome單個網頁的設定頁,操作元素,始終允許載入flash。

讓Selenium自動選擇下面的按鈕

這個操作的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進入該url設定頁
     * @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);
    }

    /**
     * 允許網頁的flash執行,chrome67版本可行,75版本提示升級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"); // 無頭模式
            // options.addArguments("--no-sandbox"); // Linux關閉沙盒模式
            // options.addArguments("--disable-gpu"); // 禁用顯示卡
            webDriver = new ChromeDriver(options);
            webDriver.manage().window().setSize(new Dimension(1300, 800));
            String url = "https://shanghai.fang.anjuke.com/";

            // 獲取重定向後網址再開啟Flash許可權
            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設定裡將所有網站加入flash白名單,但實測selenium會開啟新的chrome,不讀取通用設定,類似無痕視窗,有空再試試。

總結

  • 全域性flash載入的設定按鈕在selenium不起作用
  • 使用pref載入也沒有用

禁止javascript

禁止執行javascript還是可以通過pref的:

HashMap<String, Object> chromePrefs = new HashMap<>(2);
chromePrefs.put("profile.managed_default_content_settings.javascript", 2);
options.setExperimentalOption("prefs", chromePrefs);

Selenium調整網頁縮放大小

執行js

document.body.style.zoom='0.5'

關注我

我目前是一名後端開發工程師。主要關注後端開發,資料安全,網路爬蟲,物聯網,邊緣計算等方向。

微信:yangzd1102

Github:@qqxx6661

個人部落格:

原創部落格主要內容

  • Java知識點複習全手冊
  • Leetcode演算法題解析
  • 劍指offer演算法題解析
  • SpringCloud菜鳥入門實戰系列
  • SpringBoot菜鳥入門實戰系列
  • Python爬蟲相關技術文章
  • 後端開發相關技術文章

個人公眾號:後端技術漫談

如果文章對你有幫助,不妨收藏起來並轉發給您的朋友們~