锚文本,即超链接的文本部分,它在网页中扮演着至关重要的角色。通过点击锚文本,用户可以方便地在网页间进行跳转,从而极大地提升了用户体验。同时,在搜索引擎优化(SEO)领域,锚文本也发挥着不可忽视的作用。搜索引擎会通过分析锚文本的内容,来判断链接页面的主题和相关性,进而影响页面的排名。因此,合理地设置锚文本,对于提升网站的SEO效果具有重要意义。
自动化锚文本实现的背景和需求
随着网站内容的不断增多,手动设置锚文本变得愈发繁琐和耗时。为了提高工作效率,减少人力成本,自动化锚文本的实现成为了迫切的需求。通过自动化手段,可以快速地生成大量的锚文本,提高网站的内链建设效率,进而提升网站的SEO效果。
锚文本的收集
在实现自动化锚文本之前,首先需要收集网站中的文本内容。下面以安企CMS的自动化锚文本功能为例,介绍锚文本的收集策略。
安企CMS提供了自动提取锚文本和手动提取锚文本两种收集锚文本的方式。如果选择了自动提取锚文本,那么程序就会在用户添加文章的时候,自动解析文章的关键词,并将关键词自动添加为当前文章的锚文本。手动处理的话,则提供了逐个关键词填写以及批量导入关键词的两种方式。
自动化锚文本的策略
制定自动化锚文本的策略是关键步骤之一。这包括确定锚文本的生成规则,如关键词的选择、锚文本的长度、出现的位置等。
自定义锚文本密度
安企CMS在处理锚文本的生成策略时,提供了自定义锚文本密度的选项,用户可以自主选择锚文本的密度。按关键词由长到短匹配
安企CMS采用了长度优先的策略,就是说,如果内容中不同锚文本,优先使用长度最长的锚文本。内容里出现AAB这样的关键词时,锚文本会给AAB,而不是AA,或AB仅匹配一次
如果文章中有多个相同的锚文本关键词,则只给第一个关键词添加上锚文本,而后续的关键词只进行加粗处理,保证一个内容里同一个关键词锚文本仅出现一次,同一个URL也只做一次锚文本,其它的则会加粗显示。锚文本生成方式
安企CMS采用了长度优先的策略。就是说,如果有相同链接的不同锚文本,优先使用长度最长的锚文本。如果文章中有多个相同的锚文本关键词,则只给第一个关键词添加上锚文本,而后续的关键词只进行加粗处理。
安企CMS采用了自动插入关键词以及手动批量更新关键词两种方式,如果选择了自动插入关键词,则会在发布文章的时候,自动将文章中合适的关键词用锚文本来替代,实现锚文本的生成。如果选择了手动批量更新关键词,则会在锚文本页面中,提供批量更新锚文本的功能,用户可以按照自己的需求,手动更新锚文本。
自动化锚文本的实现代码
说明:由于安企CMS 使用的是 GoLang 开发,因此以下代码为 GoLang 语言的实现方式。
func AutoInsertAnchors(anchors []*model.Anchor, content string, link string) string {
if len(anchors) == 0 {
//没有关键词,终止执行
return ""
}
//获取纯文本字数
stripedContent := library.StripTags(content)
contentLen := len([]rune(stripedContent))
// 获取锚文本密度
if PluginAnchor.AnchorDensity < 20 {
//默认设置200
PluginAnchor.AnchorDensity = 200
}
// 判断是否是Markdown,如果开头是标签,则认为不是Markdown
isMarkdown := false
if !strings.HasPrefix(strings.TrimSpace(content), "<") {
isMarkdown = true
}
//计算最大可以替换的数量
maxAnchorNum := int(math.Ceil(float64(contentLen) / float64(PluginAnchor.AnchorDensity)))
// 定义一个替换结构体,用于存储替换的内容
type replaceType struct {
Key string
Value string
}
// 记录已存在的关键词和链接
existsKeywords := map[string]bool{}
existsLinks := map[string]bool{}
var replacedMatch []*replaceType
numCount := 0
//所有的a标签计数,并临时替换掉,防止后续替换影响
reg, _ := regexp.Compile("(?i)<a[^>]*>(.*?)</a>")
content = reg.ReplaceAllStringFunc(content, func(s string) string {
reg := regexp.MustCompile("(?i)<a\\s*[^>]*href=[\"']?([^\"']*)[\"']?[^>]*>(.*?)</a>")
match := reg.FindStringSubmatch(s)
if len(match) > 2 {
existsKeywords[strings.ToLower(match[2])] = true
existsLinks[strings.ToLower(match[1])] = true
}
key := fmt.Sprintf("{$%d}", numCount)
replacedMatch = append(replacedMatch, &replaceType{
Key: key,
Value: s,
})
numCount++
return key
})
//所有的strong标签替换掉
reg, _ = regexp.Compile("(?i)<strong[^>]*>(.*?)</strong>")
content = reg.ReplaceAllStringFunc(content, func(s string) string {
key := fmt.Sprintf("{$%d}", numCount)
replacedMatch = append(replacedMatch, &replaceType{
Key: key,
Value: s,
})
numCount++
return key
})
// 匹配 Markdown 格式的锚文本,同时要考虑别替换掉图片
// [keyword](url)
reg, _ = regexp.Compile(`(?i)(.?)\[(.*?)]\((.*?)\)`)
content = reg.ReplaceAllStringFunc(content, func(s string) string {
match := reg.FindStringSubmatch(s)
if len(match) > 2 && match[1] != "!" {
existsKeywords[strings.ToLower(match[2])] = true
existsLinks[strings.ToLower(match[3])] = true
}
key := fmt.Sprintf("{$%d}", numCount)
replacedMatch = append(replacedMatch, &replaceType{
Key: key,
Value: s,
})
numCount++
return key
})
// Markdown 格式的加粗
// **Keyword**
reg, _ = regexp.Compile(`(?i)\*\*(.*?)\*\*`)
content = reg.ReplaceAllStringFunc(content, func(s string) string {
key := fmt.Sprintf("{$%d}", numCount)
replacedMatch = append(replacedMatch, &replaceType{
Key: key,
Value: s,
})
numCount++
return key
})
//过滤所有属性,防止在自动锚文本的时候,会将标签属性也替换
reg, _ = regexp.Compile("(?i)</?[a-z0-9]+(\\s+[^>]+)>")
content = reg.ReplaceAllStringFunc(content, func(s string) string {
key := fmt.Sprintf("{$%d}", numCount)
replacedMatch = append(replacedMatch, &replaceType{
Key: key,
Value: s,
})
numCount++
return key
})
if len(existsLinks) < maxAnchorNum {
//开始替换关键词
for _, anchor := range anchors {
if anchor.Title == "" {
continue
}
if strings.HasSuffix(anchor.Link, link) {
//遇到当前url,跳过
continue
}
//已经存在存在的关键词,或者链接,跳过
if existsKeywords[strings.ToLower(anchor.Title)] || existsLinks[strings.ToLower(anchor.Link)] {
continue
}
//开始替换
replaceNum := 0
replacer := strings.NewReplacer("\\", "\\\\", "/", "\\/", "{", "\\{", "}", "\\}", "^", "\\^", "$", "\\$", "*", "\\*", "+", "\\+", "?", "\\?", ".", "\\.", "|", "\\|", "-", "\\-", "[", "\\[", "]", "\\]", "(", "\\(", ")", "\\)")
matchName := replacer.Replace(anchor.Title)
reg, _ = regexp.Compile(fmt.Sprintf("(?i)%s", matchName))
content = reg.ReplaceAllStringFunc(content, func(s string) string {
replaceHtml := ""
key := ""
if replaceNum == 0 {
//第一条替换为锚文本
if isMarkdown {
replaceHtml = fmt.Sprintf("[%s](%s)", s, anchor.Link)
} else {
replaceHtml = fmt.Sprintf("<a href=\"%s\" data-anchor=\"%d\">%s</a>", anchor.Link, anchor.Id, s)
}
key = fmt.Sprintf("{$%d}", numCount)
//加入计数
existsLinks[anchor.Link] = true
existsKeywords[anchor.Title] = true
} else {
//其他则加粗
if isMarkdown {
replaceHtml = fmt.Sprintf("**%s**", s)
} else {
replaceHtml = fmt.Sprintf("<strong data-anchor=\"%d\">%s</strong>", anchor.Id, s)
}
key = fmt.Sprintf("{$%d}", numCount)
}
replaceNum++
replacedMatch = append(replacedMatch, &replaceType{
Key: key,
Value: replaceHtml,
})
numCount++
return key
})
//判断数量是否达到了,达到了就跳出
if len(existsLinks) >= maxAnchorNum {
break
}
}
}
//关键词替换完毕,将原来替换的重新替换回去,需要倒序
for i := len(replacedMatch) - 1; i >= 0; i-- {
content = strings.Replace(content, replacedMatch[i].Key, replacedMatch[i].Value, 1)
}
// 返回替换后的内容
return content
}