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)"`
}
  1. 结构体标签名:pagser
  2. goquery选择器:.navLink li a
  3. 函数符号:->
  4. 函数名:attr
  5. 函数参数:href

函数

内置函数

  • text() - 获取元素文本,返回string,这是默认函数,如果结构体标签中未定义函数
  • eachText() - 获取每个元素的文本,返回[]string
  • html() - 获取元素内部HTML,返回string
  • eachHtml() - 获取每个元素的内部HTML,返回[]string
  • outerHtml() - 获取元素外部HTML,返回string
  • eachOutHtml() - 获取每个元素的外部HTML,返回[]string
  • attr(name) - 获取元素属性值,返回string
  • eachAttr() - 获取每个元素的属性值,返回[]string
  • attrSplit(name, sep) - 获取属性值并按分隔符分割为字符串数组
  • attr('value') - 获取元素属性值,属性名为value,返回string
  • textSplit(sep) - 获取元素文本并按分隔符分割为字符串数组,返回[]string
  • eachTextJoin(sep) - 获取每个元素的文本并连接为字符串,返回string
  • eq(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)

    //...
}

调用语法

  1. 无参数函数调用

    ->fn()
    
  2. 单参数函数调用,单引号可选

    ->fn(one)
    ->fn('one')
    
  3. 多参数函数调用

    ->fn(one, two, three, ...)
    ->fn('one', 'two', 'three', ...)
    
  4. 带单引号和转义字符的函数调用

    ->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 核心特性

  1. 基于 GoQuery 的强大选择器功能
  2. 支持将 HTML 元素映射到结构体字段
  3. 提供多种内置解析器(text, attr, html 等)
  4. 支持自定义解析器
  5. 简洁的标签语法

安装 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 选择器
  • 解析器:内置或自定义的解析函数
  • 参数:某些解析器需要的额外参数

常用内置解析器

  1. text() - 获取元素的文本内容(默认)
  2. html() - 获取元素的内部 HTML
  3. attr(name) - 获取元素的属性值
  4. trim() - 去除文本两端的空白
  5. 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())"`
}

性能优化建议

  1. 复用 Pagser 实例(它是线程安全的)
  2. 对于复杂页面,先提取主要区域再解析
  3. 合理使用缓存机制

与原生 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 文档,通过结构体标签定义映射规则,大大简化了网页抓取和数据提取的代码。它特别适合需要从多个页面提取相同结构数据的场景,能够显著提高开发效率并减少重复代码。

回到顶部