錨文本,即超鏈接的文本部分,它在網(wǎng)頁中扮演著至關(guān)重要的角色。通過點擊錨文本,用戶可以方便地在網(wǎng)頁間進(jìn)行跳轉(zhuǎn),從而極大地提升了用戶體驗。同時,在搜索引擎優(yōu)化(SEO)領(lǐng)域,錨文本也發(fā)揮著不可忽視的作用。搜索引擎會通過分析錨文本的內(nèi)容,來判斷鏈接頁面的主題和相關(guān)性,進(jìn)而影響頁面的排名。因此,合理地設(shè)置錨文本,對于提升網(wǎng)站的SEO效果具有重要意義。
自動化錨文本實現(xiàn)的背景和需求
隨著網(wǎng)站內(nèi)容的不斷增多,手動設(shè)置錨文本變得愈發(fā)繁瑣和耗時。為了提高工作效率,減少人力成本,自動化錨文本的實現(xiàn)成為了迫切的需求。通過自動化手段,可以快速地生成大量的錨文本,提高網(wǎng)站的內(nèi)鏈建設(shè)效率,進(jìn)而提升網(wǎng)站的SEO效果。
錨文本的收集
在實現(xiàn)自動化錨文本之前,首先需要收集網(wǎng)站中的文本內(nèi)容。下面以安企CMS的自動化錨文本功能為例,介紹錨文本的收集策略。
安企CMS提供了自動提取錨文本和手動提取錨文本兩種收集錨文本的方式。如果選擇了自動提取錨文本,那么程序就會在用戶添加文章的時候,自動解析文章的關(guān)鍵詞,并將關(guān)鍵詞自動添加為當(dāng)前文章的錨文本。手動處理的話,則提供了逐個關(guān)鍵詞填寫以及批量導(dǎo)入關(guān)鍵詞的兩種方式。

自動化錨文本的策略
制定自動化錨文本的策略是關(guān)鍵步驟之一。這包括確定錨文本的生成規(guī)則,如關(guān)鍵詞的選擇、錨文本的長度、出現(xiàn)的位置等。

自定義錨文本密度
安企CMS在處理錨文本的生成策略時,提供了自定義錨文本密度的選項,用戶可以自主選擇錨文本的密度。按關(guān)鍵詞由長到短匹配
安企CMS采用了長度優(yōu)先的策略,就是說,如果內(nèi)容中不同錨文本,優(yōu)先使用長度最長的錨文本。內(nèi)容里出現(xiàn)AAB這樣的關(guān)鍵詞時,錨文本會給AAB,而不是AA,或AB僅匹配一次
如果文章中有多個相同的錨文本關(guān)鍵詞,則只給第一個關(guān)鍵詞添加上錨文本,而后續(xù)的關(guān)鍵詞只進(jìn)行加粗處理,保證一個內(nèi)容里同一個關(guān)鍵詞錨文本僅出現(xiàn)一次,同一個URL也只做一次錨文本,其它的則會加粗顯示。錨文本生成方式
安企CMS采用了長度優(yōu)先的策略。就是說,如果有相同鏈接的不同錨文本,優(yōu)先使用長度最長的錨文本。如果文章中有多個相同的錨文本關(guān)鍵詞,則只給第一個關(guān)鍵詞添加上錨文本,而后續(xù)的關(guān)鍵詞只進(jìn)行加粗處理。
安企CMS采用了自動插入關(guān)鍵詞以及手動批量更新關(guān)鍵詞兩種方式,如果選擇了自動插入關(guān)鍵詞,則會在發(fā)布文章的時候,自動將文章中合適的關(guān)鍵詞用錨文本來替代,實現(xiàn)錨文本的生成。如果選擇了手動批量更新關(guān)鍵詞,則會在錨文本頁面中,提供批量更新錨文本的功能,用戶可以按照自己的需求,手動更新錨文本。
自動化錨文本的實現(xiàn)代碼
說明:由于安企CMS 使用的是 GoLang 開發(fā),因此以下代碼為 GoLang 語言的實現(xiàn)方式。
func AutoInsertAnchors(anchors []*model.Anchor, content string, link string) string {
if len(anchors) == 0 {
//沒有關(guān)鍵詞,終止執(zhí)行
return ""
}
//獲取純文本字?jǐn)?shù)
stripedContent := library.StripTags(content)
contentLen := len([]rune(stripedContent))
// 獲取錨文本密度
if PluginAnchor.AnchorDensity < 20 {
//默認(rèn)設(shè)置200
PluginAnchor.AnchorDensity = 200
}
// 判斷是否是Markdown,如果開頭是標(biāo)簽,則認(rèn)為不是Markdown
isMarkdown := false
if !strings.HasPrefix(strings.TrimSpace(content), "<") {
isMarkdown = true
}
//計算最大可以替換的數(shù)量
maxAnchorNum := int(math.Ceil(float64(contentLen) / float64(PluginAnchor.AnchorDensity)))
// 定義一個替換結(jié)構(gòu)體,用于存儲替換的內(nèi)容
type replaceType struct {
Key string
Value string
}
// 記錄已存在的關(guān)鍵詞和鏈接
existsKeywords := map[string]bool{}
existsLinks := map[string]bool{}
var replacedMatch []*replaceType
numCount := 0
//所有的a標(biāo)簽計數(shù),并臨時替換掉,防止后續(xù)替換影響
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標(biāo)簽替換掉
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
})
//過濾所有屬性,防止在自動錨文本的時候,會將標(biāo)簽屬性也替換
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 {
//開始替換關(guān)鍵詞
for _, anchor := range anchors {
if anchor.Title == "" {
continue
}
if strings.HasSuffix(anchor.Link, link) {
//遇到當(dāng)前url,跳過
continue
}
//已經(jīng)存在存在的關(guān)鍵詞,或者鏈接,跳過
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)
//加入計數(shù)
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
})
//判斷數(shù)量是否達(dá)到了,達(dá)到了就跳出
if len(existsLinks) >= maxAnchorNum {
break
}
}
}
//關(guān)鍵詞替換完畢,將原來替換的重新替換回去,需要倒序
for i := len(replacedMatch) - 1; i >= 0; i-- {
content = strings.Replace(content, replacedMatch[i].Key, replacedMatch[i].Value, 1)
}
// 返回替換后的內(nèi)容
return content
}