【问题标题】:How to add rel="nofollow" to links with preg_replace()如何使用 preg_replace() 将 rel="nofollow" 添加到链接
【发布时间】:2011-06-29 14:02:07
【问题描述】:

下面的函数旨在将rel="nofollow" 属性应用到所有外部链接,而不是内部链接,除非路径与下面定义为$my_folder 的预定义根URL 匹配。

所以给定变量...

$my_folder = 'http://localhost/mytest/go/';
$blog_url = 'http://localhost/mytest';

还有内容……

<a href="http://localhost/mytest/">internal</a>

<a href="http://localhost/mytest/go/hostgator">internal cloaked link</a>

<a href="http://cnn.com">external</a>

替换后的结果应该是……

<a href="http://localhost/mytest/">internal</a>

<a href="http://localhost/mytest/go/hostgator" rel="nofollow">internal cloaked link</a>

<a href="http://cnn.com" rel="nofollow">external</a>

请注意,第一个链接没有改变,因为它是一个内部链接。

第二行的链接也是一个内部链接,但是因为它匹配我们的$my_folder字符串,所以它也得到了nofollow

第三个链接是最简单的,因为它不匹配blog_url,它显然是一个外部链接。

但是,在下面的脚本中,我的所有链接都获得了nofollow。如何修复脚本以执行我想要的操作?

function save_rseo_nofollow($content) {
$my_folder =  $rseo['nofollow_folder'];
$blog_url = get_bloginfo('url');
    preg_match_all('~<a.*>~isU',$content["post_content"],$matches);
    for ( $i = 0; $i <= sizeof($matches[0]); $i++){
        if ( !preg_match( '~nofollow~is',$matches[0][$i])
            && (preg_match('~' . $my_folder . '~', $matches[0][$i]) 
               || !preg_match( '~'.$blog_url.'~',$matches[0][$i]))){
            $result = trim($matches[0][$i],">");
            $result .= ' rel="nofollow">';
            $content["post_content"] = str_replace($matches[0][$i], $result, $content["post_content"]);
        }
    }
    return $content;
}

【问题讨论】:

  • 我认为 DOMDocument 用这个会更好。
  • @alex:别让我开始,哈哈。我敢肯定,但每次我尝试过时,我的代码都增加了 4 倍,而且它永远不会完全正确。至少我可以让 preg_match 工作,但它需要一些小的调整。但是,如果有人可以使用适用于 WordPress 内容编辑器的 post_content 对象的 DOMdocument 示例解决问题,我不反对再给 DOMdocument 一个机会。
  • 尝试 phpQuery 而不是繁琐的 DOMDocument。但在这一点上,也不应该忽视部署rel=nofollow 是毫无意义的。它对您或任何其他人的垃圾邮件问题没有帮助。这只是免费劳动力,因此 Google 的工作量更少。也不知道它对垃圾邮件机器人有威慑作用。
  • @Scott B 我发布了一个可行的 DOMDocument 解决方案。 :)
  • @mario 我同意 DOMDocument 很麻烦。我可能很快就会查看这个 phpQuery,感谢您的建议:)

标签: php regex preg-match


【解决方案1】:

这里是 DOMDocument 解决方案...

$str = '<a href="http://localhost/mytest/">internal</a>

<a href="http://localhost/mytest/go/hostgator">internal cloaked link</a>

<a href="http://cnn.com" rel="me">external</a>

<a href="http://google.com">external</a>

<a href="http://example.com" rel="nofollow">external</a>

<a href="http://stackoverflow.com" rel="junk in the rel">external</a>
';
$dom = new DOMDocument();

$dom->preserveWhitespace = FALSE;

$dom->loadHTML($str);

$a = $dom->getElementsByTagName('a');

$host = strtok($_SERVER['HTTP_HOST'], ':');

foreach($a as $anchor) {
        $href = $anchor->attributes->getNamedItem('href')->nodeValue;

        if (preg_match('/^https?:\/\/' . preg_quote($host, '/') . '/', $href)) {
           continue;
        }

        $noFollowRel = 'nofollow';
        $oldRelAtt = $anchor->attributes->getNamedItem('rel');

        if ($oldRelAtt == NULL) {
            $newRel = $noFollowRel;
        } else {
            $oldRel = $oldRelAtt->nodeValue;
            $oldRel = explode(' ', $oldRel);
            if (in_array($noFollowRel, $oldRel)) {
                continue;
            }
            $oldRel[] = $noFollowRel;
            $newRel = implode($oldRel,  ' ');
        }

        $newRelAtt = $dom->createAttribute('rel');
        $noFollowNode = $dom->createTextNode($newRel);
        $newRelAtt->appendChild($noFollowNode);
        $anchor->appendChild($newRelAtt);

}

var_dump($dom->saveHTML());

输出

string(509) "<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<a href="http://localhost/mytest/">internal</a>

<a href="http://localhost/mytest/go/hostgator">internal cloaked link</a>

<a href="http://cnn.com" rel="me nofollow">external</a>

<a href="http://google.com" rel="nofollow">external</a>

<a href="http://example.com" rel="nofollow">external</a>

<a href="http://stackoverflow.com" rel="junk in the rel nofollow">external</a>
</body></html>
"

【讨论】:

  • 甜蜜。现在检查一下。感谢您真正攻击这个问题。
【解决方案2】:

首先尝试使其更具可读性,然后才能使您的if 规则更复杂:

function save_rseo_nofollow($content) {
    $content["post_content"] =
    preg_replace_callback('~<(a\s[^>]+)>~isU', "cb2", $content["post_content"]);
    return $content;
}

function cb2($match) { 
    list($original, $tag) = $match;   // regex match groups

    $my_folder =  "/hostgator";       // re-add quirky config here
    $blog_url = "http://localhost/";

    if (strpos($tag, "nofollow")) {
        return $original;
    }
    elseif (strpos($tag, $blog_url) && (!$my_folder || !strpos($tag, $my_folder))) {
        return $original;
    }
    else {
        return "<$tag rel='nofollow'>";
    }
}

给出以下输出:

[post_content] =>
  <a href="http://localhost/mytest/">internal</a>
  <a href="http://localhost/mytest/go/hostgator" rel=nofollow>internal cloaked link</a>    
  <a href="http://cnn.com" rel=nofollow>external</a>

原始代码中的问题可能是 $rseo 没有在任何地方声明。

【讨论】:

  • @Mario:知道了。谢谢。您如何将 rel 属性封装在引号中? rel="nofollow" 进行验证?
  • @Scott:改用return "&lt;$tag rel=\"nofollow\"&gt;"; 或内部单引号。
  • @mario: arg $content 前面的 & 有什么作用?函数(&$内容)
  • @Scott:传递参考。但我只是注意到你需要一个return。见编辑。
  • @Scott:添加更多 if 块然后保持可读性。我的支持到此结束,我不会为你编写整个插件。
【解决方案3】:

试试这个(PHP 5.3+):

  • 跳过所选地址
  • 允许手动设置rel参数

和代码:

function nofollow($html, $skip = null) {
    return preg_replace_callback(
        "#(<a[^>]+?)>#is", function ($mach) use ($skip) {
            return (
                !($skip && strpos($mach[1], $skip) !== false) &&
                strpos($mach[1], 'rel=') === false
            ) ? $mach[1] . ' rel="nofollow">' : $mach[0];
        },
        $html
    );
}

例子:

echo nofollow('<a href="link somewhere" rel="something">something</a>');
// will be same because it's already contains rel parameter

echo nofollow('<a href="http://www.cnn.com">something</a>'); // ad
// add rel="nofollow" parameter to anchor

echo nofollow('<a href="http://localhost">something</a>', 'localhost');
// skip this link as internall link

【讨论】:

  • 那么可以做些什么来让它替换现有的 rel?
  • 感谢您的解决方案。如果我想传递一系列跳过的域列表怎么办?
【解决方案4】:

使用正则表达式正确地完成这项工作会相当复杂。使用实际的解析器会更容易,例如来自DOM extension 的解析器。 DOM 对初学者不太友好,因此您可以使用 DOM 加载 HTML,然后使用 SimpleXML 运行修改。它们由同一个库提供支持,因此很容易将一个库与另一个库一起使用。

它的外观如下:

$my_folder = 'http://localhost/mytest/go/';
$blog_url = 'http://localhost/mytest';

$html = '<html><body>
<a href="http://localhost/mytest/">internal</a>
<a href="http://localhost/mytest/go/hostgator">internal cloaked link</a>
<a href="http://cnn.com">external</a>
</body></html>';

$dom = new DOMDocument;
$dom->loadHTML($html);

$sxe = simplexml_import_dom($dom);

// grab all <a> nodes with an href attribute
foreach ($sxe->xpath('//a[@href]') as $a)
{
    if (substr($a['href'], 0, strlen($blog_url)) === $blog_url
     && substr($a['href'], 0, strlen($my_folder)) !== $my_folder)
    {
        // skip all links that start with the URL in $blog_url, as long as they
        // don't start with the URL from $my_folder;
        continue;
    }

    if (empty($a['rel']))
    {
        $a['rel'] = 'nofollow';
    }
    else
    {
        $a['rel'] .= ' nofollow';
    }
}

$new_html = $dom->saveHTML();
echo $new_html;

如您所见,它非常简短。根据您的需要,您可能希望使用 preg_match() 代替 strpos() 的东西,例如:

    // change the regexp to your own rules, here we match everything under
    // "http://localhost/mytest/" as long as it's not followed by "go"
    if (preg_match('#^http://localhost/mytest/(?!go)#', $a['href']))
    {
        continue;
    }

注意

当我第一次阅读问题时,我错过了 OP 中的最后一个代码块。我发布的代码(基本上是任何基于 DOM 的解决方案)更适合处理整个页面而不是 HTML 块。否则,DOM 将尝试“修复”您的 HTML,并可能添加 &lt;body&gt; 标签、DOCTYPE 等...

【讨论】:

  • 嗨,我尝试使用您的代码,但它仍然在博客网址中添加 nofollow。有什么帮助吗?
  • 这段代码对我有帮助,但是当$html 字符串包含utf-8 字符(例如弯引号)时,我确实遇到了编码问题。用$dom-&gt;loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); 替换$dom-&gt;loadHTML($html); 解决了这个问题。修复来源:PHP DOMDocument loadHTML not encoding UTF-8 correctly
【解决方案5】:

感谢@alex 提供的出色解决方案。但是,我遇到了日语文本的问题。我已将其修复为以下方式。此外,此代码可以使用 $whiteList 数组跳过多个域。

public function addRelNoFollow($html, $whiteList = [])
{
    $dom = new \DOMDocument();
    $dom->preserveWhiteSpace = false;
    $dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
    $a = $dom->getElementsByTagName('a');

    /** @var \DOMElement $anchor */
    foreach ($a as $anchor) {
        $href = $anchor->attributes->getNamedItem('href')->nodeValue;
        $domain = parse_url($href, PHP_URL_HOST);

        // Skip whiteList domains
        if (in_array($domain, $whiteList, true)) {
            continue;
        }

        // Check & get existing rel attribute values
        $noFollow = 'nofollow';
        $rel = $anchor->attributes->getNamedItem('rel');
        if ($rel) {
            $values = explode(' ', $rel->nodeValue);
            if (in_array($noFollow, $values, true)) {
                continue;
            }
            $values[] = $noFollow;
            $newValue = implode($values, ' ');
        } else {
            $newValue = $noFollow;
        }

        // Create new rel attribute
        $rel = $dom->createAttribute('rel');
        $node = $dom->createTextNode($newValue);
        $rel->appendChild($node);
        $anchor->appendChild($rel);
    }

    // There is a problem with saveHTML() and saveXML(), both of them do not work correctly in Unix.
    // They do not save UTF-8 characters correctly when used in Unix, but they work in Windows.
    // So we need to do as follows. @see https://stackoverflow.com/a/20675396/1710782
    return $dom->saveHTML($dom->documentElement);
}

【讨论】:

    【解决方案6】:
    <?
    
    $str='<a href="http://localhost/mytest/">internal</a>
    <a href="http://localhost/mytest/go/hostgator">internal cloaked link</a>
    <a href="http://cnn.com">external</a>';
    
    function test($x){
      if (preg_match('@localhost/mytest/(?!go/)@i',$x[0])>0) return $x[0];
      return 'rel="nofollow" '.$x[0];
    }
    
    echo preg_replace_callback('/href=[\'"][^\'"]+/i', 'test', $str);
    
    ?>
    

    【讨论】:

      【解决方案7】:

      这是另一个具有白名单选项并添加 tagret 空白属性的解决方案。 并且在添加新属性之前检查是否已经存在 rel 属性。

      function Add_Nofollow_Attr($Content, $Whitelist = [], $Add_Target_Blank = true) 
      {
          $Whitelist[] = $_SERVER['HTTP_HOST'];
          foreach ($Whitelist as $Key => $Link) 
          {
              $Host = preg_replace('#^https?://#', '', $Link);
              $Host = "https?://". preg_quote($Host, '/');
              $Whitelist[$Key] = $Host;
          }
      
          if(preg_match_all("/<a .*?>/", $Content, $matches, PREG_SET_ORDER)) 
          {
              foreach ($matches as $Anchor_Tag) 
              {
                  $IS_Rel_Exist = $IS_Follow_Exist = $IS_Target_Blank_Exist = $Is_Valid_Tag =  false;
                  if(preg_match_all("/(\w+)\s*=\s*['|\"](.*?)['|\"]/",$Anchor_Tag[0],$All_matches2)) 
                  {
                      foreach ($All_matches2[1] as $Key => $Attr_Name)
                      {
                          if($Attr_Name == 'href')
                          {
                              $Is_Valid_Tag = true;
                              $Url = $All_matches2[2][$Key];
                              // bypass #.. or internal links like "/"
                              if(preg_match('/^\s*[#|\/].*/', $Url)) 
                              {
                                  continue 2;
                              }
      
                              foreach ($Whitelist as $Link) 
                              {
                                  if (preg_match("#$Link#", $Url)) {
                                      continue 3;
                                  }
                              }
                          }
                          else if($Attr_Name == 'rel')
                          {
                              $IS_Rel_Exist = true;
                              $Rel = $All_matches2[2][$Key];
                              preg_match("/[n|d]ofollow/", $Rel, $match, PREG_OFFSET_CAPTURE);
                              if( count($match) > 0 )
                              {
                                  $IS_Follow_Exist = true;
                              }
                              else
                              {
                                  $New_Rel = 'rel="'. $Rel . ' nofollow"';
                              }
                          }
                          else if($Attr_Name == 'target')
                          {
                              $IS_Target_Blank_Exist = true;
                          }
                      }
                  }
      
                  $New_Anchor_Tag = $Anchor_Tag;
                  if(!$IS_Rel_Exist)
                  {
                      $New_Anchor_Tag = str_replace(">",' rel="nofollow">',$Anchor_Tag);
                  }
                  else if(!$IS_Follow_Exist)
                  {
                      $New_Anchor_Tag = preg_replace("/rel=[\"|'].*?[\"|']/",$New_Rel,$Anchor_Tag);
                  }
      
                  if($Add_Target_Blank && !$IS_Target_Blank_Exist)
                  {
                      $New_Anchor_Tag = str_replace(">",' target="_blank">',$New_Anchor_Tag);
                  }
      
                  $Content = str_replace($Anchor_Tag,$New_Anchor_Tag,$Content);
              }
          }
          return $Content;
      }
      

      使用它:

      $Page_Content = '<a href="http://localhost/">internal</a>
                       <a href="http://yoursite.com">internal</a>
                       <a href="http://google.com">google</a>
                       <a href="http://example.com" rel="nofollow">example</a>
                       <a href="http://stackoverflow.com" rel="random">stackoverflow</a>';
      
      $Whitelist = ["http://yoursite.com","http://localhost"];
      
      echo Add_Nofollow_Attr($Page_Content,$Whitelist,true);
      

      【讨论】:

        【解决方案8】:

        WordPress 决定:

        function replace__method($match) {
            list($original, $tag) = $match;   // regex match groups
        
            $my_folder =  "/articles";       // re-add quirky config here
            $blog_url = 'https://'.$_SERVER['SERVER_NAME'];
        
            if (strpos($tag, "nofollow")) {
                return $original;
            }
            elseif (strpos($tag, $blog_url) && (!$my_folder || !strpos($tag, $my_folder))) {
                return $original;
            }
            else {
                return "<$tag rel='nofollow'>";
            }
        }
        
        add_filter( 'the_content', 'add_nofollow_to_external_links', 1 );
        
        function add_nofollow_to_external_links( $content ) {
            $content = preg_replace_callback('~<(a\s[^>]+)>~isU', "replace__method", $content);
            return $content;
        }
        

        【讨论】:

        • 一段没有任何解释的代码是没有用的。
        【解决方案9】:

        一个允许自动添加nofollow并保留其他属性的好脚本

        function nofollow(string $html, string $baseUrl = null) {
            return preg_replace_callback(
                    '#<a([^>]*)>(.+)</a>#isU', function ($mach) use ($baseUrl) {
                        list ($a, $attr, $text) = $mach;
                        if (preg_match('#href=["\']([^"\']*)["\']#', $attr, $url)) {
                            $url = $url[1];
                            if (is_null($baseUrl) || !str_starts_with($url, $baseUrl)) {
                                if (preg_match('#rel=["\']([^"\']*)["\']#', $attr, $rel)) {
                                    $relAttr = $rel[0];
                                    $rel = $rel[1];
                                }
                                $rel = 'rel="' . ($rel ? (strpos($rel, 'nofollow') ? $rel : $rel . ' nofollow') : 'nofollow') . '"';
                                $attr = isset($relAttr) ? str_replace($relAttr, $rel, $attr) : $attr . ' ' . $rel;
                                $a = '<a ' . $attr . '>' . $text . '</a>';
                            }
                        }
                        return $a;
                    },
                    $html
            );
        }
        

        【讨论】:

        • 请不要发布两次相同的答案。另一个问题是重复。
        • @Toto 我更正了帖子,所以请删除声誉!
        • @Toto Stackooverflow 我被屏蔽了
        猜你喜欢
        • 2012-11-30
        • 2011-03-23
        • 2013-10-29
        • 2014-12-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多