c# Cefsharp 如何在网站上制作正确的 JavaScript 动作序列

c# Cefsharp how to make correct sequence of JavaScript actions on the web site

这些动作序列与 Thread.Sleep 一起工作,某处在 1 秒内,某处在 2 秒内。我认为使用 Thread.Sleep/Task.Delay 不好。因为它可以在不同的计算机上执行不同的操作。如何在不使用 Thread.Sleep 的情况下执行这些序列? 或者可以使用 Thread.Sleep/Task.Delay?

        private async void ButtonFind_Click(object sender, EventArgs e)
        {

            //Action1
            string jsScript1 = "document.getElementById('story').value=" + '\'' + textFind.Text + '\'';
            await chrome.EvaluateScriptAsync(jsScript1);

            //Action2
            string jsScript2 = "document.querySelector('body > div.wrapper > div.header > div.header44 > div.search_panel > span > form > button').click();";
            await chrome.EvaluateScriptAsync(jsScript2);

            //Action3
            Thread.Sleep(1000); //it is necessary to set exactly 1 seconds
            string jsScript3 = "document.getElementsByTagName('a')[2].click();";
            await chrome.EvaluateScriptAsync(jsScript3);

            //Action4
            Thread.Sleep(2000); //it is necessary to set exactly 2 seconds
            string jsScript4 = "document.querySelector('#dle-content > div.section > ul > li:nth-child(3)').click();";
            await chrome.EvaluateScriptAsync(jsScript4);
        }

我尝试使用任务预期,但它对我没有帮助

...
var task4 = chrome.EvaluateScriptAsync(jsScript4);
task4.Wait();

我也尝试使用 DOM 渲染期望,这也没有帮助

            string jsScript4 = @"
                  if( document.readyState !== 'loading' ) {
                      myInitCode();
                  } else {
                      document.addEventListener('DOMContentLoaded', function () {
                          myInitCode();
                      });
                  }

                  function myInitCode() {
                   var a = document.querySelector('#dle-content > div.section > ul > li:nth-child(3)').click();
                  return a;
                  }
              ";
            
            chrome.EvaluateScriptAsync(jsScript4);

我的添加 (21.04.2022)


在第三个动作中,我使用的是“While”循环而不是 Thread.Sleep 这里算法是正确的,但是由于某些原因,按下应用按钮后,应用挂了

                bool test = false;
                while(test == false)
                {
                    string myScript = @"
                        (function(){
                            var x = document.getElementsByTagName('a')[1].outerText;
                            return x;
                        })();
                        ";
                    var task = chrome.EvaluateScriptAsync(myScript);
                    task.ContinueWith(x =>
                    {
                        if (!x.IsFaulted)
                        {
                            var response = x.Result;
                            if (response.Success == true)
                            {
                                var final = response.Result;
                                if (final.ToString() == textFind.Text)
                                {
                                    MessageBox.Show("You found the link");
                                    test = true;
                                }
                                else
                                {
                                    MessageBox.Show("You do not found the link");
                                }
                            }
                        }
                    }, TaskScheduler.FromCurrentSynchronizationContext());
                }

我的添加 (23.04.2022)


string jsScript1 = "document.getElementById('story').value=" + '\'' + textFind.Text + '\'' + ";"
                + @"
    Promise.resolve()
  .then(() => document.querySelector('body > div.wrapper > div.header > div.header44 > div.search_panel > span > form > button').click())
  .then(() =>  { var target = document.body;
            const config = { 
                childList: true, 
                attributes: true, 
                characterData: true, 
                subtree: true, 
                attributeFilter: ['id'], 
                attributeOldValue: true, 
                characterDataOldValue: true 
            }
            const callback = function(mutations) 
            {
                document.addEventListener('DOMContentLoaded', function(){                    
                if(document.getElementsByTagName('a')[1].innerText=='Troy')
                    {
                        alert('I got that link');
                    }
                }, true);
            };
            const observer = new MutationObserver(callback);
            observer.observe(target, config)});
            ";

            var task1 = chrome.EvaluateScriptAsPromiseAsync(jsScript1);
            task1.Wait();

使用包裹在承诺中的 MutationObserver,使用 EvaluateScriptAsPromiseAsync 评估承诺。也没有帮助。 我得出的结论是 JavaScript 在单击搜索按钮或转到另一个页面后不会保存代码。如何保存 JavaScript code/request 并在单击搜索按钮或转到另一页后继续搜索?

您永远不必在睡眠状态下工作,因为计算机之间的时间会发生变化,即使在同一台计算机上,网页加载所需的时间也可能不同。

我在抓取方面做了很多工作,IMO 管理这个的最佳重点是从 JavaScript 方面工作。您 inject/run 您 JavaScript 填充控件,单击按钮...

有了这个重点,问题是导航会让你失去状态。当您导航到其他页面时,您的 JavaScript 从头开始​​。我通过绑定对象和注入 JavaScript.

循环此共享数据以在 JavaScript 和 C# 之间持续存在

例如,您可以使用一段 JavaScript 代码 运行 操作 1、2 和 3。在单击按钮之前,您可以使用绑定对象告诉您的 C# 代码您将转到第二页。

当您的第二页加载时,您 运行 您的第二页的 JavaScript (您知道该步骤并且可以为您的第 2 页注入 JavaScript 代码)。

在所有情况下,您的 JavaScript 代码必须有某种等待机制。例如,设置一个计时器等待您的控件出现。通过这种方式,您可以 运行 您的 JavaScript 而无需等待页面完全加载(有时这种事件很难管理)。

更新

我的抓取库很大。我将公开您完成工作所需的部分,但您需要自己 assemble。

我们创建一个 BoundObject class:

public class BoundObject
{
    public BoundObject(IWebBrowser browser)
    {
        this.Browser = browser;
    }

    public void OnJavaScriptMessage(string message)
    {
        this.Browser.OnJavaScriptMessage(message);
    }
}

IWebBrowser 是我的自定义浏览器的一个界面,一个管理我所需要的一切的包装器。创建一个浏览器class,比如CustomBrowser,实现这个接口。

创建一个方法来确保您的绑定对象正常工作:

public void SetBoundObject()
{
    // To get events in C# from JavaScript
    try
    {
        var boundObject = new BoundObject();
        this._browserInternal.JavascriptObjectRepository.Register(
        "bound", boundObject, false, BindingOptions.DefaultBinder);

       this.BoundObject = boundObject;
   }
   catch (ArgumentException ex)
   {
       if (!ex.ParamName.Identical("bound"))
       {
           throw;
       }
   }
}

_browserInternal 是 CefSharp 浏览器。导航时,您必须 运行 在每个页面加载时使用该方法。这样做,您在 JavaScript 端有一个 window.bound 对象,带有 onJavaScriptMessage 函数。然后,你可以像这样在 JavaScript 中定义一个函数:

function sendMessage(msg) {
    var json = JSON.stringify(msg);
    window.bound.onJavaScriptMessage(json);
    return this;
};

您现在可以将任何对象发送到您的 C# 应用程序并在您的 CustomBrowser 中使用 OnJavaScriptMessage 方法进行管理。在该方法中,我管理我的自定义消息协议,就像套接字环境或 windows 消息系统中的典型消息协议一样,并生成我在继承 CustomBrowser.classes 中实现的 OnMessage。

使用 CefSharp 浏览器的 ExecuteScriptAsync 将信息发送到 JavaScript 很简单。

更进一步

当我从事紧张的抓取工作时。我用 classes 创建了一些脚本来管理要废弃的整个 Web。例如,我创建了 classes 来进行登录、导航到不同的部分、填写表格……就像我是网站的所有者一样。然后,当页面加载时,我注入我的脚本,我可以在远程网站中使用我自己的 classes 进行抓取......小菜一碟。

我的脚本是嵌入式资源,所以在我的最终可执行文件中。在调试中,我从磁盘读取它们以允许编辑+重新加载+测试,直到我的脚本正常工作。使用 DevTools,您可以在控制台中尝试,直到获得所需的源。然后你添加到你的 JavaScripts classes 并重新加载。

您可以使用 ExecuteScriptAsync 添加简单的 JavaScript,但是对于大文件会出现转义引号的问题...

因此您需要插入整个脚本文件。为此,实施 ISchemeHandlerFactory 以创建和 return IResourceHandler。该资源处理程序必须有一个 ProcessRequestAsync,您会在其中收到一个 request.Url,您可以使用它来对脚本进行区域设置:

  this.ResponseLength = stream.Length;
  this.MimeType = GetMimeType(fileExtension);
  this.StatusCode = (int)HttpStatusCode.OK;
  this.Stream = stream;

  callback.Continue();
  return true;

stream 可能是一个 MemoryStream,您可以在其中写入脚本文件的内容。

由于您的 JavaScript 导致导航,您需要等待新页面加载。

您可以使用类似下面的方法来等待页面加载。

// create a static class for the extension method 
public static Task<LoadUrlAsyncResponse> WaitForLoadAsync(this IWebBrowser browser)
{
    var tcs = new TaskCompletionSource<LoadUrlAsyncResponse>(TaskCreationOptions.RunContinuationsAsynchronously);

    EventHandler<LoadErrorEventArgs> loadErrorHandler = null;
    EventHandler<LoadingStateChangedEventArgs> loadingStateChangeHandler = null;

    loadErrorHandler = (sender, args) =>
    {
        //Actions that trigger a download will raise an aborted error.
        //Generally speaking Aborted is safe to ignore
        if (args.ErrorCode == CefErrorCode.Aborted)
        {
            return;
        }

        //If LoadError was called then we'll remove both our handlers
        //as we won't need to capture LoadingStateChanged, we know there
        //was an error
        browser.LoadError -= loadErrorHandler;
        browser.LoadingStateChanged -= loadingStateChangeHandler;

        tcs.TrySetResult(new LoadUrlAsyncResponse(args.ErrorCode, -1));
    };

    loadingStateChangeHandler = (sender, args) =>
    {
        //Wait for while page to finish loading not just the first frame
        if (!args.IsLoading)
        {
            browser.LoadError -= loadErrorHandler;
            browser.LoadingStateChanged -= loadingStateChangeHandler;
            var host = args.Browser.GetHost();

            var navEntry = host?.GetVisibleNavigationEntry();

            int statusCode = navEntry?.HttpStatusCode ?? -1;

            //By default 0 is some sort of error, we map that to -1
            //so that it's clearer that something failed.
            if (statusCode == 0)
            {
                statusCode = -1;
            }

            tcs.TrySetResult(new LoadUrlAsyncResponse(statusCode == -1 ? CefErrorCode.Failed : CefErrorCode.None, statusCode));
        }
    };

    browser.LoadingStateChanged += loadingStateChangeHandler;
    browser.LoadError += loadErrorHandler;

    return tcs.Task;
}

// usage example 
private async void ButtonFind_Click(object sender, EventArgs e)
{

    //Action1
    string jsScript1 = "document.getElementById('story').value=" + '\'' + textFind.Text + '\'';
    await chrome.EvaluateScriptAsync(jsScript1);

    //Action2
    string jsScript2 = "document.querySelector('body > div.wrapper > div.header > div.header44 > div.search_panel > span > form > button').click();";
   
    await Task.WhenAll(chrome.WaitForLoadAsync(), 
      chrome.EvaluateScriptAsync(jsScript2));

    //Action3
    string jsScript3 = "document.getElementsByTagName('a')[2].click();";
    await Task.WhenAll(chrome.WaitForLoadAsync(), 
      chrome.EvaluateScriptAsync(jsScript3));


    //Action4
    string jsScript4 = "document.querySelector('#dle-content > div.section > ul > li:nth-child(3)').click();";
    await chrome.EvaluateScriptAsync(jsScript4);
}