我正在尝试使用Zend_Dom_Query解析屏幕抓取的数据,但我正在努力如何正确地应用它来解决我的问题,而我在SO上看到的所有其他答案都做出了一些假设,这些假设非常坦率地用他们的天真吓唬我。
一个典型的例子是How to Pass Array from Zend Dom Query Results to table,其中通过使用对query()
方法的单独调用从文档正文中提取数据点对。
$year = $dom->query('.secondaryInfo');
$rating = $dom->query('.ratingColumn');
如果基本假设是 AND 存在相同数量的$year
和$rating
结果,则表明它们在文档中彼此正确对齐。如果这些假设中的任何一个是错误的,那么提取的数据就不会毫无价值 - 事实上它就变成了所有的谎言。
在我的情况下,我试图从一个站点中提取多个数据块,其中每个块名义上都是以下形式:
<p class="main" atrb1="value1">
<a href="#1" >href text 1</a>
<span class="sub1">
<span class="span1"></span>
<span class="sub2">
<span class="span2">data span2</span>
<a href="#2">href text 2</a>
</span>
<span class="sub3">
<span class="span3">
<p>Some other data</p>
<span class="sub4">
<span class="sub5">More data</span>
</span>
</span>
</span>
</span>
</p>
对于每个块,我需要从各个部分获取数据:
然后将数据集处理为一个不同的单元,而不是作为不同数据的多个集合。
我知道我可以硬编码每个元素的选择(我现在这样做),但这会产生依赖源数据稳定的脆弱代码。本周数据源又发生了变化,我的硬编码抓取工作无法正常工作。因此,我试图编写健壮的代码,可以找到我想要的东西,而不必关心/了解整体结构(Hmmm - Linq for php?)
所以在我看来,我希望代码看起来像
$dom = new Zend_Dom_Query($body);
$results = $dom->query('.main');
foreach ($results as $result)
{
$data1 = $result->query(".main a");
$data2 = $result->query(".main .span2");
$data3 = $result->query(".main .sub a");
etc
if ($data1 && $data2 && $data3) {
Do something
} else {
Do something else
}
}
是否有可能通过库存Zend / PHP函数调用执行我想要的操作?或者我是否需要编写某种自定义函数来实现$result->query()
?
答案 0 :(得分:0)
好的..所以我咬紧牙关并为问题写了我自己的解决方案。此代码通过Zend_Dom_Query
的结果进行递归,并查找匹配的css选择器。如上所示,代码适用于我,也帮助清理我的代码。对我来说,性能不是问题,但总是和Caveat Emptor一样。我还留下了一些注释掉的代码,可以实现搜索引导的位置。代码也是类的一部分,因此在地方使用$this->
。
代码用作:
$dom = new Zend_Dom_Query($body);
$results = $dom->query('.main');
foreach ($results as $result)
{
$data1 = $this->domQuery($result, ".sub2 a");
if (!is_null($data1))
{
Do Something
}
}
在<a href="#2">href text 2</a>
元素下找到<span class="sub2">
元素。
// Function that recurses through a Zend_Dom_Query_Result, looking for css selectors
private function recurseDomQueryResult($dom, $depth, $targets, $index, $count)
{
// Gross checking
if ($index<0 || $index >= $count) return NULL;
// Document where we are
$element = $dom->nodeName;
$class = NULL;
$id = NULL;
// $href = NULL;
// Skip unwanted elements
if ($element == '#text') return NULL;
if ($dom->hasAttributes()) {
if ($dom->hasAttribute('class'))
{
$class = trim($dom->getAttribute('class'));
}
if ($dom->hasAttribute('id'))
{
$id = trim($dom->getAttribute('id'));
}
// if ($element == 'a')
// {
// if ($dom->hasAttribute('href'))
// {
// $href = trim($dom->getAttribute('href'));
// }
// }
}
// $padding = str_repeat('==', $depth);
// echo "$padding<$element";
// if (!($class === NULL)) echo ' class="'.$class.'"';
// if (!($href === NULL)) echo ' href="'.$href.'"';
// echo '><br />'. "\n";
// See if we have a match for the current element
$target = $targets[$index];
$sliced = substr($target,1);
switch($target[0])
{
case '.':
if ($sliced === $class) {
$index++;
}
break;
case '#':
if ($sliced === $id) {
$index++;
}
break;
default:
if ($target === $element) {
$index++;
}
break;
}
// Check for having matched all
if ($index == $count) return $dom;
// We didn't have a match at this level
// So recursively look at all the children
$children = $dom->childNodes;
if ($children) {
foreach($children as $child)
{
if (!is_null(($result = $this->recurseDomQueryResult($child, $depth+1, $targets, $index, $count)))) return $result;
}
}
// Did not find anything
// echo "$padding</$element><br />\n";
return NULL;
}
// User function that you call to find a single element in a Zend_Dom_Query_Result
// $dom is the Zend_Dom_Query_Result object
// $path is a path of css selectors, e.g. ".sub2 a"
private function domQuery($dom, $path)
{
$depth = 0;
$index = 0;
$targets = explode(' ', $path);
$count = count($targets);
return $this->recurseDomQueryResult($dom, $depth, $targets, $index, $count);
}