1

我想在外部网页中提取特定 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');

一切正常,问题是这种方法花费了太多时间,特别是如果我必须多次使用不同的 $content。

我想知道是否有更好(更快、更简单等)的方法来完成相同的功能?谢谢!

4

3 回答 3

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以了解此类解析器。

于 2012-09-16T18:04:07.133 回答
2

基本上,您可以做三件事来提高代码的速度:

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

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

$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 转换为文件名,然后检查文件夹中是否存在文件。

缓存计算统计数据的结果

这很可能是您想要经常访问的统计页面(不像公共页面那样频繁,但仍然如此)。如果访问同一页面的次数多于执行基于 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;
}

一旦你开始缓存东西,你需要知道什么时候应该删除或清除缓存——否则,如果你不这样做,你的统计输出将永远不会改变。对于这种情况,清除缓存的最佳时间是在您再次获取外部网页时。因此,您应该将此行添加到“cron”脚本的底部。

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

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

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

在这种情况下不要使用 HTML 解析器

扫描 HTML 文件以获取特定的唯一值不需要使用成熟的甚至是轻量级的 HTML 解析器。错误地使用 RegExp 似乎是许多初创程序员陷入的事情之一,并且是一个经常被问到的问题。这导致更多经验丰富的编码人员做出许多自动下意识的反应,以自动遵守以下逻辑:

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

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

考虑到我还没有看到您希望解析的 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 是您的朋友),它应该可以让您更好地理解实现你正在做的事情。

但是,请提前猜测,以下是您可能会遇到上述问题的一些问题:

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

  • 我看不到我的内容- 当使用输出缓冲时,所有回显和打印命令都不会发送到浏览器,而是保存在内存中。PHP 应该在脚本退出时自动输出所有存储的内容,但是如果您使用 ob_end_clean() 之类的命令,这实际上会擦除“缓冲区”,因此所有内容都会被删除。当你知道你在呼应某些东西时,这可能会导致混乱的情况......但它只是没有出现。

(迷你免责声明:)我已经手动输入了以上所有内容,因此您可能会发现存在 PHP 错误,如果是这样,而且它们令人困惑,只需将它们写回到这里,StackOverflow 可以帮助您)

于 2012-09-17T08:49:52.647 回答
1

与其尝试不使用preg_match,为什么不直接缩小文档内容的大小?例如,您可以转储之前<body的所有内容和之后的所有内容</body>。那么preg_match将搜索更少的内容。

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

于 2012-09-16T18:10:34.267 回答