使用PHP从网站中提取特定数据

时间:2016-12-24 03:13:20

标签: php regex web-scraping domdocument

我是PHP的新手,我希望从不同的网站中提取库存数量和大小等数据。对于我将如何做到这一点感到困惑。 Domdocument会成为可能吗?

不确定这是否是最佳方法。

我是在here上的第164-174行尝试的。

非常感谢任何帮助!

编辑 - 这是我更新的代码。不要认为它是最有效的做事方式。

<html>
<?php



$url = 'https://kithnyc.com/collections/adidas/products/kith-x-adidas-    consortium-response-trail-boost?variant=35276776455';
$html = file_get_contents($url);

//preg_match('~itemprop="image"\scontent="(\w+.\w+.\w+.\w+.\w+.\w+)~',     $html, $image);
//$image = $image[1];

preg_match('~,"title":"(\w+.\w+.\w+.\w+.\w+.\w+)~', $html, $title);
$title = $title[1];


preg_match_all('~{"id":(\d+)~', $html, $id);
$id = $id[1];

preg_match_all('~","public_title":"(\d+..)~', $html, $size);
$size = $size[1];

preg_match_all('~inventory_quantity":(\d+)~', $html, $quantity);
$quantity = $quantity[1];


function plain_url_to_link($url) {
return preg_replace(
    '%(https?|ftp)://([-A-Z0-9./_*?&;=#]+)%i',
    '<a target="blank" rel="nofollow" href="$0"      target="_blank">$0</a>', $url);
}



$i = 0;
$j = 2;

echo "$title<br />";
echo "<br />";

//echo $image;

echo plain_url_to_link($url);
echo "<br />";
echo "<br />";

for($i = 0; $i < 18; $i++) {
print "Size: $size[$i] --- Quantity: $quantity[$i] --- ID: $id[$j]";
$j++;
echo "<br />";
}


echo "<br />";
//print_r($quantity);




?>
</body>
</html>

1 个答案:

答案 0 :(得分:1)

作为一般经验法则,您必须避免使用正则表达式解析HTML / XML内容。这就是原因:

  

整个使用正则表达式无法进行HTML解析,因为它取决于匹配开头和结束标记,这是正则表达式无法实现的。

     

正则表达式只能匹配常规语言,但HTML是无上下文的语言。你可以用HTML上的regexp做的唯一的事情就是启发式,但这并不适用于所有条件。应该可以呈现一个HTML文件,该文件将被任何正则表达式错误地匹配。

     

- https://stackoverflow.com/a/590789/65732

使用专为DOM parser目的而设计的parsing HTML/XML documents。这是一个例子:

# Installing Symfony's dom parser using Composer
composer require symfony/dom-crawler symfony/css-selector
<?php

require 'vendor/autoload.php';

use Symfony\Component\DomCrawler\Crawler;

$html = file_get_contents('https://kithnyc.com/collections/footwear/products/kith-x-adidas-consortium-response-trail-boost?variant=35276776455');

$crawler  = new Crawler($html);
$price    = $crawler->filter('.product-header-title[itemprop="price"]')->text();
// UPDATE: Does not work! as the page updates the button text 
// later with javascript. Read more for another solution.
$in_stock = $crawler->filter('#AddToCartText')->text();

if ($in_stock == 'Sold Out') {
    $in_stock = 0; // or `false`, if you will
}

echo "Price: $price - Availability: $in_stock";
// Outputs:
// Price: $220.00 - Availability: Buy Now
// We'll fix "Availability" later...

使用这样的解析器,您也可以使用XPath提取元素。

但是如果你想解析该页面中包含的javascript代码,你最好使用像Selenium这样的浏览器模拟器。然后,您可以以编程方式访问该页面中所有全局可用的javascript变量/函数。

更新

获得价格

因此,运行上述代码时出现此错误:

  

PHP致命错误
  未捕获的Symfony \ Component \ CssSelector \ Exception \ SyntaxErrorException:预期的标识符,但已找到。

这是因为目标页面对price元素(.-price)使用了无效的类名,并且此Symfony的CSS选择器组件无法正确解析它,因此异常。这是元素:

<span id="ProductPrice" class="product-header-title -price" itemprop="price" content="220">$220.00</span>

要解决此问题,请改用itemprop属性。这是可以匹配它的选择器:

.product-header-title[itemprop="price"]

我相应地更新了上面的代码以反映它。我对它进行了测试,它正在为价格部分工作。

获取库存状态

现在我实际测试了代码,我发现稍后使用javascript设置产品的库存状态。当您使用file_get_contents()获取页面时,它不存在。您可以自己查看,刷新页面,按钮显示为Buy Now,然后一秒后它会更改为Sold Out

但幸运的是,产品变体的数量深埋在页面的某个位置。这是Shopify用于渲染产品页面的巨大对象的pretty printed copy

所以现在问题是使用PHP解析javascript代码。有一些一般方法可以解决这个问题:

您可以跳过这些方法,因为它们并非针对您的问题。如果您只想解决问题,请直接跳到6号。

  1. 最可靠和最常见的方法是从这些网站(严重依赖于javascript)中抓取数据,使用像Selenium这样能够执行javascript代码的浏览器模拟器。看看Facebook's PHP WebDriver包,这是Selenium WebDriver最复杂的PHP绑定。它为您提供了一个API来远程控制Web浏览器并对它们执行JavaScript。

    另外,请参阅Behat的Mink,其中包含无头浏览器的各种驱动程序以及成熟的浏览器控制器。驱动程序包括Goutte,BrowserKit,Selenium1 / 2,Zombie.js,Sahi和WUnit。

  2. 参见V8js,PHP扩展;它将V8 javascript引擎嵌入到PHP中。它允许您直接从PHP脚本评估JavaScript代码。但是,如果您没有大量使用该功能,安装PHP扩展程序有点过分。但是如果你想使用DOM解析器提取相关的脚本:

    $script = $crawler->filterXPath('//head/following-sibling::script[2]')->text();

  3. 使用HtmlUnit解析页面,然后将最终的HTML提供给PHP。你需要一个small Java wrapper。对你的案子来说太过分了。

  4. 提取javascript代码并使用具有hiltonjanfield/js4php5squizlabs/PHP_CodeSnifferJS tokenizer等JS解析器/标记器库进行解析。

  5. 如果应用程序正在进行ajax调用来操作DOM。您可以重新发送这些请求并根据自己的应用程序解析响应。一个示例是页面向cart.js发出的ajax调用,以检索与购物车项目相关的数据。但在此处阅读产品变量数量并非如此。

  6. 您可能还记得我告诉过您,使用正则表达式解析整个 HTML / XML文档是个不错的主意。但是,当其他方法更加困难时,可以部分地使用它们从HTML / XML文档中提取字符串。如果您对何时使用它有任何疑惑,请阅读我在本文顶部引用的SO答案。

  7. 这种方法是通过对整个页面源运行一个简单的正则表达式来匹配产品变体的inventory_quantity(或者您只能针对脚本标记执行它以获得更好的性能):

    <?php
    
    require 'vendor/autoload.php';
    
    use Symfony\Component\DomCrawler\Crawler;
    
    $html = file_get_contents('https://kithnyc.com/collections/footwear/products/kith-x-adidas-consortium-response-trail-boost?variant=35276776455');
    
    $crawler  = new Crawler($html);
    $price    = trim($crawler->filter('.product-header-title[itemprop="price"]')->text());
    
    preg_match('/35276776455,.+?inventory_quantity":(\d)/', $html, $in_stock);
    $in_stock = $in_stock[1];
    
    echo "Price: $price - Availability: $in_stock";
    // Outputs:
    // Price: $220.00 - Availability: 0
    

    此正则表达式需要变体ID(在这种情况下为35276776455)才能正常工作,因为每个产品的数量都带有变体。您可以从网址的查询字符串中提取它:?variant=35276776455

    既然我们已经完成了库存状态并且我们已经使用正则表达式完成了它,那么您可能希望对价格执行相同的操作并删除DOM解析器依赖项:

    <?php
    
    $html = file_get_contents('https://kithnyc.com/collections/footwear/products/kith-x-adidas-consortium-response-trail-boost?variant=35276776455');
    
    // You need to check if it's matched before assigning 
    // $price[1]. Anyway, this is just an example.
    preg_match('/itemprop="price".+?>\s*\$(.+?)\s*<\/span>/s', $html, $price);
    $price = $price[1];
    
    preg_match('/35276776455,.+?inventory_quantity":(\d)/', $html, $in_stock);
    $in_stock = $in_stock[1];
    
    echo "Price: $price - Availability: $in_stock";
    // Outputs:
    // Price: $220.00 - Availability: 0
    

    结论

    即使我仍然认为使用正则表达式解析HTML / XML文档是个坏主意,但我必须承认可用的DOM解析器无法解析嵌入的javascript代码(可能永远不会),是你的情况。我们可以部分利用正则表达式从HTML / XML中提取字符串;使用DOM解析器无法解析的部分。总而言之:

    • 使用DOM解析器来解析/抓取页面中最初存在的HTML代码。
    • 拦截可能包含您想要的信息的ajax呼叫。在单独的http请求中重新调用它们以获取数据。
    • 使用浏览器模拟器解析/抓取使用ajax调用等填充其页面的JS重型站点。
    • 部分使用正则表达式来提取使用DOM解析器无法提取的内容。

    如果您只想要这两个字段,那么你可以使用正则表达式。否则,请考虑其他方法。