golang基于goquery的HTML解析与结构体映射插件库pagser的使用
Golang基于goquery的HTML解析与结构体映射插件库Pagser的使用
Pagser是一个基于goquery的HTML解析库,可以将HTML页面解析并映射到Golang结构体中。
安装
go get -u github.com/foolin/pagser
或者安装指定版本:
go get github.com/foolin/pagser@{version}
特性
- 简单 - 使用Golang结构体标签语法
- 易用 - 易于在爬虫/采集应用中使用
- 可扩展 - 支持扩展函数
- 结构体标签语法 - 语法简单,如
pagser:"a->attr(href)"
- 嵌套结构 - 支持节点嵌套结构
- 可配置 - 支持配置
- 隐式类型转换 - 自动将结果字符串转换为int、int64、float64等类型
- GoQuery/Colly - 支持所有goquery项目,如go-colly
基本用法
package main
import (
"encoding/json"
"github.com/foolin/pagser"
"log"
)
const rawPageHtml = `
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Pagser Title</title>
<meta name="keywords" content="golang,pagser,goquery,html,page,parser,colly">
</head>
<body>
<h1>H1 Pagser Example</h1>
<div class="navlink">
<div class="container">
<ul class="clearfix">
<li id=''><a href="/">Index</a></li>
<li id='2'><a href="/list/web" title="web site">Web page</a></li>
<li id='3'><a href="/list/pc" title="pc page">Pc Page</a></li>
<li id='4'><a href="/list/mobile" title="mobile page">Mobile Page</a></li>
</ul>
</div>
</div>
</body>
</html>
`
type PageData struct {
Title string `pagser:"title"`
Keywords []string `pagser:"meta[name='keywords']->attrSplit(content)"`
H1 string `pagser:"h1"`
Navs []struct {
ID int `pagser:"->attrEmpty(id, -1)"`
Name string `pagser:"a->text()"`
Url string `pagser:"a->attr(href)"`
} `pagser:".navlink li"`
}
func main() {
// 新建默认配置
p := pagser.New()
// 数据解析模型
var data PageData
// 解析HTML数据
err := p.Parse(&data, rawPageHtml)
// 检查错误
if err != nil {
log.Fatal(err)
}
// 打印数据
log.Printf("Page data json: \n-------------\n%v\n-------------\n", toJson(data))
}
func toJson(v interface{}) string {
data, _ := json.MarshalIndent(v, "", "\t")
return string(data)
}
运行输出:
Page data json:
-------------
{
"Title": "Pagser Title",
"Keywords": [
"golang",
"pagser",
"goquery",
"html",
"page",
"parser",
"colly"
],
"H1": "H1 Pagser Example",
"Navs": [
{
"ID": -1,
"Name": "Index",
"Url": "/"
},
{
"ID": 2,
"Name": "Web page",
"Url": "/list/web"
},
{
"ID": 3,
"Name": "Pc Page",
"Url": "/list/pc"
},
{
"ID": 4,
"Name": "Mobile Page",
"Url": "/list/mobile"
}
]
}
-------------
配置
type Config struct {
TagName string // 结构体标签名,默认为`pagser`
FuncSymbol string // 函数符号,默认为`->`
Debug bool // 调试模式,会打印一些日志,默认为`false`
}
结构体标签语法
[goquery selector]->[function]
示例:
type ExamData struct {
Herf string `pagser:".navLink li a->attr(href)"`
}
- 结构体标签名:
pagser
- goquery选择器:
.navLink li a
- 函数符号:
->
- 函数名:
attr
- 函数参数:
href
函数
内置函数
text()
- 获取元素文本,返回string,这是默认函数,如果结构体标签中未定义函数eachText()
- 获取每个元素的文本,返回[]stringhtml()
- 获取元素内部HTML,返回stringeachHtml()
- 获取每个元素的内部HTML,返回[]stringouterHtml()
- 获取元素外部HTML,返回stringeachOutHtml()
- 获取每个元素的外部HTML,返回[]stringattr(name)
- 获取元素属性值,返回stringeachAttr()
- 获取每个元素的属性值,返回[]stringattrSplit(name, sep)
- 获取属性值并按分隔符分割为字符串数组attr('value')
- 获取元素属性值,属性名为value
,返回stringtextSplit(sep)
- 获取元素文本并按分隔符分割为字符串数组,返回[]stringeachTextJoin(sep)
- 获取每个元素的文本并连接为字符串,返回stringeq(index)
- 将匹配的元素集合减少到指定索引的元素,返回Selection用于嵌套结构
更多内置函数请查看文档
扩展函数
Markdown()
- 将HTML转换为markdown格式UgcHtml()
- 清理HTML
扩展函数需要注册:
import "github.com/foolin/pagser/extensions/markdown"
p := pagser.New()
// 注册Markdown
markdown.Register(p)
自定义函数
函数接口
type CallFunc func(node *goquery.Selection, args ...string) (out interface{}, err error)
定义全局函数
// 全局函数需要在使用前调用pagser.RegisterFunc("MyGlob", MyGlobalFunc)
// 这个全局方法必须调用pagser.RegisterFunc("MyGlob", MyGlobalFunc)
func MyGlobalFunc(node *goquery.Selection, args ...string) (out interface{}, err error) {
return "Global-" + node.Text(), nil
}
type PageData struct{
MyGlobalValue string `pagser:"->MyGlob()"`
}
func main(){
p := pagser.New()
// 注册全局函数`MyGlob`
p.RegisterFunc("MyGlob", MyGlobalFunc)
// 数据解析模型
var data PageData
// 解析HTML数据
err := p.Parse(&data, rawPageHtml)
//...
}
定义结构体函数
type PageData struct{
MyFuncValue int `pagser:"->MyFunc()"`
}
// 这个方法会自动调用,不需要注册
func (d PageData) MyFunc(node *goquery.Selection, args ...string) (out interface{}, err error) {
return "Struct-" + node.Text(), nil
}
func main(){
p := pagser.New()
// 数据解析模型
var data PageData
// 解析HTML数据
err := p.Parse(&data, rawPageHtml)
//...
}
调用语法
-
无参数函数调用
->fn()
-
单参数函数调用,单引号可选
->fn(one) ->fn('one')
-
多参数函数调用
->fn(one, two, three, ...) ->fn('one', 'two', 'three', ...)
-
带单引号和转义字符的函数调用
->fn('it\'s ok', 'two,xxx', 'three', ...)
优先级顺序
查找函数优先级顺序:
struct method -> parent method -> ... -> global
爬取页面示例
package main
import (
"encoding/json"
"github.com/foolin/pagser"
"log"
"net/http"
)
type PageData struct {
Title string `pagser:"title"`
RepoList []struct {
Names []string `pagser:"h1->textSplit('/', true)"`
Description string `pagser:"h1 + p"`
Stars string `pagser:"a.muted-link->eqAndText(0)"`
Repo string `pagser:"h1 a->attrConcat('href', 'https://github.com', $value, '?from=pagser')"`
} `pagser:"article.Box-row"`
}
func main() {
resp, err := http.Get("https://github.com/trending")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// 新建默认配置
p := pagser.New()
// 数据解析模型
var data PageData
// 解析HTML数据
err = p.ParseReader(&data, resp.Body)
// 检查错误
if err != nil {
log.Fatal(err)
}
// 打印数据
log.Printf("Page data json: \n-------------\n%v\n-------------\n", toJson(data))
}
func toJson(v interface{}) string {
data, _ := json.MarshalIndent(v, "", "\t")
return string(data)
}
Colly示例
与Colly一起使用:
p := pagser.New()
// 在每个有href属性的a元素上调用回调
collector.OnHTML("body", func(e *colly.HTMLElement) {
// 数据解析模型
var data PageData
// 解析HTML数据
err := p.ParseSelection(&data, e.Dom)
})
依赖
扩展:
更多关于golang基于goquery的HTML解析与结构体映射插件库pagser的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于golang基于goquery的HTML解析与结构体映射插件库pagser的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Pagser:GoQuery 的 HTML 解析与结构体映射插件库
Pagser 是一个基于 GoQuery 的 HTML 解析库,它允许你将 HTML 文档直接映射到 Go 结构体中,简化了网页抓取和数据提取的过程。
Pagser 核心特性
- 基于 GoQuery 的强大选择器功能
- 支持将 HTML 元素映射到结构体字段
- 提供多种内置解析器(text, attr, html 等)
- 支持自定义解析器
- 简洁的标签语法
安装 Pagser
go get github.com/foolin/pagser
基本使用方法
1. 定义映射结构体
type Post struct {
Title string `pagser:"h1"`
Content string `pagser:"div.content->html()"`
Author string `pagser:"span.author->text()"`
Date string `pagser:"span.date->attr(datetime)"`
Tags []string `pagser:"div.tags a->text()"`
Views int `pagser:"div.views->text()"`
Comments []Comment `pagser:"div.comments li"`
}
type Comment struct {
Author string `pagser:"span.author"`
Content string `pagser:"div.content"`
Time string `pagser:"span.time->attr(title)"`
}
2. 解析 HTML 到结构体
package main
import (
"fmt"
"github.com/foolin/pagser"
"log"
"net/http"
)
func main() {
// 获取 HTML 内容
resp, err := http.Get("https://example.com/post/123")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// 创建 Pagser 实例
p := pagser.New()
// 解析到结构体
var post Post
err = p.Parse(resp.Body, &post)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%+v\n", post)
}
标签语法详解
Pagser 使用结构体标签定义映射规则,基本格式为:
`pagser:"选择器->解析器(参数)"`
- 选择器:标准的 CSS 选择器
- 解析器:内置或自定义的解析函数
- 参数:某些解析器需要的额外参数
常用内置解析器
text()
- 获取元素的文本内容(默认)html()
- 获取元素的内部 HTMLattr(name)
- 获取元素的属性值trim()
- 去除文本两端的空白regex(expr)
- 使用正则表达式提取内容
高级用法
自定义解析器
p := pagser.New()
// 注册自定义解析器
p.RegisterFunc("uppercase", func(node *goquery.Selection, args ...string) (interface{}, error) {
text := node.Text()
return strings.ToUpper(text), nil
})
// 在结构体中使用
type Data struct {
Name string `pagser:"div.name->uppercase()"`
}
处理列表数据
type Product struct {
Name string `pagser:"h3"`
Price float64 `pagser:"span.price->text()"`
}
type Page struct {
Products []Product `pagser:"ul.products li"`
}
条件解析
type Article struct {
Title string `pagser:"h1.title"`
Summary string `pagser:"div.summary->text(); ifempty(div.content->text())"`
}
性能优化建议
- 复用 Pagser 实例(它是线程安全的)
- 对于复杂页面,先提取主要区域再解析
- 合理使用缓存机制
与原生 GoQuery 对比
// 原生 GoQuery 方式
doc.Find("h1").Text()
doc.Find("div.content").Html()
doc.Find("span.date").Attr("datetime")
// Pagser 方式(结构体映射)
type Data struct {
Title string `pagser:"h1"`
Content string `pagser:"div.content->html()"`
Date string `pagser:"span.date->attr(datetime)"`
}
Pagser 更适合结构化数据的提取,而原生 GoQuery 更适合复杂的、条件化的解析场景。
总结
Pagser 提供了一种声明式的方法来解析 HTML 文档,通过结构体标签定义映射规则,大大简化了网页抓取和数据提取的代码。它特别适合需要从多个页面提取相同结构数据的场景,能够显著提高开发效率并减少重复代码。