前段时间接触到新闻页面的提取问题,发现了python 实现的 gne ,测试一段时间,效果很好,但还不适合个人的需求,于是就用 go 来实现类似的功能。
使用 gne 面临的问题
- 图片独立列表,不适合排版,我想保留内容与图片的排序
- 对于一些正文内容不多的页面,提取不够准确,比如,正文文字很少,图片很多
- python 实现,嵌入到已有 go 应用麻烦
主要考虑到上面三个问题,用 go 尝试提取。go 的库没有 python 那么丰富,但也够用了,net/html
标准库的功能也满足了需求。html
库负责把 outerHtml string
解析为 html node
,剩下的事情就是提取逻辑的处理。
gne 是根据《基于文本及符号密度的网页正文提取方法》论文实现,其理论比较复杂,个人没去深入理解,也不想用 go 直接翻译,就根据文章正文的另外一些特征来识别,方法也简单粗暴,硬编码少,效果还不错,可以提取大部分的新闻网站内容。
- 参见在线提取测试 https://pylist.com/tools/gogne
在大框里输入页面 html
内容,点 提取
按钮就会有结果,可以使用下图的方式来复制页面 html 内容
遍历 html node
下面是用简单方法遍历html node并提取 p
标签的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package main
import (
"bytes"
"fmt"
"io"
"strings"
"golang.org/x/net/html"
"golang.org/x/net/html/atom"
)
const htm = `<!DOCTYPE html>
<html>
<head>
<title>title</title>
</head>
<body>
<h1>h1 text</h1>
<div>
<p>p1</p>
<p>p2</p>
</div>
<em>em text</em>
<p>p outer</p>
<footer>
footer text
<p>p in footer</p>
</footer>
copyright
<p></p>
</body>
</html>`
func main() {
doc, err := html.Parse(strings.NewReader(htm))
if err != nil {
fmt.Println(err)
return
}
// 查找所有 <p> 子节点的文本节点
matcher := func(node *html.Node) (keep bool, exit bool) {
if node.Type == html.TextNode && strings.TrimSpace(node.Data) != "" {
exit = true
}
if node.DataAtom == atom.P {
keep = true
}
return
}
nodes := TraverseNode(doc, matcher)
for i, node := range nodes {
fmt.Println(i, renderNode(node))
}
}
// TraverseNode 收集与给定功能匹配的节点
func TraverseNode(doc *html.Node, matcher func(node *html.Node) (bool, bool)) (nodes []*html.Node) {
var keep, exit bool
var f func(*html.Node)
f = func(n *html.Node) {
keep, exit = matcher(n)
if keep {
nodes = append(nodes, n)
}
if exit {
return
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
f(c)
}
}
f(doc)
return nodes
}
func renderNode(n *html.Node) string {
var buf bytes.Buffer
w := io.Writer(&buf)
html.Render(w, n)
return buf.String()
}
运行输出
1
2
3
4
5
0 <p>p1</p>
1 <p>p2</p>
2 <p>p outer</p>
3 <p>p in footer</p>
4 <p></p>
这个遍历功能很有用,可以通过修改 matcher
来查找自己想要的标签。
比如下面是查找 class="head1"
的node :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
matcher := func(node *html.Node) (keep bool, exit bool) {
findAttrKey := "class"
findAttrVal := "head1"
if node.Type == html.ElementNode {
var s string
var ok bool
for _, attr := range node.Attr {
if attr.Key == findAttrKey {
s = attr.Val
ok = true
break
}
}
if ok && s == findAttrVal {
keep = true
}
}
return
}
解决方法
归根到底就是解析字符串或html node ,提取自己想要的内容。想造轮子的可以使用原生 net/html
库,操作简单,性能好。图方便就用现成的,如
- https://github.com/PuerkitoBio/goquery
- https://github.com/antchfx/htmlquery
- https://github.com/andybalholm/cascadia
XPath 选取节点
另外还需熟悉使用 XPath 选取文档中节点。下面列出了最有用的路径表达式:
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点。 |
/ | 从根节点选取(取子节点)。 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置(取子孙节点)。 |
. | 选取当前节点。 |
.. | 选取当前节点的父节点。 |
@ | 选取属性。 |
参考
- https://godoc.org/golang.org/x/net/html
- https://github.com/kingname/GeneralNewsExtractor
- https://pylist.com/t/1576112934
本文网址: https://pylist.com/topic/196.html 转摘请注明来源
1 thoughts on "Golang 实现新闻网页提取正文内容"
大佬你好,有 Golang 版的源码可以学习吗,太强了!