Ajax技術(shù)——跨域解決方案及抓取領(lǐng)域應(yīng)用

1Ajax定義

Ajax:(Asynchronous JavaScript and XML)用JavaScript執(zhí)行異步網(wǎng)絡(luò)請求。

1.1Ajax技術(shù)帶來的改變

  • 一次HTTP請求對應(yīng)一個頁面
  • 異步數(shù)據(jù)刷新

Ajax技術(shù)包含的內(nèi)容:

  1. 使用CSS和XHTML來表示。
  2. 使用DOM模型來交互和動態(tài)顯示。
  3. 使用XMLHttpRequest來和服務(wù)器進行異步通信。
  4. 使用javascript來綁定和調(diào)用。

1.2利用XMLHttpRequest發(fā)送請求

(function () {
    function success(text) {
        alert('success' + text);
    }

    function fail(code) {
        alert('fail' + code);
    }

    var request = new XMLHttpRequest(); // 新建XMLHttpRequest對象

    request.onreadystatechange = function () { // 狀態(tài)發(fā)生變化時,函數(shù)被回調(diào)
        if (request.readyState === 4) { // 成功完成
            // 判斷響應(yīng)結(jié)果:
            if (request.status === 200) {
                // 成功,通過responseText拿到響應(yīng)的文本:
                success(request.responseText);
            } else {
                // 失敗,根據(jù)響應(yīng)碼判斷失敗原因:
                fail(request.status);
            }
        } else {
            // HTTP請求還在繼續(xù)...
        }
    }

    // 發(fā)送請求
    request.open('GET', '/api/categories');
    // request.open('POST', '/api/categories');
    request.send();

    alert('請求已發(fā)送,請等待響應(yīng)...');
})();

從中我們可以看出XMLHttpRequest對象主要包括以下屬性:

  • onreadystatechange 回調(diào)函數(shù)
  • readyState 網(wǎng)絡(luò)發(fā)起和結(jié)束的狀態(tài)
    0: 請求未初始化
    1: 服務(wù)器連接已建立
    2: 請求已接收
    3: 請求處理中
    4: 請求已完成,且響應(yīng)已就緒
  • status 網(wǎng)絡(luò)響應(yīng)的狀態(tài)
    200: "OK"
    404: 未找到頁面
  • open 規(guī)定請求的類型、URL 以及是否異步處理請求
  • send 將請求發(fā)送到服務(wù)器,post可支持入?yún)?/li>

2AJAX跨域請求的問題

因為瀏覽器的同源策略導(dǎo)致的,默認情況下,JavaScript在發(fā)送AJAX請求時,URL的域名必須和當前頁面完全一致。
一致的含義:

那么問題就來了,不是所有的資源或數(shù)據(jù)都會緩存在當前同源服務(wù)器下面,即使通過代理或緩存服務(wù)器的策略。
如何解決跨域訪問的問題?
由于技術(shù)實現(xiàn)和應(yīng)用場景原因,存在許多js跨域請求的解決方案,下面說我應(yīng)用過的幾種解決方案。

2.1JSONP

特點:

  • 只能用GET請求
  • 返回JavaScript

原理:利用了瀏覽器允許跨域引用JavaScript資源(場景,例如引用CDN下的第三方j(luò)s依賴)。

    //動態(tài)添加<script>...</script>標簽
    var js = document.createElement('script'),
        head = document.getElementsByTagName('head')[0];
    js.src = 'http://example.com/abc.js';
    head.appendChild(js);

執(zhí)行以上方法后,瀏覽器拉取http://example.com/abc.js源碼:

refreshPrice({"0000001":{"code": "0000001", ... });

在當前JS中增加回調(diào)函數(shù)

function refreshPrice(data) {
    var p = document.getElementById('test-jsonp');
    p.innerHTML = '當前價格:' +
        data['0000001'].name +': ' + 
        data['0000001'].price + ';' +
        data['1399001'].name + ': ' +
        data['1399001'].price;
}

這樣,我們就通過JSONP完成了跨域請求,得到了返回。

一般,我們采用JQuery提供的方式可以較為方便的實現(xiàn)jsonp。前提是,對應(yīng)的源有相應(yīng)的后端實現(xiàn),即提供GET方式的請求和規(guī)范的JavaScript返回。

值得注意的是,通過get請求的目標源文件采用的是fetch方式,而不是XMLHttpRequest對象,所以在瀏覽器network中XHR里找不到該請求。JSONP真正意義上,不屬于ajax的技術(shù)范疇。

2.2CORS

CORS:全稱Cross-Origin Resource Sharing,是HTML5規(guī)范定義的如何跨域訪問資源(前提是,瀏覽器支持HTML5)。
1簡單請求

CORS

當ajax通過瀏覽器發(fā)出跨域請求,瀏覽器收到響應(yīng)后檢查header中Access-Control-Allow-Origin是否包含當前域(origin)。如果有,則跨域成功。

2預(yù)檢測請求
PUT、DELETE以及其他類型如application/json的POST請求,將會進入以下流程。
先發(fā)出OPTIONS請求詢問服務(wù)端支持的請求方式;
確認成功后,進行數(shù)據(jù)的請求。

cors

請參考https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

2.3JS-Native客戶端代理

原理很簡單,因為瀏覽器有同源限制,然后客戶端、后端并沒有,我們是可以在代理對象(客戶端或后端集群)上收到遠程服務(wù)的響應(yīng)。
因此,通過將前端的請求頭和請求body等參數(shù)封裝后,提交給客戶端進行抓取,從而達到跨域的目的。

代理請求的模型.png
            mxSaveRequest({//此處調(diào)用js-native接口mxSaveRequest將請求發(fā)送給客戶端
                itemName: "bindQuery-" + e + ".json",
                type: "GET",
                url: "https://zht.alipay.com/asset/bindQuery.json?_input_charset=utf-8&providerType=" + e + "&t=" + (new Date).getTime() + "&ctoken=" + (0, y.getCookie)("ctoken"),
                headers: {
                    Accept: "*/*",
                    "Accept-Language": "en-US,en;q=0.8,zh;q=0.6,zh-CN;q=0.4,ja;q=0.2,zh-TW;q=0.2",
                    "Cache-Control": "max-age=0",
                    "X-Requested-With": "XMLHttpRequest",
                    Host: "zht.alipay.com",
                    Referer: "https://zht.alipay.com/asset/bankList.htm",
                    "User-Agent": navigator.userAgent
                },
                encoding: "GBK"
            })

客戶端提供mxSaveRequest的端能力代碼

        @NotProguard
        @JavascriptInterface
        public void mxSaveRequest(final String str) {//注入js對象用于接受跨域請求的參數(shù)
            Logger.json(str);
            WebViewECV3Presenter.this.handler.post(new Runnable() {

                public void run() {
                    WebViewECV3Presenter.call(WebViewECV3Presenter.this, str);
                }
            });
        }


    static /* synthetic */ void call(final WebViewECV3Presenter webViewECV3Presenter, final String str) {
        try {
            new Thread(new Runnable() {

                public void run() {
                    try {
                        JsBaseRequest jsBaseRequest = JsBaseRequest.getJsRequest(str);
                        byte[] response =
                                WebViewECV3Presenter.getbytes(webViewECV3Presenter, jsBaseRequest);//http request獲取請求的接口
                        WebViewECV3Presenter.a(webViewECV3Presenter,/*call back result*/ response, jsBaseRequest);//put
                        // maps jsreq.itemName
                        WebViewECV3Presenter.callback(webViewECV3Presenter, response, jsBaseRequest);//回調(diào)js請求的接口,給js回調(diào)的數(shù)據(jù)需要base64加密
                    } catch (Throwable e) {
                        ErrorHandle.b("sendRequest fail", e);
                    }
                }
            }).start();
        } catch (Throwable e) {
            ErrorHandle.b("sendRequest fail1", e);
        }
    }
    //請求的數(shù)據(jù)結(jié)構(gòu)
public class JsBaseRequest {
    public String type = "";
    public String url = "";
    public String headers = "";
    public String data = "";
    public String itemName = "";
    public String encoding = "UTF-8";
    public int failCode = ErrorCode.INFO_CODE_BASE;
    public String responseId = "";

    public static JsBaseRequest getJsRequest(String str) {
        JsBaseRequest jsBaseRequest = new JsBaseRequest();
        JSONObject jSONObject = null;
        try {
            jSONObject = new JSONObject(str);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        jsBaseRequest.type = jSONObject.optString("type");
        jsBaseRequest.url = jSONObject.optString("url");
        jsBaseRequest.headers = jSONObject.optString("headers");
        jsBaseRequest.data = jSONObject.optString("data");
        jsBaseRequest.itemName = jSONObject.optString("itemName");
        jsBaseRequest.responseId = jSONObject.optString("responseId");
        if (jSONObject.has("encoding")) {
            jsBaseRequest.encoding = jSONObject.optString("encoding");
            if (jsBaseRequest.encoding.equalsIgnoreCase("UTF8")) {
                jsBaseRequest.encoding = "UTF-8";
            }
        }
        if (jSONObject.has("failCode")) {
            jsBaseRequest.failCode = jSONObject.optInt("failCode");
        }
        return jsBaseRequest;
    }
}
//callback JS
webViewECV3Presenter.webViewECV3Fragment.loadUrl("requestFinishCallback('" + jSONObject.toString() + "')");

2.3常見的問題

2.3.1身份憑證

Fetch(JSONP形式) 與 CORS 的一個有趣的特性是——可以基于 HTTP cookies 和 HTTP 認證信息發(fā)送身份憑證。一般而言對于跨域 XMLHttpRequest 或 Fetch 請求,瀏覽器不會發(fā)送身份憑證信息——即cookies。如果要發(fā)送憑證信息,需要設(shè)置 XMLHttpRequest 的某個特殊標志位——withCredentials。

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';
    
function callOtherDomain(){
  if(invocation) {
    invocation.open('GET', url, true);
    invocation.withCredentials = true;
    invocation.onreadystatechange = handler;
    invocation.send(); 
  }
}
2.3.2使用瀏覽器插件net-internals
image.png

遇到這樣的問題想查看header的詳細參數(shù),請使用插件。

2.3.3攜帶cookie的修改參數(shù)

ajax跨域中直接設(shè)置請求頭是不允許,我們只能在發(fā)送前修改cookie

 document.cookie = 'domain=m.taobao.com';

    $.ajax({
        url: trust_url,
        method: 'get',
        xhrFields: {
            withCredentials: true
        },
        crossDomain: true,
        success: function (res) {
            alert("success\n" + JSON.stringify(res));
            alipayAuth(res);
        },
        error: function (res) {
            alert("fail");
        }
    });

3抓取的應(yīng)用場景——芝麻分、花唄破解方案

3.1芝麻分接口獲取

image.png

18年中旬,基本完成支付寶、淘寶電商抓取的全部技術(shù)棧,其中對前端抓取最常見的就是ajax的跨域請求。


image.png

芝麻分的請求就是通過jsonp進行跨域的,支付寶服務(wù)端支持callback參數(shù),通過自定義function name可以在js端收到請求響應(yīng)回調(diào)。
我們注冊了一個名為mtopjsonp1的方法用來接受js對象。

3.2花唄接口獲取

ajax請求的代碼,其中有一次jsonp請求和一次CORS簡單跨域請求(響應(yīng)頭包含:access-control-allow-credentials: true),最后的結(jié)果會在客戶端保存。


image.png

花唄的請求結(jié)果:


image.png

CORS跨域二次請求方案在老版本的芝麻分和借唄,后端接口提供過,首先會發(fā)送一次option請求獲取后端支持跨域的操作get、post等,然后通過相應(yīng)的請求獲取數(shù)據(jù)。

當然以上討論的都是前端抓取的技術(shù)方案,如果在接口已經(jīng)破解的情況下,只需要客戶端或后端拿到cookie后,直接對服務(wù)端發(fā)起請求就可以拿到數(shù)據(jù)了,后端用python實現(xiàn)回更簡單。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容