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);
}
这些动作序列与 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);
}