使用cURL和Regex进行屏幕抓取


Screen scraping with cURL and Regex

考虑以下格式的文档:

<!DOCTYPE html>
<html>
<head>
<title></title>
<body>
   <div class="blog_post_item first">
       <?php // some child elements ?>
   </div><!-- end blog_post_item -->
</body>
</html>

我正在用PHP cURL将这样的文档从一个域加载到另一个域。我想修剪我的cURL结果,使其仅包括div.blog_post_item.first及其子级。我知道另一个页面的结构,但我不能编辑它。我想我可以使用preg_match来查找打开和关闭标签;它们将始终看起来相同,包括结束注释。

我已经搜索了使用cURL/XXPath/XXSLT/任何东西进行屏幕抓取的示例/教程,它主要是HTML解析库名称的循环性敲击。因此,请提供一个简单的工作示例。请不要简单地解释用regex解析HTML是一个潜在的安全漏洞。请不要只列出我应该进一步阅读的库和规范。

我有一些简单的PHP cURL代码:

$ch = curl_init("http://a.web.page.com");
curl_setopt($ch, CURLOPT_HEADER, 0);
$output = curl_exec($ch);
curl_close($ch);

当然,现在$output包含了整个源。如何获取该元素的内容?

如果您确信开始和结束总是相同的,那么这很容易。你所要做的就是寻找开始和结束,并匹配两者之间的一切。我想很多人会因为我使用regex来查找一些HTML而对我感到愤怒,但它会起作用的!

// cURL
$ch = curl_init("http://a.web.page.com");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec($ch);
curl_close($ch);
if(empty($output)) exit('Couldn''t download the page');
// finding your data
$pattern = '/<div class="blog_post_item first">(.*?)<'/div><!-- end blog_post_item -->/';
preg_match_all($pattern, $output, $matches);
var_dump($matches); // all matches

因为我不知道你想爬哪个网站,我不确定这是否有效。


经过很长一段时间(确切地说是26分钟)的搜索,我找到了为什么它不起作用。点(.)与换行符不匹配。因为HTML中充满了新行,所以无法与内容相匹配。使用一个稍微脏一点的破解,我还是设法让它匹配(尽管你已经选择了答案)。

// cURL
$ch = curl_init('http://blogg.oscarclothilde.com/');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec($ch);
curl_close($ch);
if(empty($output)) exit('Couldn''t download the page');
// finding your data
$pattern = '/<div class="blog_post_item first">(([^.]|.)*?)<'/div><!-- end blog_post_item -->/';
preg_match_all($pattern, $output, $matches);
var_dump($matches[1][0]); // all matches

如果您确信以下结构:

<div class="blog_post_item first">
   WHATEVER
</div><!-- end blog_post_item -->

如果你确定结束代码不会出现在WHATEVER中,那么你可以简单地获取它。

(请注意,我用WHATEVER替换了您原来的PHP。CURL只会获取HTML,它将包含内容,而不是PHP。)

您不需要正则表达式。您也可以简单地通过搜索所需的字符串来完成,就像下面的例子一样。

$curlResponse = '
<!DOCTYPE html>
<html>
<head>
<title></title>
<body>
   <div class="blog_post_item first">
       <?php // some child elements ?>
   </div><!-- end blog_post_item -->
</body>
</html>';
$startStr = '<div class="blog_post_item first">';
$endStr = '</div><!-- end blog_post_item -->';
$startStrPos = strpos($curlResponse, $startStr)+strlen($startStr);
$endStrPos = strpos($curlResponse, $endStr);
$wanted = substr($curlResponse, $startStrPos, $endStrPos-$startStrPos );
echo htmlentities($wanted);

这段代码应该可以工作(>=5.3.6和dom扩展):

$s = <<<EOM
<!DOCTYPE html>
<html>
<head>
<title></title>
<body>
   <div class="blog_post_item first">
       <?php // some child elements ?>
   </div><!-- end blog_post_item -->
</body>
</html>
EOM;
$d = new DOMDocument;
$d->loadHTML($s);
$x = new DOMXPath($d);
foreach ($x->query('//div[contains(@class, "blog_post_item") and contains(@class, "first")]') as $el) {
        echo $d->saveHTML($el);
}