C# / .NET Core 调用javascript方法(适用于Windows/Linux平台)

使用背景

最近在使用c#(dotnetcore)编写一些爬虫进行实践,在模拟网站请求的时候,往往在请求参数里含有一个根据请求内容实时生成的token,通过对前端js文件的调用,找到了用来生成token的js方法,但是将js代码翻译成c#代码有点太费劲费时,于是想要找到一个这样的框架,可以直接从c#调用js的方法并返回值。

尝试的框架

我前前后后尝试了好几个框架,大致分为两类1、浏览器内核/无头浏览器,2、js引擎/框架。主要区别是一个是模拟浏览器去请求一个完整的网页,另外的则是单单去计算调用一个纯粹的js方法,我更倾向于后者。

浏览器内核/无头浏览器

js引擎/框架

最后根据项目需求选择了JavaScriptEngineSwitcher.ChakraCore,因为支持在linux平台运行,在Windows上运行的时候需要额外引用JavaScriptEngineSwitcher.ChakraCore.Native.win-x64,在linux上运行时需要额外引用JavaScriptEngineSwitcher.ChakraCore.Native.linux-x64,这两个包可以同时引用。

PuppeteerSharp时puppeteer的c#版本,由于我在使用时好像发现他在运行时需要额外下载内容,且下载失败,故没有做仔细研究便弃用。

Microsoft.AspNetCore.NodeServices,功能满足需求,可以在linux上运行,但由于在使用Systemd运行应用时无法调用Nodejs,且暂未找到问题原因,且方法接口等已经标注过时,考虑以后的维护升级考虑,只作备选方案。

Microsoft.ClearScript.V8功能满足需求,但是不满足在linux上运行,故不选择。

代码示例
注:以下代码实例均是使用目标框架 .Net Core 3.1,先在Windows平台测试,后在Linux下测试,使用到的框架应该也均有 .Net Framework 的版本支持,但对于此点并没有做验证。测试做的并不完全,如有疏漏或错误欢迎指正。

Microsoft.AspNetCore.NodeServices

先决条件:Nodejs环境,设置环境变量NODE_PATH,脚本需要按照Nodejs的模块导出的格式将方法写成导出的形式。

c#

using Microsoft.AspNetCore.NodeServices;
using Microsoft.Extensions.DependencyInjection;

namespace NodeServicesDemo
{
    public class Demo
    {
        [Obsolete]
        private readonly INodeServices _nodeServices;
        private IServiceCollection _nodeServiceCollections = new ServiceCollection();

        [Obsolete]
        public XieChengScrapyService(IServiceProvider services)
        {
            _nodeServiceCollections.AddNodeServices(options => 
            {
                options.NodeInstanceOutputLogger = loggerFactory.CreateLogger("nodeservices");
                options.ProjectPath = Environment.CurrentDirectory; // 设置项目为挡墙项目目录
            });
            var sp = _nodeServiceCollections.BuildServiceProvider();
            _nodeServices = sp.GetRequiredService<INodeServices>();
        }
     
public async void Execute()
{
var result = await _nodeServices.InvokeAsync<string>("./Scripts/demo", input);  // 要调用的模块的相对路径,参数
} } }

demo.js(Nodejs模块)

function m(t) {
    return p(v(t))
}

module.exports = function (callback, t){
    var output = m(t);
    callback(null, output);
}

 

Microsoft.ClearScript

引入Nuget包,Microsoft.ClearScript

demo.js(原生javascript)

function m(t, e, r) {
    p(v(t))
}

注:除了NodeServices中,其他要调用的js文件均为以此为示例,后面不再复述。

引入Nuget包,Microsoft.ClearScript

using Microsoft.ClearScript.JavaScript;
using Microsoft.ClearScript.V8;

初始化

using (var engine = new V8ScriptEngine())
{
    engine.DocumentSettings.AccessFlags = Microsoft.ClearScript.DocumentAccessFlags.EnableFileLoading;
    engine.DefaultAccess = Microsoft.ClearScript.ScriptAccess.Full; // 这两行是为了允许加载js文件
    // do something
}

调用脚本有多种方案。

方案一:调用engine.ComplieDocument方法直接加载js文件,然后调用engine.Execute将引入的脚本执行一遍,这样后面就可以调用js方法,m就是js的方法名,调用格式与js相同。

V8Script script = engine.CompileDocument(ScriptFilePath);   // 载入并编译js文件, 然后Execute, 就可以直接调用。
engine.Execute(script);
var result = engine.Script.m("SHAURCOnewayduew&^%5d54nc'KH");  

方案二:将要导入的js方法的代码读出来,然后执行一遍,再调用要执行的js方法

string scriptContent = string.Empty;
using(FileStream fs = new FileStream(ScriptFilePath, FileMode.Open, FileAccess.Read))
{
    using(StreamReader sr = new StreamReader(fs))
    {
        scriptContent = sr.ReadToEnd().Replace("\r\n", "");
    }
}
engine.Execute(scriptContent);  // 取得脚本里的所有内容,Execute一下,然后,调用engine.Script.func(x,y)执行一下。

var result = engine.Script.m("SHAURCOnewayduew&^%5d54nc'KH");

直接调用执行调用的方法的js代码也是可以的

string scriptContent = string.Empty;
using(FileStream fs = new FileStream(ScriptFilePath, FileMode.Open, FileAccess.Read))
{
    using(StreamReader sr = new StreamReader(fs))
    {
        scriptContent = sr.ReadToEnd().Replace("\r\n", "");
    }
}
scriptContent += "m(\"SHAURCOnewayduew&^%5d54nc'KH\");";  // 在js代码的结尾加上执行的代码

engine.Execute(scriptContent);  // 取得脚本里的所有内容,Execute一下,然后,调用engine.Script.func(x,y)执行一下。

var result = engine.Script.m("SHAURCOnewayduew&^%5d54nc'KH");

特殊情况,调用js全局方法,就是调用js的默认的那些方法

var result = engine.Invoke("encodeURIComponent", "SHAURCOnewayduew&^%5d54nc'KH"); //只能调用全局方法,如encodeURIComponent

 

JavaScriptEngineSwitcher.ChakraCore

引入Nuget包,JavaScriptEngineSwitcher.ChakraCore,JavaScriptEngineSwitcher.ChakraCore.Native.win-x64,JavaScriptEngineSwitcher.ChakraCore.Native.linux-x64

using JavaScriptEngineSwitcher.ChakraCore;
using JavaScriptEngineSwitcher.Core;

使用,同样是先把js文件执行一遍,然后再去调用要使用的方法。

string ScriptPath = Path.Combine(Directory.GetCurrentDirectory(), "Scripts", "demo.js");
var switcher = JsEngineSwitcher.Current;
switcher.EngineFactories.Add(new ChakraCoreJsEngineFactory());
switcher.DefaultEngineName = ChakraCoreJsEngine.EngineName;
IJsEngine engine = JsEngineSwitcher.Current.CreateDefaultEngine();
engine.ExecuteFile(ScriptPath, Encoding.UTF8);
string result = engine.CallFunction<string>("m", "SHAURCOnewayduew&^%5d54nc'KH");

 

参考资料:

webmote-org/netcore-javascript

microsoft/ChakraCore -- github

Microsoft/ClearScript -- V8ScriptEngine Class

koopla/NodeServices -- github

 

posted @ 2020-06-05 01:23  Watt不想上班  阅读(1503)  评论(0编辑  收藏