内部链接目标

  • SEO优化,在博客文章中渲染时对关键词进行替换添加链接1,不改变博客原文
  • 关键词链接到电商商品、活动页、分类页等
  • 解决url收录问题,提升SEO流量,提升SEO销售额

词库和链接导入

  • 读取Excel文件或csv文件

前端渲染替换处理

  • 一个关键词(完全匹配)只能链接一条URL
  • 同一个文章页面同一个关键词只链接一次(首次出现的那个词)
  • 同组长词优先链接
  • 已经在a标签内的关键词跳过,不进行添加
  • 每篇博客文章最多只添加10个锚链接
 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
<?php

class B {
 
    /**
     * 替换博客文章关键词链接
     * @param string $content
     * @param int $limitNum 替换上限次数
     * @return string|string[]|null
     */
    public function replaceKeywordsInternalLink($content, $limitNum = 10)
    {
        $keywordsList = [
            [
                'keywords' => '关键词内链替换',
                'link' => 'https://www.stormwan.com/2020/02/%E5%85%B3%E9%94%AE%E8%AF%8D%E5%86%85%E9%93%BE/',
            ],
            [
                'keywords' => 'stormwan',
                'link' => 'https://www.stormwan.com',
            ],
        ];
    
        // 将content内a标签全部替换
        $pattern = '|(<a[^>]*>)(.*?)(</a>)|s';
        $replaceMap = [];
        if (preg_match_all($pattern, $content, $matches)) {
            if ($matches[0]) {
                foreach ($matches[0] as $item) {
                    $key = '&&&' . md5($item) . '&&&';
                    $replaceMap[$key] = $item;
                }
            }
        }
        if ($replaceMap) {
            $content = str_replace(array_values($replaceMap), array_keys($replaceMap), $content);
        }
    
        $hasReplace = [];
    
        if ($keywordsList) {
            $readnum = 0;
            foreach ($keywordsList as $key => $val) {
                // 正则替换区分大小写, 否则替换时改变原文单词大小写
                if (in_array(strtolower($val['keywords']), $hasReplace)) {
                    continue;
                }
    
                $title = $val['keywords'];
                $url   = $val['link'];
                $str   = sprintf('<a href="%s">%s</a>', $url, $title);
                $index = mb_strpos($content, $title);
    
                $content = preg_replace('/(?!<[^>]*)\b' . $this->escapePregQuote($title) . '\b(?![^<]*>)/', $str, $content, 1);
                if (is_numeric($index)) {
                    $readnum += 1;
                    $hasReplace[] = strtolower($title);
                }
    
                // 匹配到上限关键词就退出
                if ($readnum == $limitNum) {
                    break;
                }
            }
        }
    
        // 还原替换掉的a标签
        if ($replaceMap) {
            $content = str_replace(array_keys($replaceMap), array_values($replaceMap), $content);
        }
    
        return $content;
    }
    
    /**
     * 转义正则表达式字符
     * @param string $title
     * @param string $delimiter 默认分割符
     * @return string
     */
    public function escapePregQuote($title, $delimiter = '/')
    {
        return preg_quote($title, $delimiter);
    }
    
}

思考

  • 是否可以添加博客时弹框选择关键词内链
  • 目前实现的是每篇文章10个词, 优先按长尾关键词处理
  • 关键词量很大要怎么处理, 关键词量远大于一篇文章的单词, 应该结合业务出发选择一个节省成本的方案

解决变态空格问题

前面excel或csv导入需要过滤掉收尾空格,开发完后发现 trim 后还有空格没过滤掉,首先将字符分隔开,str_split,然后循环每个字节查看他的编码,ord()查看值,然后 trim($str, chr(194).chr(160)) 或者使用 preg_replace 替换

遇到多语言翻译解决方案

分析文本内容, 里面有HTML标签, 需要将标签处理后, 调用翻译接口, 然后替换回来

 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
<?php

class A {
    /**
     * 解析html字符串(断句、不翻译纯数字)
     * @param string $str 待解析文本
     * @return array 解析出的字符串(排除html标签,断句等)
     */
    public static function resolveHtml($str = '')
    {
        $return = [];

        //第一个位置的标记
        $pos = strpos($str, '<');
        if ($pos) {
            $posText = substr($str, 0, $pos);
            if (!empty($posText) && !ctype_space($posText) && preg_match('/[\x7f-\xff_A-Za-z]/', $posText)) {
                $posText = explode("\n", str_replace("\r", "\n", $posText));
                $posText = array_filter($posText);
                $return = array_merge($return, $posText);
            }
        }

        //检测结束标记
        $str = substr($str, $pos);
        $startPos = strpos($str, '>');
        if (!$startPos) {
            if (!empty($str) && !ctype_space($str) && preg_match('/[\x7f-\xff_A-Za-z]/', $str)) {
                $text = explode("\n", str_replace("\r", "\n", $str));
                $text = array_filter($text);
                $return = array_merge($return, $text);
            }
            return $return;
        }

        //递归处理文本字符
        $str = substr($str, $startPos + 1);
        $endPos = strpos($str, '<');
        $text = substr($str, 0, $endPos);
        if (!empty($text) && !ctype_space($text) && preg_match('/[\x7f-\xff_A-Za-z]/', $text)) {
            $text = explode("\n", str_replace("\r", "\n", $text));
            $text = array_filter($text);
            $return = array_merge($return, $text);
        }
        $resolve = self::resolveHtml(substr($str, $endPos));

        return $resolve ? array_merge($return, $resolve) : $return;
    }

    /**
     * 还原回标签
     * @param string $str  文本
     * @param array $resolve  解析出的纯字符数组
     * @param array $translate 纯字符翻译后的数组
     * @return string 拼装翻译后的文本
     */
    public static function resolveBack($str, $resolve, $translate)
    {
        if (!$resolve) {
            return $str;
        }

        $result = '';
        foreach ($resolve as $key=>$item)
        {
            //$str = str_replace($item, $translate[$key], $str);

            //非贪婪匹配替换
            $pos = strpos($str, $item) + mb_strlen($item);
            $prefixStr = mb_substr($str, 0, $pos); //记录需要替换的字符串
            $str = mb_substr($str, $pos); //记录剩余字符串
            $prefixStr = str_replace($item, $translate[$key], $prefixStr);
            $result .=  $prefixStr;
        }

        return $result;
    }
    
}

  1. 内部链接作用 ↩︎