Selenuim ChromeDriver自动登录试验

1.环境

  • chrome版本

chrome://version/
Google Chrome 67.0.3396.99 (正式版本) (32 位) (cohort: Stable)

chrome安装位置
%USERPROFILE%\AppData\Local\Google\Chrome\Application

 

  • chromdriver版本

>chromedriver -v
ChromeDriver 2.40.565498 (ea082db3280dd6843ebfb08a625e3eb905c4f5ab)


符合ChromeDriver与Chrome的对应关系:
http://chromedriver.storage.googleapis.com/2.40/notes.txt
----------ChromeDriver v2.40 (2018-06-07)----------
Supports Chrome v66-68
 

  • selenium-java.jar版本
<dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>3.12.0</version>
</dependency>

2.登录页面

2个登录页面
(1)https://login.taobao.com/member/login.jhtml
(2)https://login.taobao.com/member/login.jhtml?style=mini

(1)默认是手机扫码,安全登录,需要切换到密码登录J_Quick2Static
driver.findElement(By.id("J_Quick2Static")).click();
(2)不需要切换,否则产生以下异常:
org.openqa.selenium.ElementNotVisibleException: element not visible
#J_Quick2Static是不可见的.

试验采用https://login.taobao.com/member/login.jhtml?style=mini

3.基本代码

基本代码如下:

public class ExampleForChrome {  
	static String chromePath = "C:/Users/Think/AppData/Local/Google/Chrome/Application/"; 

	static int DEFAULT_TIMEOUT = 15;
	static String url = "https://login.taobao.com/member/login.jhtml?style=mini";  
        static String username = "wherer";
        static String password = "xxxx";
	
	public static void main(String[] args)   {
		test1(); ///< 具体的测试函数
	}
	
	static public void waitTime(int time) {
		try {
			Thread.sleep(time);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}	
}

后续测试替换不同的测试函数.

4.测试记录

4.1测试1


特点:
.计算偏移,拖动滑块
.执行一次

代码:

	static void test1() {
		System.setProperty("webdriver.chrome.driver", chromePath + "chromedriver.exe");


		ChromeOptions Options = new ChromeOptions();
		WebDriver driver = new ChromeDriver(Options);
		driver.manage().window().maximize();
		driver.manage().timeouts().implicitlyWait(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
		driver.get(url);

		driver.findElement(By.id("TPL_username_1")).sendKeys(username);
		driver.findElement(By.id("TPL_password_1")).sendKeys(password);

		/// < 滑轨
		WebElement sliderWay = driver.findElement(By.id("nc_1_n1t"));
		Dimension dWay = sliderWay.getSize();
		/// < 滑块
		WebElement slider = driver.findElement(By.xpath("//*[@id='nc_1_n1z']"));
		Dimension dSlider = slider.getSize();
		/// < 拖动滑块提示,出错,验证通过信息
		WebElement scaleText = driver.findElement(By.className("nc-lang-cnt"));/// ("nc_1__scale_text"));
		String text = scaleText.getText();
		/// < 拖动滑块偏移(x)
		int offset = dWay.width - dSlider.width;
		Actions action = new Actions(driver);
		action.clickAndHold(slider).moveByOffset(offset, 0).perform();
		text = driver.findElement(By.className("nc-lang-cnt")).getText();
		while (text.startsWith("加载中") || text.isEmpty()) {
			waitTime(1000);
			text = driver.findElement(By.className("nc-lang-cnt")).getText();
		}
		if (text.startsWith("哎呀")) {
			System.out.println(text); /// < 程序进入这里,提示"哎呀,出错了,点击刷新再来一次"
			/// < 刷新滑块
			driver.findElement(By.xpath("//*[@id='nocaptcha']/div/span/a")).click();
			return;
		}
		driver.findElement(By.id("J_SubmitStatic")).click();
		/// < 检查页面是否跳转
		String newUrl = driver.getCurrentUrl();
		driver.close();
	}

结果:
.提示"哎呀,出错了,点击刷新再来一次"

 

4.2测试2

特点:
.滑块拖动采用增量方式
.固定增量和随机增量,x,y方向
.重复测试
 

代码:

	static void test2() {
		System.setProperty("webdriver.chrome.driver", chromePath + "chromedriver.exe");


		ChromeOptions Options = new ChromeOptions();
		WebDriver driver = new ChromeDriver(Options);
		driver.manage().window().maximize();
		driver.manage().timeouts().implicitlyWait(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
		driver.get(url);

		driver.findElement(By.id("TPL_username_1")).sendKeys(username);
		driver.findElement(By.id("TPL_password_1")).sendKeys(password);
		/// < 滑轨
		WebElement sliderWay = driver.findElement(By.id("nc_1_n1t"));
		/// < 滑块
		WebElement slider = driver.findElement(By.xpath("//*[@id='nc_1_n1z']"));
		/// < 拖动滑块提示,出错,验证通过信息
		WebElement scaleText = driver.findElement(By.className("nc-lang-cnt"));/// ("nc_1__scale_text"));
		String text = scaleText.getText();

		/// < 拖动滑块偏移(x)
		int offset = 200; /// < 初始偏移
		int step = 5;/// < 增量步长
		int yOffset = 0;
		
		Actions action = new Actions(driver);
		action.clickAndHold(slider);
		action.moveByOffset(offset, 0).perform();

		int flag = 0; /// 滑块验证是否通过
		do {
			text = driver.findElement(By.className("nc-lang-cnt")).getText();
			while (text.startsWith("加载中") || text.isEmpty()) {
				waitTime(1000);
				text = driver.findElement(By.className("nc-lang-cnt")).getText();
			}
			if (text.startsWith("请按住滑块,拖动到最右边")) {
				// action.moveByOffset(step,0); ///< 每次x偏移固定的量,y不变
				int y = 0; /// < 每次x,y偏移在一个范围内随机,模拟人工操作.
				do {
					y = (int) (Math.random() * 10);
					if (y % 2 == 0)
						y *= -1;
					yOffset += y;
					if (Math.abs(yOffset) < 10)
						break;
				} while (true);
				int x = (int) (Math.random() * step);
				action.moveByOffset(x, y).perform();
				offset += x;
				if (x > 0)
					waitTime(2000);
				System.out.println(MessageFormat.format("x_offset={0},y_offset={1}", offset, yOffset));
				continue;
			}
			if (text.startsWith("哎呀")) {
				System.out.println(text); /// < 程序进入这里,提示"哎呀,出错了,点击刷新再来一次"
				/// < 刷新滑块
				driver.findElement(By.xpath("//*[@id='nocaptcha']/div/span/a")).click();


				// break;
				/// < 模拟刷新后再次尝试
				action = new Actions(driver);
				slider = driver.findElement(By.xpath("//*[@id='nc_1_n1z']"));
				action.clickAndHold(slider);
				offset = 200;
				yOffset = 0;
				action.moveByOffset(offset, 0).perform();
				continue;
			}
			flag = 1;
		} while (flag == 0);


		if (flag == 1) {
			driver.findElement(By.id("J_SubmitStatic")).click();
			/// < 检查页面是否跳转
			String newUrl = driver.getCurrentUrl();
		}
		driver.close();
	}

结果:
.重复测试过程中,始终出现"哎呀,出错了,点击刷新再来一次"
 

4.3测试3

特点:
.试图利用本地的cookie.


关于利用cookie,
(1)cookie来源
在chrome浏览器中打开登录页面,登录成功后通过开发者工具复制出cookies内容保存在磁盘文件中.这个文件是可以被网页数据抓取程序使用的,只是会过期.这也是此试验的原因.
(1)在导航到页面(driver.get)之前不能设置cookie.

driver.manage().addCookie(c);
driver.get(url);

addCookie产生异常:
org.openqa.selenium.WebDriverException: unable to set cookie

https://github.com/detro/ghostdriver/issues/178
其中,关于WebDriver的规范描述如下:

After discussing with @AutomatedTester, I filed an issue against the WebDriver W3C specs https://www.w3.org/Bugs/Public/show_bug.cgi?id=20975

The correct behaviour is to FORBID setting cookies against a Session that has not navigated to a concrete domain.

Firefox behaviour is wrong.

意思可能是在导航到一个具体的域之前,禁止对session设置cookies,是W3C关于WebDriver的规范。而Firefox没有遵守这一点。

(2)driver.get打开网页后,从cookie文件中读取,再driver.manage().addCookie(c).
   driver.get(url);
   后增加以下代码

	load(cookiesFile);
	for (Entry<String,Cookie> entry : cookies.entrySet()) {
		driver.manage().addCookie(entry.getValue());
	}

相关代码:

	static IdentityHashMap<String, Cookie> cookies;
	static void load(String cookiesFileName) {
		cookies = new IdentityHashMap<String, Cookie>();
		try {
			File file = new File(cookiesFileName);
			InputStreamReader isr = new InputStreamReader(new FileInputStream(file));


			BufferedReader bufferedReader = new BufferedReader(isr);
			String line = null;
			while ((line = bufferedReader.readLine()) != null) {
				String[] v = line.split("\\s+");
				String s = v[4].replace("Z", " UTC");
				Date d = null;
				SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS Z");
				try {
					d = sdf.parse(s);
				} catch (ParseException e) {
					e.printStackTrace();
				}
				Cookie c = new Cookie(v[0], v[1], v[2], v[3], d);
				cookies.put(v[0], c);
			}
			isr.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

问题依旧!


(3)document.cookie设置cookie

来自下文的启示:
https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/1227
Another work around is to just set cookies via javascript until addCookie is fixed.
代码如下:

		driver.get(url);
		load(cookiesFile);
		JavascriptExecutor executor = (JavascriptExecutor) driver;
		String js = "document.cookie=\"";
		for (Entry<String, Cookie> entry : cookies.entrySet()) {
			js += String.format("%s=%s; ", entry.getKey(), entry.getValue().getValue());
		}
		js += "\"";
		executor.executeScript(js);

cookie之间用分号+空格分隔
问题依旧!

(4)直接利用chrome的cookies
Can I run Selenium ChromeDriver with cookies from actual Chrome installation?
https://stackoverflow.com/questions/34179420/can-i-run-selenium-chromedriver-with-cookies-from-actual-chrome-installation
    

ChromeOptions options = new ChromeOptions();
Options.addArguments("user-data-dir=C:/Users/Think/AppData/Local/Google/Chrome/User Data");

运行产生异常:
Exception in thread "main" org.openqa.selenium.WebDriverException: unknown error: failed to write prefs file

对此类异常,网上有的说法:
1.chrome安装所在磁盘空间:解决方法有转到其它磁盘用mklink连接; 清理临时文件
2. 2个并发的chromedriver不能使用相同的user-data-dir

My solution was to specify option user-data-dir. Two concurrent Chromedriver should not use same user data directory

我的情况是:磁盘肯定有空间; 本机已kill所有chromedriver,chrome进程----仍旧是报错!

user-data-dir更换到其它目录是可以消除此错误的,但违背了利用相同环境的初衷.

3.有的说不同的版本表现不同:
chromedriver 2.12.301324没有此问题,但2.15则重现了.
https://stackoverflow.com/questions/35458272/selenium-failed-to-write-prefs-file
 

Exception "unknown error: failed to write prefs file" is thrown when same user-data-dir and profile is used by two chrome instances in parallel.
This is reproducible in chromedriver:2.15

I am using chromedriver 2.12.301324 and do not have this problem.

网上说chromedriver版本过期,不支持chrome。---我的都是最新版本的,且匹配。
https://stackoverflow.com/questions/21001652/chrome-driver-error-using-selenium-unable-to-discover-open-pages

 

5.前端代码

(1)拖动滑块响应
验证需要与服务器验证,请求url:
https://cf.aliyun.com/nocaptcha/analyze.jsonp

主要代码:
https://g.alicdn.com/sd/ncpc/nc.js?t=2018062922

调用堆栈:

(anonymous) (index.js:formatted:533)
jsonp (nc.js?t=2018062922:formatted:1468)
waitForUmx (nc.js?t=2018062922:formatted:4250)
onScaleReady (nc.js?t=2018062922:formatted:4296)
a (nc.js?t=2018062922:formatted:4729)
r (nc.js?t=2018062922:formatted:4743)

jsonp发起调用.
返回内容如下:

jsonp_030083264854398717({
    "result": {
        "csessionid": "01JMJ3tV_LVnWLUTbouHo5IxXzjp5ZZQYVK4OmuIOT6ThqUrKTCoWLhmuVdl5RNc25usTewr2VuQ3GRULoUGbBvSN97axihY17TVZ4ZqHfGtKFI_0nxJpkPm-CPuYkq9aUzkG0yuDQ3-PsoKvbRbSI8BGVZ2mBOoLq1jkJWTPRlUfICsAjhUS6YghmCEuKVS2wCkp4_yY6dw3nhAQyKoRpU7PJCYdFAnBzQz1IvVs5ozA-hRzK9zSC7WIXgWL-bga-Vyleni8r6UQuO3-lOsEzN-1ZHfe45wd8Pg5td_7d-5Oq7jEk4Tr5cVW_tpA5xTvy1b_6vkGFVhIqCduPjV0uS0knk_BgXOiCZ4MaVegvn4mb6xO2lZmK5tDapwFM4jOd",
        "value": "block",
        "code": 300
    },
    "success": true
});			

其中result.code=300表示错误.
onScaleReadyCallback(nc.js中)根据code处理.

(2)错误提示信息
nc.js 第721行:
中文是采用unicode编码保存的
   部分内容如下:

	cn: {
		_yesTEXT: "\u9a8c\u8bc1\u901a\u8fc7", --- 验证通过
		_Loading: "\u52a0\u8f7d\u4e2d",  ---加载中
		_errorServer: "\u670d\u52a1\u5668\u9519\u8bef\u6216\u8005\u8d85\u65f6",
		_error300: ["\u54ce\u5440\uff0c\u51fa\u9519\u4e86\uff0c\u70b9\u51fb", d, "\u5237\u65b0", "\u518d\u6765\u4e00\u6b21"],
		---哎呀,出错了,点击
	}

tw: en:分别为台湾繁体,英文
 

(3)刷新

debugger:VM1320的内容:

noCaptcha.reset(1)

noCaptcha.reset的代码在:

https://g.alicdn.com/sd/ncpc/nc.js?t=2018062812:formatted

	r.reset = function(e) {
		var t = r.getByIndex(e);
		t ? t.reset() : window.outer_nc_list && window.outer_nc_list[e].reset()
	}
	
	reset: function() {
		this.__nc_afterUM = !1,
		win.UA_Opt && (UA_Opt.Token = (new Date).getTime() + ":" + opt.token);
		var e;
		opt.renderTo && opt.appkey && opt.token && (e = _.id(opt.renderTo),
		e && util.addClass(el_render_to, "nc-container"),
		e.innerHTML = '<div id="' + nc_prefix + 'nocaptcha"><div id="' + nc_prefix + 'wrapper" class="nc_wrapper"><div id="' + nc_prefix + '_n1t_loading" class="nc_scale"><div id="' + nc_prefix + '_bg" class="nc_bg" style="width: 0;"></div><div id="' + nc_prefix + '_scale_text_loading" class="scale_text">' + language[opt.language]._Loading + loading_circle_html + "</div></div></div></div>",
		"undefined" == typeof win.acjs ? this.loaduab() : (UA_Opt.LogVal = "_n",
		this.initUaParam(),
		UA_Opt.Token = (new Date).getTime() + ":" + opt.token,
		UA_Opt.reload && UA_Opt.reload()),
		this.afterUA())
	},

6.结论

试验失败,没有实现自动登录的预期,无法绕过滑块验证.

https://www.zhihu.com/question/35538123

该文提及破解难度,需要解析大量的js代码,而且算法经常改变。

关于淘宝UA算法分析的资料:
http://livezingy.com/ua_inputid-in-taobao-ua/
http://livezingy.com/simple-understanding-about-taobao-ua/

遗留问题有:
.jsonp调用是怎么发生的? XHR请求,但没跟踪到ajax调用。
.jsonp请求的数据都有哪些,来源是什么,有什么算法处理
.同一个登录页面,直接在chrome打开和chromedriver打开,效果不一样,前者有cookies记录的用户名,密码信息,且没有滑块;后者则没有用户,密码,有滑块.是什么原因?

已没有新的思路继续,就此打住,不再浪费精力了.

7.其它HOWTO

  • 设置为Headless模式
Options.addArguments("--headless");
  • 屏蔽"Chrome is being controlled by automated test software "
		ChromeOptions options = new ChromeOptions();
		options.addArguments("disable-infobars");
  • 使用RemoteWebDriver
	ChromeDriverService service = new ChromeDriverService.Builder().usingDriverExecutable(  
				new File(chromePath+"chromedriver.exe")) .usingAnyFreePort().build();  
	service.start();
	ChromeOptions options = new ChromeOptions();
	options.addArguments("--headless");
	DesiredCapabilities capabilities = DesiredCapabilities.chrome();
	capabilities.setCapability(ChromeOptions.CAPABILITY, options);

	WebDriver driver = new RemoteWebDriver(service.getUrl(),  capabilities);  

猜你喜欢

转载自blog.csdn.net/wherwh/article/details/80892535