Ajax技术——跨域解决方案及抓取领域应用

1Ajax定义

Ajax:(Asynchronous JavaScript and XML)用JavaScript执行异步网络请求。

1.1Ajax技术带来的改变

  • 一次HTTP请求对应一个页面
  • 异步数据刷新

Ajax技术包含的内容:

  1. 使用CSS和XHTML来表示。
  2. 使用DOM模型来交互和动态显示。
  3. 使用XMLHttpRequest来和服务器进行异步通信。
  4. 使用javascript来绑定和调用。

1.2利用XMLHttpRequest发送请求

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

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

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

    request.onreadystatechange = function () { // 状态发生变化时,函数被回调
        if (request.readyState === 4) { // 成功完成
            // 判断响应结果:
            if (request.status === 200) {
                // 成功,通过responseText拿到响应的文本:
                success(request.responseText);
            } else {
                // 失败,根据响应码判断失败原因:
                fail(request.status);
            }
        } else {
            // HTTP请求还在继续...
        }
    }

    // 发送请求
    request.open('GET', '/api/categories');
    // request.open('POST', '/api/categories');
    request.send();

    alert('请求已发送,请等待响应...');
})();

从中我们可以看出XMLHttpRequest对象主要包括以下属性:

  • onreadystatechange 回调函数
  • readyState 网络发起和结束的状态
    0: 请求未初始化
    1: 服务器连接已建立
    2: 请求已接收
    3: 请求处理中
    4: 请求已完成,且响应已就绪
  • status 网络响应的状态
    200: "OK"
    404: 未找到页面
  • open 规定请求的类型、URL 以及是否异步处理请求
  • send 将请求发送到服务器,post可支持入参

2AJAX跨域请求的问题

因为浏览器的同源策略导致的,默认情况下,JavaScript在发送AJAX请求时,URL的域名必须和当前页面完全一致。
一致的含义:

那么问题就来了,不是所有的资源或数据都会缓存在当前同源服务器下面,即使通过代理或缓存服务器的策略。
如何解决跨域访问的问题?
由于技术实现和应用场景原因,存在许多js跨域请求的解决方案,下面说我应用过的几种解决方案。

2.1JSONP

特点:

  • 只能用GET请求
  • 返回JavaScript

原理:利用了浏览器允许跨域引用JavaScript资源(场景,例如引用CDN下的第三方js依赖)。

    //动态添加<script>...</script>标签
    var js = document.createElement('script'),
        head = document.getElementsByTagName('head')[0];
    js.src = 'http://example.com/abc.js';
    head.appendChild(js);

执行以上方法后,浏览器拉取http://example.com/abc.js源码:

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

在当前JS中增加回调函数

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提供的方式可以较为方便的实现jsonp。前提是,对应的源有相应的后端实现,即提供GET方式的请求和规范的JavaScript返回。

值得注意的是,通过get请求的目标源文件采用的是fetch方式,而不是XMLHttpRequest对象,所以在浏览器network中XHR里找不到该请求。JSONP真正意义上,不属于ajax的技术范畴。

2.2CORS

CORS:全称Cross-Origin Resource Sharing,是HTML5规范定义的如何跨域访问资源(前提是,浏览器支持HTML5)。
1简单请求

CORS

当ajax通过浏览器发出跨域请求,浏览器收到响应后检查header中Access-Control-Allow-Origin是否包含当前域(origin)。如果有,则跨域成功。

2预检测请求
PUT、DELETE以及其他类型如application/json的POST请求,将会进入以下流程。
先发出OPTIONS请求询问服务端支持的请求方式;
确认成功后,进行数据的请求。

cors

请参考https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

2.3JS-Native客户端代理

原理很简单,因为浏览器有同源限制,然后客户端、后端并没有,我们是可以在代理对象(客户端或后端集群)上收到远程服务的响应。
因此,通过将前端的请求头和请求body等参数封装后,提交给客户端进行抓取,从而达到跨域的目的。

代理请求的模型.png
            mxSaveRequest({//此处调用js-native接口mxSaveRequest将请求发送给客户端
                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对象用于接受跨域请求的参数
            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);//回调js请求的接口,给js回调的数据需要base64加密
                    } catch (Throwable e) {
                        ErrorHandle.b("sendRequest fail", e);
                    }
                }
            }).start();
        } catch (Throwable e) {
            ErrorHandle.b("sendRequest fail1", e);
        }
    }
    //请求的数据结构
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 认证信息发送身份凭证。一般而言对于跨域 XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息——即cookies。如果要发送凭证信息,需要设置 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的详细参数,请使用插件。

2.3.3携带cookie的修改参数

ajax跨域中直接设置请求头是不允许,我们只能在发送前修改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抓取的应用场景——芝麻分、花呗破解方案

3.1芝麻分接口获取

image.png

18年中旬,基本完成支付宝、淘宝电商抓取的全部技术栈,其中对前端抓取最常见的就是ajax的跨域请求。


image.png

芝麻分的请求就是通过jsonp进行跨域的,支付宝服务端支持callback参数,通过自定义function name可以在js端收到请求响应回调。
我们注册了一个名为mtopjsonp1的方法用来接受js对象。

3.2花呗接口获取

ajax请求的代码,其中有一次jsonp请求和一次CORS简单跨域请求(响应头包含:access-control-allow-credentials: true),最后的结果会在客户端保存。


image.png

花呗的请求结果:


image.png

CORS跨域二次请求方案在老版本的芝麻分和借呗,后端接口提供过,首先会发送一次option请求获取后端支持跨域的操作get、post等,然后通过相应的请求获取数据。

当然以上讨论的都是前端抓取的技术方案,如果在接口已经破解的情况下,只需要客户端或后端拿到cookie后,直接对服务端发起请求就可以拿到数据了,后端用python实现回更简单。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容