js333 > 计算机互联网 > php中preg_match_all函数用法详解

原标题:php中preg_match_all函数用法详解

浏览次数:93 时间:2019-10-02

Apache/2.2.9 (Win32) + PHP/5.2.17,在使用正则表达式 preg_match_all (如 preg_match_all("/ni(.*?)wo/", $html, $matches);)进行分析匹配比较长的字符串 $html 时(大于10万字节,一般用于分析采集回来的网页源码),Apache服务器会崩溃自动重启。

平台是windows server 2003(32位系统) + Apache/2.2.9 (Win32) + PHP/5.2.17,在使用正则表达式 preg_match_all (如 preg_match_all("/ni(.*?)wo/", $html, $matches);)进行分析匹配比较长的字符串 $html 时(大于10万字节,一般用于分析采集回来的网页源码),Apache服务器会崩溃自动重启。

int preg_match_all ( string pattern, string subject, array matches [, int flags])

根据前两篇的博文:

在Apache错误日志里有这样的提示:

在Apache错误日志里有这样的提示:

在 subject 中搜索所有与 pattern 给出的正则表达式匹配的内容并将结果以 flags 指定的顺序放到 matches 中。

基于curl数据采集之单页面采集函数get_html的使用

[Thu Apr 11 18:31:31 2013] [notice] Parent: child process exited with status 128 -- Restarting.
[Thu Apr 11 18:31:31 2013] [notice] Apache/2.2.9 (Win32) PHP/5.2.17 configured -- resuming normal operations
[Thu Apr 11 18:31:31 2013] [notice] Server built: Jun 13 2008 04:04:59
[Thu Apr 11 18:31:31 2013] [notice] Parent: Created child process 2964
[Thu Apr 11 18:31:31 2013] [notice] Disabled use of AcceptEx() WinSock2 API
[Thu Apr 11 18:31:31 2013] [notice] Child 2964: Child process is running
[Thu Apr 11 18:31:31 2013] [notice] Child 2964: Acquired the start mutex.
[Thu Apr 11 18:31:31 2013] [notice] Child 2964: Starting 350 worker threads.
[Thu Apr 11 18:31:31 2013] [notice] Child 2964: Listening on port 80.

[Thu Apr 11 18:31:31 2013] [notice] Parent: child process exited with status 128 -- Restarting.
[Thu Apr 11 18:31:31 2013] [notice] Apache/2.2.9 (Win32) PHP/5.2.17 configured -- resuming normal operations
[Thu Apr 11 18:31:31 2013] [notice] Server built: Jun 13 2008 04:04:59
[Thu Apr 11 18:31:31 2013] [notice] Parent: Created child process 2964
[Thu Apr 11 18:31:31 2013] [notice] Disabled use of AcceptEx() WinSock2 API
[Thu Apr 11 18:31:31 2013] [notice] Child 2964: Child process is running
[Thu Apr 11 18:31:31 2013] [notice] Child 2964: Acquired the start mutex.
[Thu Apr 11 18:31:31 2013] [notice] Child 2964: Starting 350 worker threads.
[Thu Apr 11 18:31:31 2013] [notice] Child 2964: Listening on port

搜索到第一个匹配项之后,接下来的搜索从上一个匹配项末尾开始。

基于curl数据采集之单页面并行采集函数get_htmls的使用

经过查阅Apache官方以及论坛资料后,发现win平台下用正则 preg_match_all 或preg_match 分析比较长的字符串时,导致apache崩溃重启的原因是windows平台下默认分配的线程堆栈空间 ThreadStackSize 太小导致的。 win32默认只有256KB,而在 linux下默认值是 8M,这就是为什么同样的程序在 linux平台下正常,而在 win平台下不正常的原因。

flags 可以是下列标记的组合(注意把 PREG_PATTERN_ORDER 和 PREG_SET_ORDER 合起来用没有意义):

已经可以得到了我们需要的html文件,现在需要处理得到的文件获取到我们需要的采集的数据。

根据PCRE library的官方说明:256 KB 的堆栈空间对应的pcre.recursion_limit大小应该不超过524。
Here is a table of safe values of pcre.recursion_limit for a variety of executable stack sizes:
下面就是一张Stacksize和pcre.recursion_limit对应的建议安全值,超过这个数值就极有可能发生堆栈溢出,apache crash:

经过查阅Apache官方以及论坛资料后,发现win平台下用正则 preg_match_all 或preg_match 分析比较长的字符串时,导致apache崩溃重启的原因是windows平台下默认分配的线程堆栈空间 ThreadStackSize 太小导致的。 win32默认只有256KB,而在 linux下默认值是 8M,这就是为什么同样的程序在 linux平台下正常,而在 win平台下不正常的原因。

PREG_PATTERN_ORDER

对于html文档的解析,没有像XML那样的解析类,因为HTML文档有很多不成对的标签,很不严格。这个时候就需要采用其他的一些辅助类了,simplehtmldom是一个类似于JQuery方式操作HTML文档的解析类。可以很方便的得到想要的数据,可惜速度慢。这里不是我们这里讨论的重点,我主要使用正则来匹配我所需要的采集的数据,可以很快速的得到我需要采集的信息。

Stacksize   pcre.recursion_limit
 64 MB      134217
 32 MB      67108
 16 MB      33554
  8 MB      16777
  4 MB      8388
  2 MB      4194
  1 MB      2097
512 KB      1048
256 KB      524
如果你没有调整堆栈大小,就必须在使用正则的PHP页面最开头加入:

根据PCRE library的官方说明:256 KB 的堆栈空间对应的pcre.recursion_limit大小应该不超过524。
Here is a table of safe values of pcre.recursion_limit for a variety of executable stack sizes:
下面就是一张Stacksize和pcre.recursion_limit对应的建议安全值,超过这个数值就极有可能发生堆栈溢出,apache crash:
Stacksize   pcre.recursion_limit
 64 MB      134217
 32 MB      67108
 16 MB      33554
  8 MB      16777
  4 MB      8388
  2 MB      4194
  1 MB      2097
512 KB      1048
256 KB      524

对结果排序使 $matches[0] 为全部模式匹配的数组,$matches[1] 为第一个括号中的子模式所匹配的字符串组成的数组,以此类推。

考虑到get_html可以判断返回的数据,但是get_htmls没有办法判断,为了方便调式和调用写了如下两个函数:

<?php
ini_set("pcre.recursion_limit", "524"); // PHP default is 100,000.
?>

如果你没有调整堆栈大小,就必须在使用正则的PHP页面最开头加入:

 代码如下

复制代码 代码如下:

查看具体的错误可以使用下面的代码:

<?php
ini_set("pcre.recursion_limit", "524"); // PHP default is 100,000.
?>

<?php
preg_match_all ("|<[^>]+>(.*)</[^>]+>|U",
    "<b>example: </b><div align=left>this is a test</div>",
    $out, PREG_PATTERN_ORDER);
print $out[0][0].", ".$out[0][1]."/n";
print $out[1][0].", ".$out[1][1]."/n";
?>

function get_matches($pattern,$html,$err_msg,$multi=false,$flags=0,$offset=0){
     if(!$multi){
         if(!preg_match($pattern,$html,$matches,$flags,$offset)){
             echo $err_msg."! 错误信息: ".get_preg_err_msg()."n";
             return false;
         }
     }else{
         if(!preg_match_all($pattern,$html,$matches,$flags,$offset)){
             echo $err_msg."! 错误信息: ".get_preg_err_msg()."n";
             return false;
         }
     }
     return $matches;
 }
 function get_preg_err_msg(){
     $error_code = preg_last_error();
     switch($error_code){
         case PREG_NO_ERROR :
             $err_msg = 'PREG_NO_ERROR';
             break;
         case PREG_INTERNAL_ERROR:
             $err_msg = 'PREG_INTERNAL_ERROR';
             break;
         case PREG_BACKTRACK_LIMIT_ERROR:
             $err_msg = 'PREG_BACKTRACK_LIMIT_ERROR';
             break;
         case PREG_RECURSION_LIMIT_ERROR:
             $err_msg = 'PREG_RECURSION_LIMIT_ERROR';
             break;
         case PREG_BAD_UTF8_ERROR:
             $err_msg = 'PREG_BAD_UTF8_ERROR';
             break;
         case PREG_BAD_UTF8_OFFSET_ERROR:
             $err_msg = 'PREG_BAD_UTF8_OFFSET_ERROR';
             break;
         default:
             return '未知错误!';
     }
     return $err_msg.': '.$error_code;
 }

 代码如下

查看具体的错误可以使用下面的代码:

本例将输出:

可以这样调用:

$resultsArray = preg_match_all("/table.*?<a>/isU", $html, $contents);
if ($resultsArray === 0){
echo get_pcre_err();
}
function get_pcre_err(){
        $pcre_err = preg_last_error();  // PHP 5.2 and above.
        if ($pcre_err === PREG_NO_ERROR) {
            $msg = 'Successful non-match.';
        } else {
            // preg_match error!
            switch ($pcre_err) {
                case PREG_INTERNAL_ERROR:
                    $msg = 'PREG_INTERNAL_ERROR';
                    break;
                case PREG_BACKTRACK_LIMIT_ERROR:
                    $msg = 'PREG_BACKTRACK_LIMIT_ERROR';
                    break;
                case PREG_RECURSION_LIMIT_ERROR:
                    $msg = 'PREG_RECURSION_LIMIT_ERROR';
                    break;
                case PREG_BAD_UTF8_ERROR:
                    $msg = 'PREG_BAD_UTF8_ERROR';
                    break;
                case PREG_BAD_UTF8_OFFSET_ERROR:
                    $msg = 'PREG_BAD_UTF8_OFFSET_ERROR';
                    break;
                default:
                    $msg = 'Unrecognized PREG error';
                    break;
            }
        }
    return($msg);
}

$resultsArray = preg_match_all("/table.*?<a>/isU", $html, $contents);
if ($resultsArray === 0){
echo get_pcre_err();
}
function get_pcre_err(){
        $pcre_err = preg_last_error();  // PHP 5.2 and above.
        if ($pcre_err === PREG_NO_ERROR) {
            $msg = 'Successful non-match.';
        } else {
            // preg_match error!
            switch ($pcre_err) {
                case PREG_INTERNAL_ERROR:
                    $msg = 'PREG_INTERNAL_ERROR';
                    break;
                case PREG_BACKTRACK_LIMIT_ERROR:
                    $msg = 'PREG_BACKTRACK_LIMIT_ERROR';
                    break;
                case PREG_RECURSION_LIMIT_ERROR:
                    $msg = 'PREG_RECURSION_LIMIT_ERROR';
                    break;
                case PREG_BAD_UTF8_ERROR:
                    $msg = 'PREG_BAD_UTF8_ERROR';
                    break;
                case PREG_BAD_UTF8_OFFSET_ERROR:
                    $msg = 'PREG_BAD_UTF8_OFFSET_ERROR';
                    break;
                default:
                    $msg = 'Unrecognized PREG error';
                    break;
            }
        }
    return($msg);
}

<b>example: </b>, this is a test example: , this is a test

复制代码 代码如下:

    对于正则的修饰符 isU 说明:
i: 表示in-casesensitive,即大小写不敏感
s: PCRE_DOTALL,表示点号可以匹配换行符。
U: 表示PCRE_UNGREEDY,表示非贪婪,相当于perl/python语言的.*?,在匹配过程中,对于.*正则,一有匹配立即执行,而不是等.*搜索了所有字符再一一返回
    在使用正则表达式时,我们应该尽量避免递归调用,递归容易导致堆栈溢出。比如:
/<table((?!<table).)*?</a>/isU 就会发生错误,而使用 /<table.*?</a>/i 就正常。

对于正则的修饰符 isU 说明:

因此,$out[0] 包含匹配整个模式的字符串,$out[1] 包含一对 HTML 标记之间的字符串

$url = '';
 $html = get_html($url);
 $matches = get_matches('!<a[^<]+</a>!',$html,'没有找到链接',true);
 if($matches){
     var_dump($matches);
 }

 那么如何增加win平台下 ThreadStackSize 的大小呢? 在apache的配置文件 httpd.conf 里启用 “Include conf/extra/httpd-mpm.conf”(删除前面的注释#),然后在 httpd-mpm.conf 文件里的 mpm_winnt_module 配置模块里设置 “ThreadStackSize 8400000”即可(大约8M)。

i: 表示in-casesensitive,即大小写不敏感
s: PCRE_DOTALL,表示点号可以匹配换行符。
U: 表示PCRE_UNGREEDY,表示非贪婪,相当于perl/python语言的.*?,在匹配过程中,对于.*正则,一有匹配立即执行,而不是等.*搜索了所有字符再一一返回

对结果排序使 $matches[0] 为全部模式匹配的数组,$matches[1] 为第一个括号中的子模式所匹配的字符串组成的数组,以此类推。(即$matches[0] [0]为全部模式匹配中的每一项,$matches[0] [1]为全部模式匹配中的第二项,$matches[1] [0]为匹配每一个括号中的第一项,$matches[1] [0]为匹配每一个括号中的第二项)

或者这样调用:

 代码如下

在使用正则表达式时,我们应该尽量避免递归调用,递归容易导致堆栈溢出。比如:

 代码如下

复制代码 代码如下:

<IfModule mpm_winnt_module>
    ThreadStackSize 8400000
    ThreadsPerChild      200
    MaxRequestsPerChild    10000
    Win32DisableAcceptEx
</IfModule>

/<table((?!<table).)*?</a>/isU 就会发生错误,而使用 /<table.*?</a>/i 就正常。

<?php

$urls = array('');
 $htmls = get_htmls($urls);
 foreach($htmls as $html){
     $matches = get_matches('!<a[^<]+</a>!',$html,'没有找到链接',true);
     if($matches){
         var_dump($matches);
     }
 }

这里需要注意的是,32位的Apache程序只能最多使用大约2GB内存空间! 因此,ThreadStackSize 和ThreadsPerChild 的值相乘后(8M * 200)不应该超过2G,否则无法启动apache,出现的错误日志如下:
[Thu Apr 11 20:02:45 2013] [crit] (OS 8)存储空间不足,无法处理此命令。  : Child 4832: _beginthreadex failed. Unable to create all worker threads. Created 212 of the 220 threads requested with the ThreadsPerChild configuration directive.
    通过上面的提示,飘易可以告诉大家的是在我的这台服务器上,当线程堆栈大小设为8M时,我可以设置的线程数最多是212个。

那么如何增加win平台下 ThreadStackSize 的大小呢? 在apache的配置文件 httpd.conf 里启用 “Include conf/extra/httpd-mpm.conf”(删除前面的注释#),然后在 httpd-mpm.conf 文件里的 mpm_winnt_module 配置模块里设置 “ThreadStackSize 8400000”即可(大约8M)。

 
  preg_match_all ("|<[^>]+>(.*)</[^>]+>|U",

就可以得到所需的信息,无论单页面采集还是多页面采集,最终PHP还是只能处理一个页面,由于使用get_matches了,可以对返回的值进行判断真假,得到正确的数据,由于使用正则的时候遇到了超过正则回溯的问题,增加get_preg_err_msg来提示正则信息。

<IfModule mpm_winnt_module>
    ThreadStackSize 8400000
    ThreadsPerChild      200
    MaxRequestsPerChild    10000
    Win32DisableAcceptEx
</IfModule>

 
  "<b>example: </b><div align=left>this is a test</div>",

由于采集数据的时候,经常是采集列表页,根据列表页得到的内容页链接再采集内容页,或者更多的层次,那么循环嵌套会很多,对于代码的控制会感觉力不从心。那我们是否可以把采集列表页的代码和采集内容页的代码,或者更多的层次的采集代码分离开,甚至循环都简化呢?

这里需要注意的是,32位的Apache程序只能最多使用大约2GB内存空间! 因此,ThreadStackSize 和ThreadsPerChild 的值相乘后(8M * 200)不应该超过2G,否则无法启动apache,出现的错误日志如下:

 
  $out, PREG_PATTERN_ORDER);

您可能感兴趣的文章:

  • php使用curl抓取qq空间的访客信息示例
  • php file_get_contents抓取Gzip网页乱码的三种解决方法
  • 使用PHP curl模拟浏览器抓取网站信息
  • 一个PHP的远程图片抓取函数分享
  • php抓取页面的几种方法详解
  • PHP CURL模拟登录新浪微博抓取页面内容 基于EaglePHP框架开发
  • php下通过curl抓取yahoo boss 搜索结果的实现代码
  • php curl 登录163邮箱并抓取邮箱好友列表的代码(经测试)
  • php使用curl和正则表达式抓取网页数据示例

[Thu Apr 11 20:02:45 2013] [crit] (OS 8)存储空间不足,无法处理此命令。  : Child 4832: _beginthreadex failed. Unable to create all worker threads. Created 212 of the 220 threads requested with the ThreadsPerChild configuration directive.

 
  print $out[0][0].", ".$out[0][1]."n";

通过上面的提示,飘易可以告诉大家的是在我的这台服务器上,当线程堆栈大小设为8M时,我可以设置的线程数最多是212个。

 
  print $out[1][0].", ".$out[1][1]."n";

 
  ?>

 

本例将输出:

 
  <b>example: </b>, <div align=left>this is a test</div>

 
  example: , this is a test

 
因此,$out[0] 包含匹配整个模式的字符串,$out[1] 包含一对 HTML 标记之间的字符串。

 
如果使用PREG_SET_ORDER

对结果排序使 $matches[0] 为第一组匹配项的数组,$matches[1] 为第二组匹配项的数组,以此类推。(即$matches[0] [0]为第一组匹配项中完整匹配的字符串,$matches[0] [1]为第一组匹配中完整匹配第一个括号中的字符串)

 代码如下

<?php

 
  preg_match_all ("|<[^>]+>(.*)</[^>]+>|U",

 
  "<b>example: </b><div align=left>this is a test</div>",

 
  $out, PREG_SET_ORDER);

 
  print $out[0][0].", ".$out[0][1]."n";

 
  print $out[1][0].", ".$out[1][1]."n";

 
  ?>

 

本例将输出:

 
  <b>example: </b>, example:

 
  <div align=left>this is a test</div>, this is a test

 
  本例中,$matches[0] 是第一组匹配结果,$matches[0][0] 包含匹配整个模式的文本,$matches[0][1] 包含匹配第一个子模式的文本,以此类推。同样,$matches[1] 是第二组匹配结果,等等。

 
PREG_OFFSET_CAPTURE

 
  如果设定本标记,对每个出现的匹配结果也同时返回其附属的字符串偏移量。注意这改变了返回的数组的值,使其中的每个单元也是一个数组,其中第一项为匹配字符串,第二项为其在 subject 中的偏移量。本标记自 PHP 4.3.0 起可用。

 
  如果没有给出标记,则假定为 PREG_PATTERN_ORDER。

 
  返回整个模式匹配的次数(可能为零),如果出错返回 FALSE。

 
例子 1. 从某文本中取得所有的电话号码

 代码如下

 
  <?php

 
  preg_match_all ("/(? (d)? )? (?(1) [-s] ) d-d/x",

 
  "Call 555-1212 or 1-800-555-1212", $phones);

 
  ?>

例子 2. 搜索匹配的 HTML 标记(greedy)

 代码如下

 
  <?php

 
  // 2 是一个逆向引用的例子,其在 PCRE 中的含义是

 
  // 必须匹配正则表达式本身中第二组括号内的内容,本例中

 
  // 就是 ([w]+)。因为字符串在双引号中,所以需要

 
  // 多加一个反斜线。

 
  $html = "<b>bold text</b><a href=howdy.html>click me</a>";

 
  preg_match_all ("/(<([w]+)[^>]*>)(.*)(</2>)/", $html, $matches);

 
  for ($i=0; $i< count($matches[0]); $i++) {

 
  echo "matched: ".$matches[0][$i]."n";

 
  echo "part 1: ".$matches[1][$i]."n";

 
  echo "part 2: ".$matches[3][$i]."n";

 
  echo "part 3: ".$matches[4][$i]."nn";

 
  }

 
  ?>

本例将输出:

 
  matched: <b>bold text</b>

 
  part 1: <b>

 
  part 2: bold text

 
  part 3: </b>

 
  matched: <a href=howdy.html>click me</a>

 
  part 1: <a href=howdy.html>

 
  part 2: click me

 
part 3: </a>

例1. 在文本中搜索“php”

 代码如下

    <?php

    // 模式定界符后面de “i” 表示不区分大小写字母de搜索

    if (preg_match (“/php/i”, “PHP is the web scripting language of choice.”)) {

    print “A match was found.”;

    } else {

    print “A match was not found.”;

    }

    ?>

    例2. 搜索单词“web”

 代码如下

    <?php

    /* 模式中de b 表示单词de边界,因此只you独立de “web” 单词会被匹配,

    * 而不会匹配例如 “webbing” 或 “cobweb” 中de一部分 */

    if (preg_match (“/bwebb/i”, “PHP is the web scripting language of choice.”)) {

    print “A match was found.”;

    } else {

    print “A match was not found.”;

    }

    if (preg_match (“/bwebb/i”, “PHP is the website scripting language of choice.”)) {

    print “A match was found.”;

    } else {

    print “A match was not found.”;

    }

    ?>

    例3. 从 URL 中取出域名

 代码如下

    <?php

    // 从 URL 中取得主机名

    preg_match(“/^(,

    $host = $matches.;

    // 从主机名中取得后面两段

    preg_match(“/[^./]+.[^./]+$/”, $host, $matches);

    echo “domain name is: {$matches[0]}n”;

    ?>

    输出:

    domain name is: php.net

preg_match_all 导致apache 重启的解决办法

如 preg_match_all("/ni(.*?)wo/", $html, $matches);)进行分析匹配比较长的字符串 $html 时(大于10万字节,一般用于分析采集回来的网页源码),Apache服务器会崩溃自动重启。

在Apache错误日志里有这样的提示:

[Thu Apr 11 18:31:31 2013] [notice] Parent: child process exited with status 128 -- Restarting.
[Thu Apr 11 18:31:31 2013] [notice] Apache/2.2.9 (Win32) PHP/5.2.17 configured -- resuming normal operations
[Thu Apr 11 18:31:31 2013] [notice] Server built: Jun 13 2008 04:04:59
[Thu Apr 11 18:31:31 2013] [notice] Parent: Created child process 2964
[Thu Apr 11 18:31:31 2013] [notice] Disabled use of AcceptEx() WinSock2 API
[Thu Apr 11 18:31:31 2013] [notice] Child 2964: Child process is running
[Thu Apr 11 18:31:31 2013] [notice] Child 2964: Acquired the start mutex.
[Thu Apr 11 18:31:31 2013] [notice] Child 2964: Starting 350 worker threads.
[Thu Apr 11 18:31:31 2013] [notice] Child 2964: Listening on port 80.

那么如何增加win平台下 ThreadStackSize 的大小呢? 在apache的配置文件 httpd.conf 里启用 “Include conf/extra/httpd-mpm.conf”(删除前面的注释#),然后在 httpd-mpm.conf 文件里的 mpm_winnt_module 配置模块里设置 “ThreadStackSize 8400000”即可(大约8M)。

 代码如下 复制代码

 代码如下

<IfModule mpm_winnt_module>
    ThreadStackSize 8400000
    ThreadsPerChild      200
    MaxRequestsPerChild    10000
    Win32DisableAcceptEx
</IfModule>

 

这里需要注意的是,32位的Apache程序只能最多使用大约2GB内存空间! 因此,ThreadStackSize 和ThreadsPerChild 的值相乘后(8M * 200)不应该超过2G,否则无法启动apache,出现的错误日志如下:
[Thu Apr 11 20:02:45 2013] [crit] (OS 8)存储空间不足,无法处理此命令。  : Child 4832: _beginthreadex failed. Unable to create all worker threads. Created 212 of the 220 threads requested with the ThreadsPerChild configuration directive.
    通过上面的提示,飘易可以告诉大家的是在我的这台服务器上,当线程堆栈大小设为8M时,我可以设置的线程数最多是212个。

本文由js333发布于计算机互联网,转载请注明出处:php中preg_match_all函数用法详解

关键词:

上一篇:没有了

下一篇:php中文乱码问题总结与解决办法,PHP统一页面编