C# / .NET Core 调用javascript方法(适用于Windows/Linux平台)
<h1><span style="font-size: 18pt">使用背景</span></h1><p><span style="font-size: 14px">最近在使用c#(dotnetcore)编写一些爬虫进行实践,在模拟网站请求的时候,往往在请求参数里含有一个根据请求内容实时生成的token,通过对前端js文件的调用,找到了用来生成token的js方法,但是将js代码翻译成c#代码有点太费劲费时,于是想要找到一个这样的框架,可以直接从c#调用js的方法并返回值。</span></p>
<h2><span style="font-size: 18pt">尝试的框架</span></h2>
<p><span style="font-size: 14px">我前前后后尝试了好几个框架,大致分为两类1、浏览器内核/无头浏览器,2、js引擎/框架。主要区别是一个是模拟浏览器去请求一个完整的网页,另外的则是单单去计算调用一个纯粹的js方法,我更倾向于后者。</span></p>
<p><span style="font-size: 14px">浏览器内核/无头浏览器</span></p>
<ul>
<li><span style="font-size: 14px">PuppeteerSharp</span></li>
</ul>
<p><span style="font-size: 14px">js引擎/框架</span></p>
<ul>
<li><span style="font-size: 14px">Microsoft.AspNetCore.NodeServices (接口方法已标注过时,后续有可能不会在.net 新版本中支持)</span></li>
<li><span style="font-size: 14px">Microsoft.ClearScript (主要是使用v8引擎)</span></li>
<li><span style="font-size: 14px">JavaScriptEngineSwitcher.ChakraCore (使用的ChakraCore引擎)</span></li>
</ul>
<p><span style="font-size: 14px">最后根据项目需求选择了JavaScriptEngineSwitcher.ChakraCore,因为支持在linux平台运行,在Windows上运行的时候需要额外引用JavaScriptEngineSwitcher.ChakraCore.Native.win-x64,在linux上运行时需要额外引用JavaScriptEngineSwitcher.ChakraCore.Native.linux-x64,这两个包可以同时引用。</span></p>
<p><span style="font-size: 14px">PuppeteerSharp时puppeteer的c#版本,由于我在使用时好像发现他在运行时需要额外下载内容,且下载失败,故没有做仔细研究便弃用。</span></p>
<p><span style="font-size: 14px">Microsoft.AspNetCore.NodeServices,功能满足需求,可以在linux上运行,但由于在使用Systemd运行应用时无法调用Nodejs,且暂未找到问题原因,且方法接口等已经标注过时,考虑以后的维护升级考虑,只作备选方案。</span></p>
<p><span style="font-size: 14px">Microsoft.ClearScript.V8功能满足需求,但是不满足在linux上运行,故不选择。</span></p>
<h2><span style="font-size: 18pt">代码示例<br><span style="font-size: 14px; color: rgba(255, 0, 0, 1)">注:以下代码实例均是使用目标框架 .Net Core 3.1,先在Windows平台测试,后在Linux下测试,使用到的框架应该也均有 .Net Framework 的版本支持,但对于此点并没有做验证。测试做的并不完全,如有疏漏或错误欢迎指正。</span></span></h2>
<h3><span style="font-size: 18px"><strong><em>Microsoft.AspNetCore.NodeServices</em></strong></span></h3>
<p>先决条件:Nodejs环境,设置环境变量NODE_PATH,脚本需要按照Nodejs的模块导出的格式将方法写成导出的形式。</p>
<p>c#</p>
<div class="cnblogs_code">
<pre><span style="font-size: 12px"><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Microsoft.AspNetCore.NodeServices;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Microsoft.Extensions.DependencyInjection;
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> NodeServicesDemo
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Demo
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span><span style="color: rgba(0, 0, 0, 1)"> INodeServices _nodeServices;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> IServiceCollection _nodeServiceCollections = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ServiceCollection();
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> XieChengScrapyService(IServiceProvider services)
{
_nodeServiceCollections.AddNodeServices(options </span>=><span style="color: rgba(0, 0, 0, 1)">
{
options.NodeInstanceOutputLogger </span>= loggerFactory.CreateLogger(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">nodeservices</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
options.ProjectPath </span>=<span style="color: rgba(0, 0, 0, 1)"> Environment.CurrentDirectory; // 设置项目为挡墙项目目录
});
</span><span style="color: rgba(0, 0, 255, 1)">var</span> sp =<span style="color: rgba(0, 0, 0, 1)"> _nodeServiceCollections.BuildServiceProvider();
_nodeServices </span>= sp.GetRequiredService<INodeServices><span style="color: rgba(0, 0, 0, 1)">();
}<br> <br> public async void Execute()<br> {<br> var result = await _nodeServices.InvokeAsync<string>("./Scripts/demo", input); // 要调用的模块的相对路径,参数<br> }
}
}</span></span></pre>
</div>
<p>demo.js(Nodejs模块)</p>
<div class="cnblogs_code">
<pre><span style="font-size: 12px"><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> m(t) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 0, 1)">p(v(t))
}
module.exports </span>= <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> (callback, t){
</span><span style="color: rgba(0, 0, 255, 1)">var</span> output =<span style="color: rgba(0, 0, 0, 1)"> m(t);
callback(</span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">, output);
}</span></span></pre>
</div>
<p> </p>
<h3><span style="font-size: 18px"><strong><em>Microsoft.ClearScript</em></strong></span></h3>
<p><span style="font-size: 14px">引入Nuget包,Microsoft.ClearScript</span></p>
<p><span style="font-size: 14px">demo.js(原生javascript)</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 14px"><span style="color: rgba(0, 0, 255, 1); font-size: 12px">function</span><span style="color: rgba(0, 0, 0, 1)"><span style="font-size: 12px"> m(t, e, r) {
p(v(t))
}</span></span></span></pre>
</div>
<p><span style="background-color: rgba(255, 255, 255, 1); color: rgba(255, 0, 0, 1); font-size: 14px">注:除了NodeServices中,其他要调用的js文件均为以此为示例,后面不再复述。</span></p>
<p><span style="font-size: 14px">引入Nuget包,Microsoft.ClearScript</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 12px"><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Microsoft.ClearScript.JavaScript;
</span><span style="color: rgba(0, 0, 255, 1)">using</span> Microsoft.ClearScript.V8;</span></pre>
</div>
<p><span style="font-size: 14px">初始化</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 12px">using (<span style="color: rgba(0, 0, 255, 1)">var</span> engine = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> V8ScriptEngine())
{
engine.DocumentSettings.AccessFlags </span>=<span style="color: rgba(0, 0, 0, 1)"> Microsoft.ClearScript.DocumentAccessFlags.EnableFileLoading;
engine.DefaultAccess </span>= Microsoft.ClearScript.ScriptAccess.Full; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这两行是为了允许加载js文件</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> do something</span>
}</span></pre>
</div>
<p><span style="font-size: 14px">调用脚本有多种方案。</span></p>
<p><span style="font-size: 14px">方案一:调用engine.ComplieDocument方法直接加载js文件,然后调用engine.Execute将引入的脚本执行一遍,这样后面就可以调用js方法,m就是js的方法名,调用格式与js相同。</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 12px">V8Script script = engine.CompileDocument(ScriptFilePath); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 载入并编译js文件, 然后Execute, 就可以直接调用。</span>
<span style="color: rgba(0, 0, 0, 1)">engine.Execute(script);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> result = engine.Script.m(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">SHAURCOnewayduew&^%5d54nc'KH</span><span style="color: rgba(128, 0, 0, 1)">"</span>); </span></pre>
</div>
<p><span style="font-size: 14px">方案二:将要导入的js方法的代码读出来,然后执行一遍,再调用要执行的js方法</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 12px">string scriptContent =<span style="color: rgba(0, 0, 0, 1)"> string.Empty;
using(FileStream fs </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FileStream(ScriptFilePath, FileMode.Open, FileAccess.Read))
{
using(StreamReader sr </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StreamReader(fs))
{
scriptContent </span>= sr.ReadToEnd().Replace("\r\n", ""<span style="color: rgba(0, 0, 0, 1)">);
}
}
engine.Execute(scriptContent);</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 取得脚本里的所有内容,Execute一下,然后,调用engine.Script.func(x,y)执行一下。</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> result = engine.Script.m("SHAURCOnewayduew&^%5d54nc'KH");</span></pre>
</div>
<p><span style="font-size: 14px">直接调用执行调用的方法的js代码也是可以的</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 12px">string scriptContent =<span style="color: rgba(0, 0, 0, 1)"> string.Empty;
using(FileStream fs </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FileStream(ScriptFilePath, FileMode.Open, FileAccess.Read))
{
using(StreamReader sr </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> StreamReader(fs))
{
scriptContent </span>= sr.ReadToEnd().Replace("\r\n", ""<span style="color: rgba(0, 0, 0, 1)">);
}
}
scriptContent </span>+= "m(\"SHAURCOnewayduew&^%5d54nc'KH\");";<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 在js代码的结尾加上执行的代码</span>
<span style="color: rgba(0, 0, 0, 1)">
engine.Execute(scriptContent);</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 取得脚本里的所有内容,Execute一下,然后,调用engine.Script.func(x,y)执行一下。</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> result = engine.Script.m("SHAURCOnewayduew&^%5d54nc'KH");</span></pre>
</div>
<p><span style="font-size: 14px">特殊情况,调用js全局方法,就是调用js的默认的那些方法</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 12px"><span style="color: rgba(0, 0, 255, 1)">var</span> result = engine.Invoke("encodeURIComponent", "SHAURCOnewayduew&^%5d54nc'KH"); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">只能调用全局方法,如encodeURIComponent</span></span></pre>
</div>
<p> </p>
<p><span style="font-size: 18px"><strong><em>JavaScriptEngineSwitcher.ChakraCore</em></strong></span></p>
<p><span style="font-size: 14px">引入Nuget包,JavaScriptEngineSwitcher.ChakraCore,JavaScriptEngineSwitcher.ChakraCore.Native.win-x64,JavaScriptEngineSwitcher.ChakraCore.Native.linux-x64</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 12px"><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> JavaScriptEngineSwitcher.ChakraCore;
</span><span style="color: rgba(0, 0, 255, 1)">using</span> JavaScriptEngineSwitcher.Core;</span></pre>
</div>
<p><span style="font-size: 14px">使用,同样是先把js文件执行一遍,然后再去调用要使用的方法。</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 12px"><span style="color: rgba(0, 0, 255, 1)">string</span> ScriptPath = Path.Combine(Directory.GetCurrentDirectory(), <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Scripts</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">demo.js</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> switcher =<span style="color: rgba(0, 0, 0, 1)"> JsEngineSwitcher.Current;
switcher.EngineFactories.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ChakraCoreJsEngineFactory());
switcher.DefaultEngineName </span>=<span style="color: rgba(0, 0, 0, 1)"> ChakraCoreJsEngine.EngineName;
IJsEngine engine </span>=<span style="color: rgba(0, 0, 0, 1)"> JsEngineSwitcher.Current.CreateDefaultEngine();
engine.ExecuteFile(ScriptPath, Encoding.UTF8);
</span><span style="color: rgba(0, 0, 255, 1)">string</span> result = engine.CallFunction<<span style="color: rgba(0, 0, 255, 1)">string</span>>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">m</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">SHAURCOnewayduew&^%5d54nc'KH</span><span style="color: rgba(128, 0, 0, 1)">"</span>);</span></pre>
</div>
<p> </p>
<h3><span style="font-size: 16px">参考资料:</span></h3>
<p><span style="font-size: 14px">webmote-org/netcore-javascript</span></p>
<p><span style="font-size: 14px">microsoft/ChakraCore -- github</span></p>
<p><span style="font-size: 14px">Microsoft/ClearScript -- V8ScriptEngine Class</span></p>
<p><span style="font-size: 14px">koopla/NodeServices -- github</span></p>
<h1 class="publicd-flex flex-wrap flex-items-center break-word float-none "> </h1><br><br>
来源:https://www.cnblogs.com/FFFirer/p/csharpjs.html
頁:
[1]