替代php preg_match从外部网站提取数据?

时间:2012-09-16 17:54:56

标签: php

我想在外部网页中提取特定div的内容,div看起来像这样:

<dt>Win rate</dt><dd><div>50%</div></dd>

我的目标是“50%”。我实际上是使用这个PHP代码来提取内容:

function getvalue($parameter,$content){
    preg_match($parameter, $content, $match);
    return $match[1];
    };
$parameter = '#<dt>Score</dt><dd><div>(.*)</div></dd>#';
$content = file_get_contents('https://somewebpage.com');

一切正常,问题是这种方法花费了太多时间,特别是如果我要使用不同的内容多次使用它。

我想知道是否有更好(更快,更简单等)的方式来实现相同的功能? THX!

3 个答案:

答案 0 :(得分:3)

您可以使用DOMDocument::loadHTML并导航到指定节点。

$content = file_get_contents('https://somewebpage.com');
$doc = new DOMDocument();
$doc->loadHTML($content);

现在要到达所需的节点,您可以使用方法DOMDocument::getElementsByTagName,例如

$dds = $doc->getElementsByTagName('dd');
foreach($dds as $dd) {
  // process each <dd> element here, extract inner div and its inner html...
}

编辑:我看到@pebbl关于DomDocument变慢的观点。实际上,用preg_match解析HTML是一个麻烦的呼唤;在这种情况下,我还建议查看事件驱动的SAX XML解析器。它更轻量,更快,内存更少,因为它不构建树。您可以查看XML_HTMLSax这样的解析器。

答案 1 :(得分:2)

您可以采取三项主要措施来提高代码的速度:

将外部页面加载卸载到另一个时间(即使用cron)

在基于linux的服务器上,我知道要建议什么,但在使用Windows时看到我不确定等效的是什么,但Cron for linux允许您在某些计划时间偏移时触发脚本 - < em>在后台 - 所以不使用浏览器。基本上我建议您创建一个脚本,其唯一目的是在特定时间偏移(取决于您需要更新数据的频率)然后获取网站页面然后将这些网页写入本地系统上的文件。

$listOfSites = array(
  'http://www.something.com/page.htm',
  'http://www.something-else.co.uk/index.php',
);

$dirToContainSites = getcwd() . '/sites';

foreach ( $listOfSites as $site ) {
  $content = file_get_contents( $site );

  /// i've just simply converted the URL into a filename here, there are
  /// better ways of handling this, but this at least keeps things simple.
  /// the following just converts any non letter or non number into an
  /// underscore... so, http___www_something_com_page_htm
  $file_name = preg_replace('/[^a-z0-9]/i','_', $site);

  file_put_contents( $dirToContainSites . '/' . $file_name, $content );
}

一旦您创建了此脚本,您就需要将服务器设置为根据需要定期执行。然后,您可以修改显示要从本地文件读取的统计信息的前端脚本,这样可以显着提高速度。

您可以在此处了解如何从目录中读取文件:

http://uk.php.net/manual/en/function.dir.php

或更简单的方法(但容易出现问题)只是重新处理您的网站数组,使用上面的preg_replace将URL转换为文件名,然后检查文件&# 39;存在于文件夹中。

缓存计算统计数据的结果

很可能这是一个统计信息页面,您会非常频繁地访问(不像公共页面那样频繁,但仍然)。如果访问同一页面的次数比执行基于cron的脚本更频繁,则没有理由再次进行所有计算。所以基本上你要做的就是缓存你的输出所做的事情类似于以下内容:

$cachedVersion = getcwd() . '/cached/stats.html';

/// check to see if there is a cached version of this page
if ( file_exists($cachedVersion) ) {
  /// if so, load it and echo it to the browser
  echo file_get_contents($cachedVersion);
}
else {
  /// start output buffering so we can catch what we send to the browser
  ob_start();

  /// DO YOUR STATS CALCULATION HERE AND ECHO IT TO THE BROWSER LIKE NORMAL

  /// end output buffering and grab the contents so we now have a string
  /// of the page we've just generated
  $content = ob_get_contents(); ob_end_clean();

  /// write the content to the cached file for next time
  file_put_contents($cachedVersion, $content);

  echo $content;
}

一旦你开始缓存你需要注意的东西,你应该删除或清除你的缓存 - 否则,如果你不是你的统计数据输出永远不会改变。关于这种情况,清除缓存的最佳时间是你去的地方再次获取外部网页。因此,您应该将此行添加到&#34; cron&#34;的底部。脚本。

$cachedVersion = getcwd() . '/cached/stats.html';

unlink( $cachedVersion ); /// will delete the file

您可以对缓存系统进行其他速度改进(您甚至可以记录外部网页的修改时间并仅在更新时加载)但我已尝试过让事情易于解释。

在这种情况下不要使用HTML Parser

扫描HTML文件以获取一个特定的唯一值不需要使用完全成熟甚至轻量级的HTML Parser。错误地使用RegExp似乎是许多初创程序员所涉及的问题之一,并且是一个总是被问到的问题。这导致了更多经验编码员的大量自动下意识反应,自动遵循以下逻辑:

if ( $askedAboutUsingRegExpForHTML ) {
  $automatically->orderTheSillyPersonToUse( $HTMLParser );
} else {
  $soundAdvice = $think->about( $theSituation );
  print $soundAdvice;
}

当标记中的目标不是那么独特时,应该使用HTMLParsers,或者您要匹配的模式依赖于这样的脆弱规则,以至于它会破坏第二个额外的标记或字符。它们应该用于使您的代码更可靠,而不是如果您想加快速度。即使不构建所有元素的树的解析器仍将使用某种形式的字符串搜索或正则表达式表示法,因此除非您使用的库代码以极其优化的方式编译,否则它将无法很好地编码strpos / preg_match logic。

考虑到我没有看到你希望解析的HTML,我可能已经离开了,但是根据我在你的代码段中看到的,使用strpos和preg_match的组合找到值应该很容易。显然,如果你的HTML更复杂并且可能随机多次出现<dt>Win rate</dt><dd><div>50%</div></dd>,它将导致问题 - 但即便如此 - HTMLParser仍然会遇到同样的问题。

$offset = 0;

/// loop through the occurances of 'Win rate'
while ( ($p = stripos ($html, 'win rate', $offset)) !== FALSE ) {

  /// grab out a snippet of the surrounding HTML to speed up the RegExp
  $snippet = substr($html, $p, $p + 50 ); 

  /// I've extended your RegExp to try and account for 'white space' that could
  /// occur around the elements. The following wont take in to account any random
  /// attributes that may appear, so if you find some pages aren't working - echo
  /// out the $snippet var using something like "echo '<xmp>'.$snippet.'</xmp>';"
  /// and that should show you what is appearing that is breaking the RegExp.

  if ( preg_match('#^win\s+rate\s*</dt>\s*<dd>\s*<div>\s*([0-9]+%)\s*<#i', $snippet, $regs) ) {
    /// once you are here your % value will be in $regs[1];
    break; /// exit the while loop as we have found our 'Win rate'
  }

  /// reset our offset for the next loop
  $offset = $p;
}

要知道

如果您是PHP的新手,正如您在上面的评论中所述,那么上述内容可能看起来相当复杂 - 它就是这样。你要做的事情非常复杂,特别是如果你想以最佳和快速的方式做到这一点。但是,如果你按照我给出的代码进行研究并研究你不确定/没有听说过(php.net是你的朋友) ,它应该让你更好地了解实现你正在做的事情的好方法。

然而,提前猜测,以下是您可能遇到的一些问题:

  • 文件权限错误 - 为了能够从本地操作系统读取和写入文件,您需要具有正确的权限才能执行此操作。如果您发现无法将文件写入特定目录,则可能是您正在使用的主机不允许您这样做。如果是这种情况,您可以联系他们询问如何获得文件夹的写入权限,或者如果不可能,您可以轻松更改上面的代码以使用数据库。

  • 我无法看到我的内容 - 使用输出缓冲时,所有的echo和print命令都不会被发送到浏览器,而是会被保存在内存中。当脚本退出时,PHP应该自动输出所有存储的内容,但如果你使用像ob_end_clean()这样的命令,这实际上会擦除&#39;缓冲区&#39;所以所有内容都被删除了。当你知道自己在回应某些东西时,这会导致令人困惑的情况......但它并没有出现。

(迷你免责声明:)我已手动输入以上所有内容,因此您可能会发现存在PHP错误,如果是这样,他们会感到困惑,只需将它们写回此处,StackOverflow可以帮助您解决问题)

答案 2 :(得分:1)

不是试图不使用preg_match,而是为了不缩小文档内容的大小?例如,您可以转储<body之前的所有内容以及</body>之后的所有内容。然后preg_match将搜索更少的内容。

此外,您可以尝试将这些进程中的每一个作为伪单独的线程,这样它们就不会一次发生。

相关问题