将关键词替换成指定网址

0. 需求及背景

这是工作上遇到的一个问题

对文档内容进行分词解析,如果分词内容(关键词)在字典中存在,则使用字典对应的信息页面进行替换。

一般来说这个工作是在发布文档时,进行处理的,包括手工添加与自动添加,今天的这个情况从两个方面来说不得不做成一个动态替换的方案:

  1. 内容与分词包括处理并不在同一个业务内
  2. 替换时,允许字典发生变更,即被替换的关键词是变化的

1. 处理规则

大体规则是这样的

  1. 被替换的关键词是段落内容中的,即被 p 标签包裹
  2. 已经被 a 标签包裹的关键词不被替换
  3. 被自定义标签 generalize 标签包裹的关键词不需要被替换

2. 同事的方案

同事写了一段解决方案,但并不理想

1
2
3
4
5
6
7
8
<?php
$preg = '/(<p\s[^>]*>.*(?<!<a>))奖学金((?!<\/a>).*<\/p>)/U';
$doc = "<div><p></p><p><generalize > 奖学金 </generalize><p></p>----<a > 奖学金 </a>---- 奖学金 </p><p class='dd'>----- 奖学金 ----</p></div><p></p><p > 奖学金 </p>";
echo $doc, PHP_EOL, PHP_EOL;
$doc = preg_replace("$preg","$1#替换内容#$2", $doc);
echo $doc, PHP_EOL, PHP_EOL;
exit;
?>

很明显,内容中出现的第 3 个 奖学金 并没有被替换掉。

3. 我的思路

对于上面的情况,我换了个思路

使用类似 jQuery 的 php 文档匹配类处理

  1. PHP Simple HTML DOM Parser
  2. phpQuery

先来说一下第一个类,以前爬页面,抓数据时用过,效率不高,而且查阅文档,对 selector 支持的也不够好。果断 pass

phpQuery 使用起来友好些,功能也比前者更加强大,但依然存在一个问题,就是对标签匹配的能力有偏差,对于内容中排除部份数据的能力有限,当然也有可能是我的大脑短路了一下。暂时没有通过这个方法实现目标。

4. 中间方案

回家吃完晚饭,按处理逻辑整理了一份暂时能解决问题的方案,有时间再去优化完全正则的处理方式 或 使用类库的处理方式吧。

不多说,上代码 ;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
<?php
$document = "<div><p></p><generalize > 奖学金 </generalize><p>----<a > 奖学金 </a>---- 奖学 金 </p><p class='dd'>----- <br/>奖学金 ----</p></div><br><p></p><p > 奖学金 </p>";
$document .= "<div><p></p><generalize > 奖学金 </generalize><p>----<a > 奖学金 </a>---- 奖学金 </p><p class='dd'>----- <br/>奖学金代理 ----</p></div><br><p></p><p > 奖学金代 </p>";
// 设置要忽略的标签列表,暂时包括 a 和 generalize 两个,可以再向其中添加
// 含意为 忽略标签包裹的关键词,不进行替换链接!!!
$ignoreTags = array('a', 'generalize');
// 测试使用的关键词
$test = array('奖学金','奖学金代','奖学金代理');
$orders = array(); // 临时数组,用于调整替换顺序
$words = array(); // 临时数组,用于记录要替换的关键词
// 先进预处理,优先替换长词,再替换短词 (如果忽略列表中没有 a 标签,可将下面的过程进行调整,每个步骤独立操作,而不是一个词一个词操作)
foreach ( $test as $i => $word ) {
$md5 = md5($word);
$len = strlen($word);
$orders[$len] = $md5;
$words[$md5] = $word;
}
krsort($orders);
// 进行循环替换
$count = 1;
foreach ( $orders as $i => $item ) {
$word = $words[$item];
$md5 = $item;
$count ++;
// 1. 初始替换
$document = str_replace($word, $md5, $document);
}
$count = 1;
foreach ( $orders as $i => $item ) {
$word = $words[$item];
$md5 = $item;
echo '>>> 第 ', $count, ' 次迭代, 当前替换 /关键词/ 为 [', $word, ']', PHP_EOL;
$count ++;
// 2. 恢复忽略的标签
foreach ( $ignoreTags as $_i => $_tag ) {
$_sub_reg = '/(<'.$_tag.'\s?[^>]*>.*) '.$md5.' (.*<\/'.$_tag.'>)/U';
$document = preg_replace($_sub_reg, '$1'.$word.'$2', $document);
}
// 3. 替换该替换的数据
$rep = '<a href="https://www.baidu.com/s?wd'.$word.'" target="_blank">'.$word.'</a>';
$document = str_replace($md5, $rep, $document);
}
echo '[FANIAL] 最终结果 : ', $document, PHP_EOL;
?>

5. 哎,洗洗碎了

先这样吧。已经挺晚了 :P