使用背景
最近在使用c#(dotnetcore)編寫一些爬蟲進行實踐,在模擬網站請求的時候,往往在請求參數里含有一個根據請求內容實時生成的token,通過對前端js文件的調用,找到了用來生成token的js方法,但是將js代碼翻譯成c#代碼有點太費勁費時,於是想要找到一個這樣的框架,可以直接從c#調用js的方法並返回值。
嘗試的框架
我前前后后嘗試了好幾個框架,大致分為兩類1、瀏覽器內核/無頭瀏覽器,2、js引擎/框架。主要區別是一個是模擬瀏覽器去請求一個完整的網頁,另外的則是單單去計算調用一個純粹的js方法,我更傾向於后者。
瀏覽器內核/無頭瀏覽器
js引擎/框架
- Microsoft.AspNetCore.NodeServices (接口方法已標注過時,后續有可能不會在.net 新版本中支持)
- Microsoft.ClearScript (主要是使用v8引擎)
- JavaScriptEngineSwitcher.ChakraCore (使用的ChakraCore引擎)
最后根據項目需求選擇了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