作为一般经验法则,您必须避免使用正则表达式解析 HTML/XML 内容。原因如下:
整个 正则表达式无法解析 HTML,因为它依赖于匹配正则表达式无法实现的开始和结束标记。
正则表达式只能匹配正则语言,但 HTML 是一种上下文无关语言。在 HTML 上使用正则表达式唯一可以做的就是启发式方法,但这并不适用于所有条件。应该有可能呈现一个将被任何正则表达式错误匹配的 HTML 文件。
——https://stackoverflow.com/a/590789/65732
使用专为parsing HTML/XML documents 设计的DOM parser。这是一个例子:
# 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),并且这个 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 项。
-
最可靠和最常用的方法是从此类网站(严重依赖 javascript)抓取数据是使用能够执行 javascript 代码的浏览器模拟器,如 Selenium。查看Facebook's PHP WebDriver 包,它是 Selenium WebDriver 可用的最复杂的 PHP 绑定。它为您提供了一个 API 来远程控制 Web 浏览器并针对它们执行 javascript。
另外,请参阅 Behat 的 Mink,它附带了适用于无头浏览器和成熟浏览器控制器的各种驱动程序。驱动程序包括 Goutte、BrowserKit、Selenium1/2、Zombie.js、Sahi 和 WUnit。
-
见V8js,PHP 扩展;它将 V8 javascript 引擎嵌入到 PHP 中。它允许您直接从您的 PHP 脚本评估 javascript 代码。但是,如果您没有大量使用该功能,那么安装 PHP 扩展就有点过头了。但是如果你想使用 DOM 解析器提取相关脚本:
$script = $crawler->filterXPath('//head/following-sibling::script[2]')->text();
使用 HtmlUnit 解析页面,然后将最终的 HTML 提供给 PHP。你需要一个small Java wrapper。对,你的情况有点矫枉过正。
提取 javascript 代码并使用 JS 解析器/标记器库解析它,例如 hiltonjanfield/js4php5 或 squizlabs/PHP_CodeSniffer,其中包含 JS tokenizer。
如果应用程序正在进行 ajax 调用来操作 DOM。为了您自己的应用程序,您可能能够重新分派这些请求并解析响应。一个示例是页面对cart.js 进行的ajax 调用以检索与购物车项目相关的数据。但这里不是读取产品变体数量的情况。
您可能还记得我告诉过您使用正则表达式解析整个 HTML/XML 文档是个坏主意。但是当其他方法更难时,可以部分地使用它们从 HTML/XML 文档中提取字符串。如果您对何时使用它有任何困惑,请阅读我在这篇文章顶部引用的 SO 答案。
这种方法是通过对整个页面源运行一个简单的正则表达式来匹配产品变体的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)才能工作,因为每个产品的数量都带有一个变体。您可以从 URL 的查询字符串中提取它:?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-heavy 站点。
- 部分使用正则表达式来提取使用 DOM 解析器无法提取的内容。
如果您只需要这两个字段,则可以使用正则表达式。否则,请考虑其他方法。