Anchor text, the text part of the hyperlink, plays a crucial role in the web page. ‌ By clicking on the anchor text, users can easily jump between web pages, thus greatly improving the user experience. At the same time, in the field of search engine optimization (SEO), anchor text also plays an important role. ‌Search engines will analyze the content of anchor text to judge the theme and relevance of the linked page, which will affect the page's ranking. Therefore, setting up anchor text reasonably is of great significance to improving the website's SEO effect. ‌

Background and requirements for automated anchor text implementation

As the website content continues to increase, manually setting anchor text has become increasingly cumbersome and time-consuming. In order to improve work efficiency and reduce labor costs, the realization of automated anchor text has become an urgent need. ‌ Through automation, a large number of anchor text can be quickly generated, improving the efficiency of internal link construction of the website, and thus improving the SEO effect of the website. ‌

Collection of anchor text

Before implementing automated anchor text, you first need to collect text content from the website. ‌The following is to take the automated anchor text function of Anqi CMS as an example to introduce the anchor text collection strategy. ‌

Anqi CMS provides two ways to collect anchor text automatically and manually extract anchor text. If you select Automatically extract anchor text, the program will automatically parse the keywords of the article when the user adds the article and automatically add the keywords as the anchor text of the current article. If processed manually, two ways are provided to fill in keywords one by one and import keywords in batches.

Strategies for automating anchor text

Developing strategies for automated anchor texts is one of the key steps. ‌This includes determining the generation rules of anchor text, such as keyword selection, length of anchor text, location of occurrence, etc.

  • Custom anchor text density
    When handling anchor text generation strategy, Anqi CMS provides the option to customize anchor text density, and users can independently select the density of anchor text.

  • Match from long to short by keyword
    Anqi CMS adopts a length-first strategy, that is, if the anchor text in the content is different, the anchor text with the longest length is preferred. When keywords like AAB appear in the content, the anchor text will be given to AAB, not AA, or AB.

  • Match only once
    If there are multiple same anchor text keywords in the article, only the anchor text is added to the first keyword, and the subsequent keywords are only bolded to ensure that the anchor text of the same keyword in one content only appears once, and the same URL is only used as anchor text once, and the others will be displayed in bold.

  • How to generate anchor text
    Anqi CMS adopts a length-first strategy. That is, if there are different anchor texts with the same link, the longest length anchor text is preferred. If there are multiple identical anchor text keywords in the article, only the anchor text is added to the first keyword, and the subsequent keywords are only bolded.

Anqi CMS adopts two methods: automatic insertion of keywords and manual batch update of keywords. If you choose to automatically insert keywords, the appropriate keywords in the article will be automatically replaced with anchor text when publishing the article to realize the generation of anchor text. If you select the manual batch update keyword, the function of batch update of anchor text will be provided on the anchor text page. Users can manually update the anchor text according to their needs.

Implementation code for automated anchor text

Note: Since Anqi CMS uses GoLang development, the following code is the implementation method of the GoLang language.

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
}