轻轻松松打印网页并生成pdf文档

鸟窝 2021-05-08 15:26

chromedp是一个更快更简单的支持Chrome DevTools Protocol协议的Go库,它是目前最流行的headless浏览器库之一,你可以使用它做很多只能通过浏览器才能执行的任务,比如网页截屏、网页渲染测试、下载视频、模拟登录等,今天我介绍它的一个有用而且很简单的功能:为一个网页生成一个pdf格式的截屏,更多的例子你可以查看官方示例chromedp/examples

首先有一点,你需要安装chrome,这样chromedp库才能通过cdp协议调用chrome执行任务(动作)。

为网页生成pdf

首先,你需要引入chromedp库:

1
go get -u github.com/chromedp/chromedp

然后,你就可以通过chromedp.Run执行一系列的动作,比如我们这个例子就是先导航到某个页面,然后将页面生成为pdf:

1234567891011121314
// 生成任务列表func printToPDF(urlstr string, res *[]byte) chromedp.Tasks {	return chromedp.Tasks{		chromedp.Navigate(urlstr), // 浏览指定的页面		chromedp.ActionFunc(func(ctx context.Context) error {			buf, _, err := page.PrintToPDF().WithPrintBackground(true).Do(ctx) // 通过cdp执行PrintToPDF			if err != nil {				return err			}			*res = buf			return nil		}),	}}

ActionFunc是一个便利的方法,用来执行一个函数作为Action,就像标准库http.Handler和http.HandleFunc的关系。因为这里我们要执行的逻辑比较简单,所以就通过一个函数实现就可以了。

page.PrintToPDF()是定义要执行输出pdf的一些参数,你可以额外设置一些参数,这些参数包括:

123456789101112131415161718
type PrintToPDFParams struct {	Landscape               bool                   `json:"landscape,omitempty"`               // 横向打印. 默认false.	DisplayHeaderFooter     bool                   `json:"displayHeaderFooter,omitempty"`     // 打印header和footer. 默认false.	PrintBackground         bool                   `json:"printBackground,omitempty"`         // 打印背景图.  默认false.	Scale                   float64                `json:"scale,omitempty"`                   // 放缩因子. 默认为1.	PaperWidth              float64                `json:"paperWidth,omitempty"`              // 页面宽度(英寸). 默认8.5英寸(美国Letter标准尺寸,和A4纸差不太多).	PaperHeight             float64                `json:"paperHeight,omitempty"`             // 页面高度(英寸). 默认11英寸(Letter标准尺寸).	MarginTop               float64                `json:"marginTop"`                         // 上边距(英寸). 默认1cm (大约0.4 英寸).	MarginBottom            float64                `json:"marginBottom"`                      // 底边距(英寸). 默认1cm (大约0.4 英寸).	MarginLeft              float64                `json:"marginLeft"`                        // 左边距(英寸). 默认1cm (大约0.4 英寸).	MarginRight             float64                `json:"marginRight"`                       // 右边距(英寸). 默认1cm (大约0.4 英寸).	PageRanges              string                 `json:"pageRanges,omitempty"`              // 要打印的页码, 比如, '1-5, 8, 11-13'.默认为空,全打印.	IgnoreInvalidPageRanges bool                   `json:"ignoreInvalidPageRanges,omitempty"` // 是否要忽略非法的页码范围. 默认false.	HeaderTemplate          string                 `json:"headerTemplate,omitempty"`          // HTML模板head. 	FooterTemplate          string                 `json:"footerTemplate,omitempty"`          // HTML模板footer.	PreferCSSPageSize       bool                   `json:"preferCSSPageSize,omitempty"`       // 是否首选css定义的页面大小?默认false,将自动适应.	TransferMode            PrintToPDFTransferMode `json:"transferMode,omitempty"`            // 返回stream}

这里我们的例子不做额外的设置,只调整了打印背景图参数,当然你为了打印出漂亮的pdf话,可以调整这里的参数,更适合阅读和打印。

Do通过cdp协议执行打印并返回结果。

主要逻辑就完成了,下一步就是执行这些任务了。

首先要创建一个chromedp的Context:

1
ctx, cancel := chromedp.NewContext(context.Background())

然后调用chromedp.Run执行任务就可以了:

123
if err := chromedp.Run(ctx, printToPDF(`https://colobu.com/`, &buf)); err != nil {	log.Fatal(err)}

最后把pdf写入到文件中,完成。

123
if err := ioutil.WriteFile("colobu.pdf", buf, 0644); err != nil {	log.Fatal(err)}

生成的pdf效果如下:

完整的代码如下:

1234567891011121314151617181920212223242526272829303132333435363738394041
package mainimport (	"context"	"io/ioutil"	"log"	"github.com/chromedp/cdproto/page"	"github.com/chromedp/chromedp")func main() {	// 创建 context	ctx, cancel := chromedp.NewContext(context.Background())	defer cancel()	// 生成pdf	var buf []byte	if err := chromedp.Run(ctx, printToPDF(`https://colobu.com/`, &buf)); err != nil {		log.Fatal(err)	}	if err := ioutil.WriteFile("colobu.pdf", buf, 0644); err != nil {		log.Fatal(err)	}}// 生成任务列表func printToPDF(urlstr string, res *[]byte) chromedp.Tasks {	return chromedp.Tasks{		chromedp.Navigate(urlstr), // 浏览指定的页面		chromedp.ActionFunc(func(ctx context.Context) error {			buf, _, err := page.PrintToPDF().WithPrintBackground(false).Do(ctx) // 通过cdp执行PrintToPDF			if err != nil {				return err			}			*res = buf			return nil		}),	}}

生成漂亮的图表

echarts是我厂(百度)贡献的一个非常知名的图表库,可以通过js为网页生成巨漂亮的图表,用来数据展示。Go语言虽然有一些"玩具"类的图表库,但是并没有一个真正拿的出手的图标库,所以有人就利用echarts,生成一个网页,把数据展示出来,这个库是go-echarts

但是,毕竟这是曲折的方式,最终生成的数据是一个网页。

既然刚才我们通过chromedp可以生成pdf,那么是不是也可以截图,将go-echarts生成的图表截图成一个Go的Image对象?让我们试一试。

首先,我们先利用go-echarts生成一个图表,并把它保存成一个html网页:

123456789101112131415161718192021222324
func generateEcharts() {	bar := charts.NewBar()	// set some global options like Title/Legend/ToolTip or anything else	bar.SetGlobalOptions(charts.WithTitleOpts(opts.Title{		Title:    "生成一个漂亮的bar图表",		Subtitle: "我要得到它的灵魂",	}))	// Put data into instance	bar.SetXAxis([]string{"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}).		AddSeries("Category A", generateBarItems()).		AddSeries("Category B", generateBarItems())	// Where the magic happens	f, _ := os.Create("bar.html")	bar.Render(f)}func generateBarItems() []opts.BarData {	items := make([]opts.BarData, 0)	for i := 0; i < 7; i++ {		items = append(items, opts.BarData{Value: rand.Intn(300)})	}	return items}

下一步就是chromedp的工作了,浏览这个本地网页,并进行截图:

1234567891011121314151617181920212223242526272829
// 生成echarts网页generateEcharts()// 创建chromedp contextctx, cancel := chromedp.NewContext(	context.Background(),	chromedp.WithDebugf(log.Printf),)defer cancel()// 定义taskselementScreenshot := func(urlstr, sel string, res *[]byte) chromedp.Tasks {	return chromedp.Tasks{		chromedp.Navigate(urlstr),		chromedp.Screenshot(sel, res, chromedp.NodeVisible),	}}// 生成截图var buf []bytebarFile, _ := filepath.Abs("./bar.html")if err := chromedp.Run(ctx, elementScreenshot(`file://`+barFile, `canvas`, &buf)); err != nil {	log.Fatal(err)}// 将截图写入到文件中if err := ioutil.WriteFile("bar.png", buf, 0o644); err != nil {	log.Fatal(err)}

最终,生成一个截图。你可以把这个截图生成Image对象,或者把它保存到一个文件中。这里我们不进行额外的处理了,所以把它保存到文件中。生成的文件如下:

当然,利用chromedp的打印和截图功能还能做很多事,比如转换epub电子书成pdf格式,grafana截图报警等等。

更多的,你可以利用chromedp干很多很多事,这依赖你的想象力,或者你可以搜一下一些网友的分享,比如Golang爬虫终极杀器——Chromedp让你成为二维码登陆终结者

[返回] [原文链接]