CEFSharp
在前面的文章中,我介绍过CEFSharp,
CEF 全称是Chromium Embedded Framework(Chromium嵌入式框架),是个基于Google Chromium项目的开源Web browser控件,支持Windows, Linux, Mac平台。
CEFSharp就是CEF的C#移植版本。
CEFSharp提供了一个headless(无头模式)版本。
所谓无头模式(Headless Mode),就是浏览器不显示 GUI 界面,后台完整运行渲染引擎与 JS 引擎,适合服务器 / 自动化场景。
如何使用CEFSharp的无头模式
这里提供了一个CefSharp.OffScreen包,基础使用步骤如下:
1、创建一个控制台项目,nuget安装CefSharp.OffScreen包

注意:截止最新稳定版 147.0.100,CEFSharp.OffScreen.NETCore包仅支持.NET 6.0-windows版本,后续 不知道会不会支持.NET 6.0以上版本
2、项目属性 → 32 位 / 64 位一致

3、 设置为 STA 线程
1 class Program
2 {
3 [STAThread]
4 static async Task Main(string[] args)
5 {
6
7 }
8 }
4、基础使用代码如下
1 // 全局浏览器实例
2 private static ChromiumWebBrowser _browser;
3
4 //需要指定STA线程
5 [STAThread]
6 static void Main(string[] args)
7 {
8 //CEF全局配置
9 var cefSettings = new CefSettings
10 {
11 //缓存路径
12 CachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Cache"),
13
14 //自定义UA
15 UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.95 Safari/537.36"
16 };
17
18 //最优离屏渲染参数
19 cefSettings.SetOffScreenRenderingBestPerformanceArgs();
20
21 //初始化CEF
22 if (!Cef.Initialize(cefSettings, true))
23 {
24 Console.WriteLine("CEF初始化失败");
25 return;
26 }
27
28 try
29 {
30 _browser = new ChromiumWebBrowser();
31
32 //加载网页
33 _browser.Load("https://www.baidu.com");
34
35 //等待加载完成
36 LoadPageAsync(_browser).Wait();
37
38 }
39 catch (Exception ex)
40 {
41 Console.WriteLine(ex.Message);
42 }
43 finally
44 {
45 //释放资源
46 _browser?.Dispose();
47 Cef.Shutdown();
48
49 Console.WriteLine("请输入任意键退出");
50 Console.ReadLine();
51 }
52 }
这里我们需要封装一个等待函数来等待网页执行完成
1 /// <summary>
2 /// 等待页面加载完成
3 /// </summary>
4 /// <param name="browser"></param>
5 /// <returns></returns>
6 public static Task LoadPageAsync(IWebBrowser browser)
7 {
8 var tcs = new TaskCompletionSource<bool>();
9 EventHandler<LoadingStateChangedEventArgs> handler = null;
10 handler = (sender, args) =>
11 {
12 //判断没有在加载,则代表整个页面加载完成了
13 if (!args.IsLoading)
14 {
15 browser.LoadingStateChanged -= handler;
16 tcs.TrySetResult(true);
17 }
18 };
19 //处理加载状态更改事件
20 browser.LoadingStateChanged += handler;
21 return tcs.Task;
22 }
运行效果:

常用事件
1 // 渲染画面回调(每一帧都会触发)
2 _browser.Paint += _browser_Paint;
3
4 // JS控制台日志捕获
5 _browser.ConsoleMessage += _browser_ConsoleMessage;
6
7 _browser.FrameLoadEnd += _browser_FrameLoadEnd;
事件处理程序
1 /// <summary>
2 /// 浏览器完成页面框架加载时触发的事件处理程序。
3 /// 页面可能同时存在多个框架正在加载;
4 /// 主框架加载完成后,子框架仍可能发起或继续加载流程。
5 /// 无论请求是否加载成功,该方法都会对所有框架执行回调。
6 /// 请注意:此事件在 CEF 界面线程触发,默认情况下该线程与应用程序界面主线程不是同一个线程。
7 /// 不要在该线程中进行任何耗时阻塞操作,否则浏览器会无响应甚至卡死。
8 /// 如果需要操作界面控件,必须通过Invoke/Dispatch切换至应用 UI 主线程。
9 /// </summary>
10 /// <param name="sender"></param>
11 /// <param name="e"></param>
12 /// <see cref="https://cefsharp.github.io/api/118.6.x/html/E_CefSharp_IChromiumWebBrowserBase_FrameLoadEnd.htm"/>
13 private static async void _browser_FrameLoadEnd(object? sender, FrameLoadEndEventArgs e)
14 {
15 //参考:https://www.cnblogs.com/zhaotianff/p/9556270.html
16 var html = await e.Browser.GetSourceAsync();
17 Console.WriteLine(html);
18 Console.WriteLine("frame 加载完成");
19 }
20
21 /// <summary>
22 /// 用于接收网页发送的 JavaScript 控制台消息的事件处理程序。
23 /// 需要重点注意:该事件在 CEF 界面线程触发,默认情况下该线程与你的应用程序界面线程并非同一线程。
24 /// 切勿在此线程中执行任何耗时阻塞操作,否则浏览器会失去响应甚至卡死。
25 /// 若要操作界面控件,你必须通过调用Invoke/Dispatch切换至应用主线程。
26 /// </summary>
27 /// <param name="sender"></param>
28 /// <param name="e"></param>
29 /// <see cref="https://cefsharp.github.io/api/118.6.x/html/E_CefSharp_IChromiumWebBrowserBase_ConsoleMessage.htm"/>
30 private static void _browser_ConsoleMessage(object? sender, ConsoleMessageEventArgs e)
31 {
32
33 }
34
35 /// <summary>
36 ///
37 /// </summary>
38 /// <param name="sender"></param>
39 /// <param name="e"></param>
40 /// <exception cref="NotImplementedException"></exception>
41 private static void _browser_Paint(object? sender, OnPaintEventArgs e)
42 {
43 // 该回调在 CEF 界面线程触发,默认情况下该线程与应用程序主线程并非同一线程。
44 // 页面元素需要绘制时触发此方法
45
46 //BGRA 4字节/像素
47 int stride = e.Width * 4;
48
49 //创建Bitmap:CEF像素格式 BGRA
50 Bitmap bmp = new Bitmap(e.Width, e.Height, stride, System.Drawing.Imaging.PixelFormat.Format32bppArgb, e.BufferHandle);
51
52 bmp.Save(DateTime.Now.ToString("yyyy_MM_dd_HH_mm_ss_ffff") + ".jpg");
53
54 bmp.Dispose();
55 }
截图效果

常用方法
1 // 执行JS获取返回值
2 var jsResult = await _browser.EvaluateScriptAsync("document.title");
3 if (jsResult.Success)
4 {
5 Console.WriteLine("网页标题:" + jsResult.Result);
6 }
7
8 var host = _browser.GetBrowser().GetHost();
9
10 // 点击页面元素(坐标点击)
11 // x,y 网页相对坐标
12 //按下
13 host.SendMouseClickEvent(120, 120, MouseButtonType.Left, false, 1, CefEventFlags.None);
14 //等待
15 await Task.Delay(200);
16 //松开
17 host.SendMouseClickEvent(120, 120, MouseButtonType.Left, true, 1, CefEventFlags.None);
18
19 //输入键盘内容
20 KeyEvent keyEvent = new KeyEvent();
21 keyEvent.WindowsKeyCode = (int)Keys.H;
22 keyEvent.Type = KeyEventType.KeyDown;
23 //按下
24 host.SendKeyEvent(keyEvent);
25 //松开
26 keyEvent.Type = KeyEventType.KeyUp;
27 host.SendKeyEvent(keyEvent);
28
29 // 网页后退/前进/刷新
30 //_browser.Back();
31 //_browser.Forward();
32 //_browser.Reload();
33
34 //导出网页为PDF
35 var pdfSettings = new PdfPrintSettings
36 {
37 Landscape = false,
38 PrintBackground = true
39 };
40 await _browser.PrintToPdfAsync("网页导出.pdf", pdfSettings);
41 Console.WriteLine("PDF导出完成");
42
43 //设置Cookie
44 //var cookieManager = Cef.GetGlobalCookieManager();
45 //cookieManager.SetCookie("https://www.baidu.com", new Cookie
46 //{
47 // Name = "testCookie",
48 // Value = "123456",
49 // Domain = ".baidu.com"
50 //});
51
52 //获取当前URL
53 Console.WriteLine("当前地址:" + _browser.Address);
参考资料:
General Usage · cefsharp/CefSharp Wiki · GitHub
C#使用Puppeteer - zhaotianff - 博客园
示例代码
cnblog-demo-code/CEFSharpOffScreenDemo at main · zhaotianff/cnblog-demo-code · GitHub