百度空间 | 百度首页 
 
查看文章
 
用php替换禁止外链图片地址时使用preg_match的限制
2009-03-18 13:47
对于聚合类网站来说,在显示内容中图片的时候,需要解决一个问题:有的网站对图片采取了防盗链的措施,只有当请求头的referer来自指定的host时才会正确显示图片,比如,百度空间的,163空间的。那么,理所当然的,在显示来自这些网站的内容时,需要对图片标签进行一些预处理,使得相应的图片能正确显示出来。

一般的处理方式,都是利用正则表达式来找到img标签,并对src中的url进行检测,如果是来自这些禁止外链的网站过来的图片,则对src进行一些替换,使得其能正确的显示。比如一个src="http://hi.baidu.com/aaa.jpg",替换成src="http://mydomain.com/downloadPicture.php?http://hi.baidu.com/aaa.jpg",这样的话,图片的显示就交给http://mydomain.com/downloadPicture.php来处理了。downloadPicture.php先检测对应http://hi.baidu.com/aaa.jpg的图片是否已经下载过,下载过则直接将其显示,否则,通过带有referer=baidu.com等header值的请求下载到正确的图片,然后再将其显示出来。

之前我们进行图片地址替换时,用到了以下类似的代码:
$READPICURL = '$1http://mydomain.com/downloadPicture.php?$2';
$RegReplaceUrl = '/(<img\s.*?src\s*=\s*[\'"])(.*?(baidu\.com|sina\.com\.cn|qq\.com|sohu\.com|sohu\.com\.cn|tianya\.cn|blog\.163\.com|bababian\.com)\/[^"\']+)/i';

$newDesc = preg_replace($RegReplaceUrl,$READPICURL,$desc);
if ($newDesc){
$desc = $newDesc;
}

这段代码一直工作得很正常。最近,随着禁止外链的网站越来越多,我们给$RegReplaceUrl变量又增加了一些domain进去,一切貌似平静。不过,接下来的几天,就陆续收到了一些用户的反馈,说是有一些网站的图片怎么显示不出来了。一看,还果然如此,原来工作得正常的图片替换代码,貌似都不起作用了。

找了一段文章的内容下来,进行测试,发现代码还真不正常了,$newDesc得到的值总是为null。查了一下官方的文档:http://cn.php.net/preg_replace ,发现在评论中有这么一段:
It may not be obvious to everybody that the function returns NULL if an error of any kind occurres. An error I happen to stumple about quite often was the back-tracking-limit:
http://de.php.net/manual/de/pcre.configuration.php
#ini.pcre.backtrack-limit

那么,这段代码出错的原因到底是不需要调整php.ini中正则表达式的相关配置呢?google一阵后,知道原来还有一个grep_last_error()函数,可以直接显示使用preg_match时的出错码。于是在后面用grep_last_error()输出一下错误码,发现为2,再对照一下错误码:

  • PREG_NO_ERROR
  • PREG_INTERNAL_ERROR
  • PREG_BACKTRACK_LIMIT_ERROR (siehe auch pcre.backtrack_limit)
  • PREG_RECURSION_LIMIT_ERROR (siehe auch pcre.recursion_limit)
  • PREG_BAD_UTF8_ERROR
  • PREG_BAD_UTF8_OFFSET_ERROR (ab PHP 5.3.0)
知道是PREG_BACKTRACK_LIMIT_ERROR 了,看来还真是需要修改php.ini中有关pcre的配置项了。用phpinfo()显示一下配置项,结果如下:

PCRE
PCRE (Perl Compatible Regular Expressions) Support enabled
PCRE Library Version 7.6 2008-01-28

Directive Local Value Master Value
pcre.backtrack_limit 100000 100000
pcre.recursion_limit 100000 100000

于是尝试性的在php.ini中调整pcre.backtrack_limit为120000,代码还真就正常工作了。

不过,这样的更改在我看来,效果还不好,毕竟以后禁止外链的网站会越来越多,那么也就意味着,我们需要随时调整php.ini中的pcre.backtrack_limit值,或者是把pcre.backtrack_limit值设置得很大。经常性地调整显然不是我们想要的,而把pcre.backtrack_limit设置得很大显然对于内存的开销有更大的要求,也更容易带进一些不稳定的因素,影响代码的工作效率。

那么,就需要从代码本身来考虑,看看怎么着能使backtrack的次数降低。

首先,肯定是要降低正则表达式的复杂程度,减少backtrack的次数。所以,一开始只让正则表达式捕获img的所有标签,然后,再通过正则表达式来检测img标签的src是否有禁止外链的host,从而决定是否替换src。这样做,虽然利用正则表达式的次数由原来的一次增加到1+图片数量,但是,每次要捕获的规则都要降低很多,而且每次调用正则表达式的backtrack次数都是有限的,不会随着其他外在的因素(比如host的增加,内容的增加)而增加得特别明显。

最后的代码如下:
$RegReplaceUrl = '/(<img\s.*?src\s*=\s*[\'"])([^"\']+)/i';
$newDesc = preg_replace_callback($RegReplaceUrl,'replace',$desc);

function replace($matches) {
    $pattern = "(baidu\.com|sina\.com\.cn|qq\.com|sohu\.com|sohu\.com\.cn|tianya\.cn|blog\.163\.com|bababian\.com|yeeyan\.com|meijumi\.com)";
    if (preg_match($pattern, $matches[2])) {
        return $matches[1]."http://mydomain.com/downloadPicture.php?".$matches[2];
    }
    return $matches[0];
}

最后,总结一下,在运用php的正则表达式时,需要注意以下几点:
1、如果代码运行不正常,可以用grep_last_error()函数输出错误码,即可基本了解出错的原因。
2、如果错误码为2(PREG_BACKTRACK_LIMIT_ERROR)或3(PREG_RECURSION_LIMIT_ERROR),简单地话可以通过调整php.ini中pcre.backtrack_limit、pcre.recursion_limit值来大小来解决。
3、对于2中的错误,也可以通过增加正则表达式匹配次数降低表达式的复杂程度,从而减小每次使用表达式是的backtrack(回溯)次数或者recursion(递归)次数。

类别:技术追峰 | 添加到搜藏 | 浏览() | 评论 (1)
 
最近读者:
 
网友评论:
3
2009-09-14 23:20 | 回复
这个要顶,百度的速度还是很赞的。
 
发表评论:
姓 名:
网址或邮箱: (选填)
内 容:
验证码: 请点击后输入四位验证码,字母不区分大小写
      

     

©2009 Baidu