使用大量术语,搜索页面文本并用链接替换单词

时间:2012-10-10 21:10:21

标签: javascript jquery ajax

前一段时间我发布了this问题,询问是否可以将文本转换为HTML链接,如果它们与我的数据库中的术语列表匹配。

我有一个相当庞大的术语列表 - 大约6000个。

关于那个问题的accepted answer非常棒,但从未使用过XPath,当问题开始出现时,我感到很茫然。有一次,在摆弄代码之后,我设法在我们的数据库中添加了超过40,000个随机字符 - 其中大部分需要手动删除。从那以后,我对这个想法失去了信心,更简单的PHP解决方案根本没有足够的效率来处理数据量和术语数量。

我对解决方案的下一次尝试是编写一个JS脚本,一旦页面加载,就会检索这些术语并将它们与页面上的文本进行匹配。

This answer有一个我想尝试的想法。

我会使用AJAX从数据库中检索术语,以构建如下对象:

var words = [
    {
        word: 'Something',
        link: 'http://www.something.com'
    },
    {
        word: 'Something Else',
        link: 'http://www.something.com/else'
    }
];

构建对象后,我会使用这种代码:

//for each array element
$.each(words,
    function() {
        //store it ("this" is gonna become the dom element in the next function)
        var search = this;
        $('.message').each(
            function() {
                //if it's exactly the same
                if ($(this).text() === search.word) {
                    //do your magic tricks
                    $(this).html('<a href="' + search.link + '">' + search.link + '</a>');
                }
            }
        );
    }
);

现在,乍一看,这里有一个主要问题:有6,000个术语,这段代码能以任何有效的方式完成我想做的事吗?

一个选项可能是在AJAX与之通信的PHP脚本中执行一些开销。例如,我可以发送帖子的ID,然后PHP脚本可以使用SQL语句从帖子中检索所有信息并将其与所有6,000个术语进行匹配..然后对JavaScript的返回调用可能只是匹配这些术语会大大减少上面jQuery的匹配数量(最多约50个)。

如果脚本花了几秒钟“加载”在用户的浏览器上,我没有问题,只要它不会影响他们的CPU使用率或类似的东西。

所以,两个问题合而为一:

  • 我可以做这个吗?
  • 我可以采取哪些措施使其尽可能高效?

提前致谢,

5 个答案:

答案 0 :(得分:2)

您可以在insert上缓存结果。

基本上,当有人插入新帖子时,您不必将其插入数据库,而是运行替换过程。

如果你的帖子在DB中存储如下

Table: Posts
id        post
102       "Google is a search engine"

您可以创建另一个表

Table: cached_Posts
id       post_id   date_generated   cached_post                             
1        102       2012-10-10       <a href="http://google.com">Google</a> is a search engine"

检索帖子时,检查它是否存在于cached_Posts表中。

您应该保留原始内容的原因可能是您可能需要添加新关键字来替换。您只需重新制作缓存即可。

通过这种方式,不需要客户端JS,并且每个帖子只需要执行一次,因此您的结果应该非常快。

答案 1 :(得分:1)

正如reverseSpear所说,你不应该因为你无法让它发挥作用而放弃PHP。一个Javascript解决方案,虽然减轻了服务器上的负载,但最终用户看起来可能会变慢。您也可以随时缓存服务器端解决方案,这在客户端无法实现。

话虽如此,这些是我对你的Javascript的看法。我自己没有尝试过这样的事情所以我不能评论你是否可以让它发挥作用但是我可以看到一些可能存在问题的事情:

  1. jQuery的$.each()函数虽然非常有用,但效率不高。尝试运行此基准测试,您将看到我的意思:http://jsperf.com/jquery-each-vs-for-loops/9

  2. 如果您要在循环的每次迭代中运行$('.message'),那么您可能会进行大量相当昂贵的DOM遍历。如果可能,您应该在开始循环words

  3. 之前将此操作的结果缓存在变量中
  4. 您是否依赖于“搜索”文本的每个实例,这些实例是由具有类message的任何元素封装而且没有其他文本围绕它?因为那是if ($(this).text() === search.word) {行所暗示的。在您的其他问题中,您似乎建议您有更多关于要替换的术语的文本,在这种情况下,您可能需要查看正则表达式来执行替换。您还需要确保文本未包含在<a>标记中。

答案 2 :(得分:1)

我想出的是相对简单的东西。对不起,没有彻底的测试,也没有性能测试。我保证它可以进一步优化,我只是没有时间去做。我提出了一些评论,以使其更简单http://pastebin.com/nkdTSvi6 StackOverflow可能有点长,但无论如何我都会在这里发布。为了更舒适的观看,可以使用pastebin。

function buildTrie(hash) {
    "use strict";
    // A very simple function to build a Trie
    // we could compress this later, but simplicity
    // is better for this example. If we don't
    // perform well, we'll try to optimize this a bit
    // there is a room for optimization here.
    var p, result = {}, leaf, i;
    for (p in hash) {
        if (hash.hasOwnProperty(p)) {
            leaf = result;
            i = 0;
            do {
                if (p[i] in leaf) {
                    leaf = leaf[p[i]];
                } else {
                    leaf = leaf[p[i]] = {};
                }
                i += 1;
            } while (i < p.length);
            // since, obviously, no character
            // equals to empty character, we'll
            // use it to store the reference to the
            // original value
            leaf[""] = hash[p];
        }
    }
    return result;
}

function prefixReplaceHtml(html, trie) {
    "use strict";
    var i, len = html.length, result = [], lastMatch = 0,
        current, leaf, match, matched, replacement;
    for (i = 0; i < len; i += 1) {
        current = html[i];
        if (current === "<") {
            // don't check for out of bounds access
            // assume we never face a situation, when
            // "<" is the last character in an HTML
            if (match) {
                result.push(
                    html.substring(lastMatch, i - matched.length),
                    "<a href=\"", match, "\">", replacement, "</a>");
                lastMatch = i - matched.length + replacement.length;
                i = lastMatch - 1;
            } else {
                if (matched) {
                    // go back to the second character of the
                    // matched string and try again
                    i = i - matched.length;
                }
            }
            matched = match = replacement = leaf = "";
            if (html[i + 1] === "a") {
                // we want to skip replacing inside
                // anchor tags. We also assume they
                // are never nested, as valid HTML is
                // against that idea
                if (html[i + 2] in
                    { " " : 1, "\t" : 1, "\r" : 1, "\n" : 1 }) {
                    // this is certainly an anchor
                    i = html.indexOf("</a", i + 3) + 3;
                    continue;
                }
            }
            // if we got here, it's a regular tag, just look
            // for terminating ">"
            i = html.indexOf(">", i + 1);
            continue;
        }
        // if we got here, we need to start checking
        // for the match in the trie
        if (!leaf) {
            leaf = trie;
        }
        leaf = leaf[current];
        // we prefer longest possible match, just like POSIX
        // regular expressions do
        if (leaf && ("" in leaf)) {
            match = leaf[""];
            replacement = html.substring(
                i - (matched ? matched.length : 0), i + 1);
        }
        if (!leaf) {
            // newby-style inline (all hand work!) pay extra
            // attention, this code is duplicated few lines above
            if (match) {
                result.push(
                    html.substring(lastMatch, i - matched.length),
                    "<a href=\"", match, "\">", replacement, "</a>");
                lastMatch = i - matched.length + replacement.length;
                i = lastMatch - 1;
            } else {
                if (matched) {
                    // go back to the second character of the
                    // matched string and try again
                    i = i - matched.length;
                }
            }
            matched = match = replacement = "";
        } else if (matched) {
            // perhaps a bit premature, but we'll try to avoid
            // string concatenation, when we can.
            matched = html.substring(i - matched.length, i + 1);
        } else {
            matched = current;
        }
    }
    return result.join("");
}

function testPrefixReplace() {
    "use strict";
    var trie = buildTrie(
        { "x" : "www.xxx.com", "yyy" : "www.y.com",
          "xy" : "www.xy.com", "yy" : "www.why.com" });
    return prefixReplaceHtml(
        "<html><head>x</head><body><a >yyy</a><p>" +
            "xyyy yy x xy</p><abrval><yy>xxy</yy>", trie);
}

答案 3 :(得分:0)

你可以做任何事情,问题是:你值得花时间吗?

步骤1,抛弃AJAX要求。 Ajax用于与服务器交互,向服务器提交少量数据并获得响应。不适合你想要的东西。

步骤2,抛弃JS需求,JS与用户交互,你真的想要提供一个文本块,其中一些单词被链接替换,这应该在服务器端处理。

第3步,专注于php,如果效率不高,那就攻击它。找到提高效率的方法。你在PHP中尝试了什么?为什么效率不高?

答案 4 :(得分:0)

如果你有数据库访问消息和单词列表,我真的建议你用PHP做一切。虽然这可以在JS中完成,但作为服务器端脚本会更好。

在JS中,基本上,你必须

  • 加载消息
  • 加载“dictionnary”
  • 循环显示词典中的每个单词
    • 在DOM中寻找匹配(哎哟)
      • 替换

前两个点是请求,这会产生相当大的开销。循环将在客户端的CPU上征税。

为什么我建议将其作为服务器端代码:

  • 服务器更适合这些类型的工作
  • JS在客户端浏览器上运行。每个客户都是不同的(例如:有人可能会使用性能较差的IE,或者有人使用智能手机)

这在PHP中很容易实现..

<?php
    $dict[] = array('word' => 'dolor', 'link' => 'DOLORRRRRR');
    $dict[] = array('word' => 'nulla', 'link' => 'NULLAAAARRRR');

    //  Pretty sure there's a more efficient way to separate an array.. my PHP is rusty, sorry. 
    $terms = array();
    $replace = array();
    foreach ($dict as $v) {
        // If you want to make sure it's a complete word, add a space to the term. 
        $terms[] = ' ' . $v['word'] . ' ';
        $replace[] = ' '. $v['link'] . ' ';
    }

    $text = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";

    echo str_replace($terms, $replace, $text);


    /* Output: 
    Lorem ipsum DOLORRRRRR sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure DOLORRRRRR in reprehenderit in voluptate velit esse cillum dolore eu fugiat NULLAAAARRRR pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
    */

?>

虽然这个脚本非常基本 - 它不会接受不同的情况。

我会做什么:

如果PHP性能真的很难打击你(我怀疑它......),你可以替换它一次并保存它。然后,当您添加一个新单词时,删除缓存并重新生成它们(您可以编写一个cron来执行此操作)