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