19

我正在使用Selenium WebDriver来抓取一个无限滚动的网站(仅举例来说,我也会抓取其他网站!)。

问题陈述:

向下滚动无限滚动页面,直到使用 Selenium Web 驱动程序停止加载内容。

我的方法: 目前我正在这样做-

第一步:滚动到页面底部

JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("javascript:window.onload=toBottom();"+
                        "function toBottom(){" +
                        "window.scrollTo(0,Math.max(document.documentElement.scrollHeight," +
                        "document.body.scrollHeight,document.documentElement.clientHeight));" +
                "}");

然后我等待一段时间让 Ajax 请求像这样完成 -

第 2 步:显式等待 Ajax 请求结束

线程.sleep(1000);

然后我给出另一个 java 脚本来检查页面是否可滚动

第三步:检查页面是否可滚动

//Alternative to document.height is to be used which is document.body.clientHeight
//refer to https://developer.mozilla.org/en-US/docs/DOM/document.height

    if((Long)js.executeScript("return " +
                                "(document.body.clientHeight-(window.pageYOffset + window.innerHeight))")>0)

如果上述条件为真,那么我重复第 1 步到第 3 步,直到第 3 步中的条件为假。

问题: 我不想Thread.sleep(1000);在第 2 步中给出,而是我想使用 Java Script 检查后台 Ajax 请求是否结束,然后在第 3 步中的条件为 true 时进一步向下滚动。

PS:我不是页面的开发者,所以我无法访问运行页面的代码,我只能在网页中注入 java 脚本(如步骤 1 和 3)。而且,我必须为无限滚动期间使用 Ajax 请求的任何网站编写通用逻辑。

我会很感激有人可以在这里抽出一些时间!

编辑:好的,在挣扎了 2 天之后,我发现我通过 Selenium WebDriver 抓取的页面可以包含任何这些JavaScript 库,我将不得不根据不同的库进行池化,例如,如果使用jQuery api的 web 应用程序,我可能正在等待

(Long)((JavascriptExecutor)driver).executeScript("return jQuery.active")

返回一个零。

同样,如果 Web 应用程序正在使用Prototype JavaScript 库,我将不得不等待

(Long)((JavascriptExecutor)driver).executeScript("return Ajax.activeRequestCount")

返回一个零。

现在问题是如何编写可以处理大多数可用 JavaScript 库的通用代码?

我在实施这个过程中面临的问题 -

1 . 如何找到 Web 应用程序中正在使用的 JavaScript 库(在 Java 中使用 Selenium WebDriver),以便我可以编写相应的等待方法?目前,我正在使用这个

代码

2 . 这样,我将不得不为单独的 JavaScript 库编写多达 77 个方法,因此,我还需要一种更好的方法来处理这种情况。

简而言之,我需要通过 Selenium Web Driver 的 java 实现确定浏览器是否正在使用或不使用任何 JavaScript 库进行任何调用(Ajax 或简单)

PS:Chorme 的JavaScript Lib 检测器 和 Firefox 的JavaScript 库检测器有附加组件,可以检测正在使用的 JavaScript 库。

4

3 回答 3

11

对于在无限滚动期间使用 Ajax 响应并使用 jQuery API(或其他操作)的网页,在开始打开网页之前。

    //Inject the pooling status variable
    js.executeScript("window.status = 'fail';");

    //Attach the Ajax call back method
    js.executeScript( "$(document).ajaxComplete(function() {" +
    "status = 'success';});");

第 1 步:将保持与原始问题相同

第 2 步合并以下脚本(这是一个不需要Thread.Sleep()并使逻辑更加动态的脚本)

String aStatus = (String)js.executeScript("return status;");

                        if(aStatus!=null && aStatus.equalsIgnoreCase("success")){
                            js.executeScript("status = 'fail';");
                            break poolingLoop;
                        }

第3步:现在不需要!

结论:没必要给生硬的 Thread.sleep(); 一次又一次地使用 Selenium WebDriver!

只有在 Web 应用程序中使用了 jQuery api 时,这种方法才有效。

编辑: 根据@jayati给出的链接,我注入了javascript-

Javascript一:

//XMLHttpRequest instrumentation/wrapping
var startTracing = function (onnew) {
    var OldXHR = window.XMLHttpRequest;

    // create a wrapper object that has the same interfaces as a regular XMLHttpRequest object
    // see http://www.xulplanet.com/references/objref/XMLHttpRequest.html for reference on XHR object
    var NewXHR = function() {
        var self = this;
        var actualXHR = new OldXHR();

        // private callbacks (for UI):
        // onopen, onsend, onsetrequestheader, onupdate, ...
        this.requestHeaders = "";
        this.requestBody = "";

        // emulate methods from regular XMLHttpRequest object
        this.open = function(a, b, c, d, e) { 
            self.openMethod = a.toUpperCase();
            self.openURL = b;
            ajaxRequestStarted = 'open';

            if (self.onopen != null && typeof(self.onopen) == "function") { 
                self.onopen(a,b,c,d,e); } 
            return actualXHR.open(a,b,c,d,e); 
        }
        this.send = function(a) {
            ajaxRequestStarted = 'send';

            if (self.onsend != null && typeof(this.onsend) == "function") { 
                self.onsend(a); } 
            self.requestBody += a;
            return actualXHR.send(a); 
        }
        this.setRequestHeader = function(a, b) {
            if (self.onsetrequestheader != null && typeof(self.onsetrequestheader) == "function") { self.onsetrequestheader(a, b); } 
            self.requestHeaders += a + ":" + b + "\r\n";
            return actualXHR.setRequestHeader(a, b); 
        }
        this.getRequestHeader = function() {
            return actualXHR.getRequestHeader(); 
        }
        this.getResponseHeader = function(a) { return actualXHR.getResponseHeader(a); }
        this.getAllResponseHeaders = function() { return actualXHR.getAllResponseHeaders(); }
        this.abort = function() { return actualXHR.abort(); }
        this.addEventListener = function(a, b, c) { return actualXHR.addEventListener(a, b, c); }
        this.dispatchEvent = function(e) { return actualXHR.dispatchEvent(e); }
        this.openRequest = function(a, b, c, d, e) { return actualXHR.openRequest(a, b, c, d, e); }
        this.overrideMimeType = function(e) { return actualXHR.overrideMimeType(e); }
        this.removeEventListener = function(a, b, c) { return actualXHR.removeEventListener(a, b, c); }

        // copy the values from actualXHR back onto self
        function copyState() {
            // copy properties back from the actual XHR to the wrapper
            try {
                self.readyState = actualXHR.readyState;
            } catch (e) {}
            try {
                self.status = actualXHR.status;
            } catch (e) {}
            try {
                self.responseText = actualXHR.responseText;
            } catch (e) {}
            try {
                self.statusText = actualXHR.statusText;
            } catch (e) {}
            try {
                self.responseXML = actualXHR.responseXML;
            } catch (e) {}
        }

        // emulate callbacks from regular XMLHttpRequest object
        actualXHR.onreadystatechange = function() {
            copyState();

            try {
                if (self.onupdate != null && typeof(self.onupdate) == "function") { self.onupdate(); } 
            } catch (e) {}

            // onreadystatechange callback            
            if (self.onreadystatechange != null && typeof(self.onreadystatechange) == "function") { return self.onreadystatechange(); } 
        }
        actualXHR.onerror = function(e) {

            ajaxRequestComplete = 'err';
            copyState();

            try {
                if (self.onupdate != null && typeof(self.onupdate) == "function") { self.onupdate(); } 
            } catch (e) {}

            if (self.onerror != null && typeof(self.onerror) == "function") { 
                return self.onerror(e); 
            } else if (self.onreadystatechange != null && typeof(self.onreadystatechange) == "function") { 
                return self.onreadystatechange(); 
            }
        }
        actualXHR.onload = function(e) {

            ajaxRequestComplete = 'loaded';
            copyState();

            try {
                if (self.onupdate != null && typeof(self.onupdate) == "function") { self.onupdate(); } 
            } catch (e) {}

            if (self.onload != null && typeof(self.onload) == "function") { 
                return self.onload(e); 
            } else if (self.onreadystatechange != null && typeof(self.onreadystatechange) == "function") { 
                return self.onreadystatechange(); 
            }
        }
        actualXHR.onprogress = function(e) {
            copyState();

            try {
                if (self.onupdate != null && typeof(self.onupdate) == "function") { self.onupdate(); } 
            } catch (e) {}

            if (self.onprogress != null && typeof(self.onprogress) == "function") { 
                return self.onprogress(e);
            } else if (self.onreadystatechange != null && typeof(self.onreadystatechange) == "function") { 
                return self.onreadystatechange(); 
            }
        }

        if (onnew && typeof(onnew) == "function") { onnew(this); }
    }

    window.XMLHttpRequest = NewXHR;

}
window.ajaxRequestComplete = 'no';//Make as a global javascript variable
window.ajaxRequestStarted = 'no';
startTracing();

Javascript 两个:

var startTracing = function (onnew) {
    window.ajaxRequestComplete = 'no';//Make as a global javascript variable
    window.ajaxRequestStarted = 'no';

    XMLHttpRequest.prototype.uniqueID = function() {
        if (!this.uniqueIDMemo) {
            this.uniqueIDMemo = Math.floor(Math.random() * 1000);
        }
        return this.uniqueIDMemo;
    }

    XMLHttpRequest.prototype.oldOpen = XMLHttpRequest.prototype.open;

    var newOpen = function(method, url, async, user, password) {

        ajaxRequestStarted = 'open';
        /*alert(ajaxRequestStarted);*/
        this.oldOpen(method, url, async, user, password);
    }

    XMLHttpRequest.prototype.open = newOpen;

    XMLHttpRequest.prototype.oldSend = XMLHttpRequest.prototype.send;

    var newSend = function(a) {
        var xhr = this;

        var onload = function() {
            ajaxRequestComplete = 'loaded';
            /*alert(ajaxRequestComplete);*/
        };

        var onerror = function( ) {
            ajaxRequestComplete = 'Err';
            /*alert(ajaxRequestComplete);*/
        };

        xhr.addEventListener("load", onload, false);
        xhr.addEventListener("error", onerror, false);

        xhr.oldSend(a);
    }

    XMLHttpRequest.prototype.send = newSend;
}
startTracing();

并检查 java 代码中的状态变量的状态ajaxRequestStarted, ajaxRequestComplete,可以确定 ajax 是启动还是完成。

现在我有办法等到 Ajax 完成,我还可以找到 Ajax 是否在某些操作上被触发

于 2012-08-01T10:42:20.583 回答
7

方法1

您的方法很好,只需进行一些更改即可:

第 1 步:改进此步骤,使用 . 定期调用 toBottom 函数window.setInterval(c >= totalcount)随叫随到_window.clearInterval

Setp 2:不是检查页面是否可滚动,而是检查(c >= totalcount). 并且此条件每 200 毫秒返回一次,直到(c >= totalcount)返回 true。

仅供参考:如果第 1 步不适用于所有浏览器,那么您可以参考 Tata-Nano-Reviews-925076578.js 的第 5210 行并通过c变量检查调用它。

方法2

转到jQuery API并键入“ajax”。您可以找到一些可用于 ajax 请求的回调处理程序。

可能在发送请求之前和正确接收请求之后设置一个变量。

在这两者之间使用你原来的定期滚动到底部的方法,除非你不能再滚动。此时清除区间变量。

现在,定期检查该区间变量是否为空。Null 表示您已到达底部。

于 2012-08-01T09:16:45.210 回答
0

我们必须解决同样的问题,并使用一个长的 Javascript 函数进行管理。只需添加检查即可查看未定义的库。

PS 感谢您给我一个关于如何检查正在进行的原型请求的简单答案!

例如。处理 JQuery 和 XHR/原型

var jsExecutor = /*Get your WebDriverInstance*/ as IJavaScriptExecutor;
while(/*your required timeout here*/)
{
    var ajaxComplete = 
      jsExecutor.ExecuteScript("return ((typeof Ajax === 'undefined') ||   
      Ajax.activeRequestCount == 0) && ((typeof jQuery === 'undefined') || $.active == 0)");
    if (ajaxIsComplete)
      return
}
于 2016-02-04T01:36:16.017 回答