根據同源策略,ajax在非同源的情況下的訪問是受限的,為解決跨域交互的問題,我們會想到利用jsonp 或者 Iframe 的 window.name 來傳輸數據。如果對兩個域都有控制權,我們還會使用window.domain 使非同源的交互成為可能。 抑或是用代理頁面這種中間層來傳遞數據等等。
跨域訪問的方法很多,根據自己的需求來選擇合適的方案。
最近,公司有個業務,抓取一個安全性很高的網站的數據,這個網站綁定了機器上的一些物理信息以及IP地址等,一個賬號只能在一台機上面運行。一般而言,抓取數據,使用服務器代碼(C#,java,ruby 等都可以),將頁面下載下分析就可以了,也不會存在跨域訪問的問題。然而,由於安全性的問題,這個網站拒絕一般情況下頁面下載的請求。所以這個方案不可行。
被抓取頁面的結構(超鏈接列表對應頁面的內容是我們想抓取的)
此方案不可行,只能換個解決方案:用一個頁面,嵌套被抓去的頁面,主頁面再讀取Iframe的內容。這個方案咋乍看起來順理成章,然而,在跨域的情況下,主頁面無法操作Iframe的內容。此方案也流產了,原因是對於同源策略的不了解。
最終方案:
1. 登陸系統,以獲得訪問權限;
2. 往頁面中注入 js ,以獲得對頁面的操控權;
3. 獲取超鏈接連表,放入隊列中,在頁面中生成Ifream,指向超鏈接,解決跨域訪問的問題;
4. 再抓取Ifream加載的內容,以jsonp的形式發回服務器;
在我上一篇博文中,我們知道怎么向一個頁面中注入js,可以使用下面的代碼:

1 javascript: void((function() { 2 var d = document, 3 e = d.createElement("script"); 4 e.setAttribute("charset", "UTF-8"); 5 e.setAttribute("src", "the js's url" + Math.floor(new Date / 1E7)); 6 d.body.appendChild(e) 7 })());
將代碼保存到收藏夾,然后打開想注入的頁面,點擊該收藏就OK了。
注入完js后,我們對現有頁面有了絕對控制權,當然,順帶也解決了跨域訪問的問題(所有iframe都在受控的頁面中生成,而iframe指向的頁面跟主頁面是同域的)。這樣我們便可以很好的操作iframe生成的dom對象了。當然,剛開始要拿frame生成的對象時,總是報錯“對象為空或不存在”,其實這跟iframe的生命周期有關,簡單來說,iframe相當於一個新的瀏覽器窗口,想要操作窗口內的對象,得確保該對象已經加載完成。下面的代碼,便是判斷iframe是否加載結束:

1 if (iframeobj.attachEvent) { 2 iframeobj.attachEvent("onload", 3 function() { 4 alert(document.frames[iframename].document.body.innerHTML); 5 }); 6 } else { 7 iframeobj.onload = function() { 8 alert(document.frames[ifreamname].document.body.innerHTML); 9 }; 10 } 11 }
當ifream加載結束,我們才可拿到iframe的window對象或document對象,這時我們才可以為所欲為。
使用iframe自動抓取數據的過程中遇到了一個問題,就是iframe會在頁面加載時執行一些腳本,例如彈出提示框,這樣,便會阻塞了主線程的運行。如何解決這個問題?首先想到的應該就是重寫iframe的alert事件,代碼如下:

1 document.getElementById(iframeid).contentWindow.alert = function() { 2 return null; 3 } 4 或者 5 window.frames[iframename].window.alert = function() { 6 return null; 7 }
iframe的alert事件重寫結束了,現在又遇到了一個問題:服務器后台代碼與javascript代碼加載順序的問題!也就是說,在我重寫子頁面的alert事件之前,已經先執行了一個alert提示,因而無法屏蔽掉它!!郁悶。換個想法,直接用noscript標簽來禁用iframe的所有腳本,然而在動態生成iframe的時候會報錯。后來,想起了富文本編輯器,也是用iframe,會屏蔽腳本,而且還能獲取到內容。其實,富文本編輯器的原理很簡單,就是打開iframe的designMode和editMode,所以,在創建iframe的時候,我加了以下的代碼:
1 document.getElementById(iframeid).contentWindow.document.designMode = 'On';
當然,這不是真正意義上的解決iframe彈提示框的問題,這樣做的應用場景是:想獲取頁面的內容,卻不想被提示框阻塞。
現在,想抓取的內容抓取到了,把內容發送回服務器就完成了,剛開始的做法,動態創建超鏈接a,拼接帶參的url指向服務器,模擬點擊事件觸發超鏈接。這樣可以解決問題,但是我抓取的內容有20W之多,每次抓取在3000內,我不可能彈出3000個瀏覽器窗口!!!轉眼想想,我不是在頁面中創建了很多iframe么,把超鏈接的target指向iframe,這樣便可以解決這個問題,而且,我們還對頁面的iframe有絕對控制器。但是問題又來了,這時,target指向iframe,而超鏈接的url又與頁面不同域,在IE下會報錯,而抓取的網站只能在IE下運行!!!萬惡的IE啊!!!
行不通,就使用jsonp來解決跨域交互的問題。根據同源策略,瀏覽器會隔離各個域的資源,使它們間資源不共享,不能相互訪問。然而,有個特例,那便是script!!同源策略允許加載不同域的javascript!!創建jsonp代碼如下(當然,你可以在參數中加入回調函數):

1 var _jsonpscript = document.createElement("script"); 2 _jsonpscript.setAttribute("type", "text/javascript"); 3 _jsonpscript.setAttribute("id", "jsonpscript"); 4 _jsonpscript.setAttribute("src", serviceUrl? +參數(用&分隔)); 5 document.body.appendChild(_jsonpscript);
至此,完成了從抓取到回傳的整個過程。(本人的javascript能力還處於最低級的探索階段,如果有什么寫的不好或者錯誤的地方,請不吝指出,謝謝)