前言
昨天接到一個新需求,需要用 Url 連結到其他網頁將該頁面截圖後,轉換成pdf檔供使用者下載。
今天研究了一下覺得好玩紀錄一下。
本文
接到需求後一開始是用 .NET Webpage to pdf 當關鍵字搜尋,無奈找了幾個套件不是要收費就是已經年代久遠,最後用中文搜尋到了黑大去年寫的一篇文章。
C# 整合 Headless Chrome 的好工具 - Puppeteer Sharp
此篇是用 Puppeteer Sharp 這個套件將網頁截圖後儲存,套件資訊可以自行參閱。
Puppeteer Sharp 是使用 Chromium 幫你截圖網站頁面,所以其實後背後運作 .NET 還是有幫你開啟網頁的。
無奈今天嘗試後遇到 Failed to launch chrome 錯誤。
Google後發現很多人都有遇到類似問題,有很多人發issue給作者。
最後決定回頭繼續搜尋其他方式,這時候發現黑大今年又寫了另一篇文章。
C# 網頁轉圖檔 WebAPI - 青春版
這方法使用的是原生 System.Web.Forms.WebBrowser 方式擷取網頁圖片,一樣的 .NET 有幫你開啟網頁進行截圖的動作,只是這個原生的方式就是使用IE。
測試的此方式可行後我就開始尋找第二步動作,該怎麼把圖檔轉換成 pdf,於是找到了另一個套件iTextSharp。
可以套過套件將圖檔直接轉換成pdf的強大方法。
將黑大的方法結合 iTextSharp 就能得到想到的結果如下…
private static void Main(string[] args)
{
string url = null;
var png = WebSnapTool.Snapshot(url ?? "https://sunnyday0932.github.io/2020/object-oriented%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91%E8%A8%AD%E8%A8%88%E5%8E%9F%E5%89%87solid-1_single-responsibility-principlesrp-%E5%96%AE%E4%B8%80%E8%81%B7%E8%B2%AC/", delaySecs: 0);
using (var stream = File.Create(@"D:\test\test.pdf"))
using (var doc = new Document())
using (var pdfWriter = PdfWriter.GetInstance(doc, stream))
{
doc.Open();
var image = iTextSharp.text.Image.GetInstance(png);
image.ScalePercent(50); //調比例方式
doc.Add(image);
doc.Close();
}
}
public class WebSnapTool
{
static WebSnapTool()
{
var appName = Process.GetCurrentProcess().MainModule.ModuleName;
long webBrowserEmuVer = 11001; //指定 IE 相容模式 - IE11 Edge Mode
Registry.SetValue(@"HKEY_CURRENT_USER\SOFTWARE\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_BROWSER_EMULATION",
appName, webBrowserEmuVer, RegistryValueKind.DWord);
Registry.SetValue(@"HKEY_CURRENT_USER\SOFTWARE\Wow6432Node\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_BROWSER_EMULATION",
appName, webBrowserEmuVer, RegistryValueKind.DWord);
}
public static byte[] Snapshot(string url, int width = 1024, int height = 768, int delaySecs = 0, bool autoSize = true)
{
var sync = new AutoResetEvent(false);
byte[] png = null;
//STA
var thread = new Thread(() =>
{
var threadSync = new AutoResetEvent(false);
using (var browser = new WebBrowser()
{
Width = width,
Height = height
})
{
Func<string, string> Eval = (script) =>
{
return browser.Document.InvokeScript("eval", new string[] {
"(function() { return " + script + "; })()" }).ToString();
};
browser.DocumentCompleted += delegate
{
Thread.Sleep(delaySecs * 1000);
if (autoSize)
{
browser.Width = int.Parse(Eval("document.body.clientWidth"));
browser.Height = int.Parse(Eval("document.body.clientHeight"));
}
using (var pic = new Bitmap(browser.Width, browser.Height))
{
browser.DrawToBitmap(pic, new System.Drawing.Rectangle(0, 0, pic.Width, pic.Height));
using (var ms = new MemoryStream())
{
pic.Save(ms, ImageFormat.Png);
png = ms.ToArray();
Application.ExitThread();
}
}
};
browser.ScrollBarsEnabled = false;
browser.Navigate(url);
Application.Run();
sync.Set();
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
//20秒無法產生即放棄
var succ = sync.WaitOne(20 * 1000);
if (thread.IsAlive)
{
thread.Abort();
}
if (succ) return png;
else throw new ApplicationException("Web snapshot failed");
}
}
後記
如果本篇文章主題-純屬好玩,實際做過之後發現幾個問題,跟主管討論後決定不採用這個方式製作,因此只是覺得好玩紀錄一下,也把發現的問題提供出來給大家參考。
- 1、因為 .NET 實際背後運作是有幫你打開瀏覽器做截圖,若使用者過多是很吃資源的。
- 2、當目標網頁若有語法錯誤的時候這個方法就會跳出警示Ex:前端一些 js 錯誤等。
- 3、如上圖所示 pdf 出來大小會有點奇怪,需要透過自己調整 iTextSharp 的方法,才能符合最佳大小,這也意味著不同網站,或是同網站不同結果都需要客製化參數調整。
- 4、回到該需求本身,若目標網頁修改了任意參數,那這個方式就會直接死亡。