0

HTML ソース コードから製品名と価格をスクレイピングするために、Curl、XPath、および PHP を使用しています。これは、私が調べているソース コードに似たサンプルです。

<div class="Gamesdb">
  <p class="media-title">
    <a href="/Games/Console/4-/105/Bluetooth-Headset/">Bluetooth Headset</a>
  </p>
  <p class="sub-title"> Console </p>
  <p class="rating star-50">
    <a href="/Games/Console/4-/105/Bluetooth-Headset/ProductReviews.html">(1)</a>
  </p>
  <p class="mt5">
    <span class="price-preffix">
      <a href="/Games/Console/4-/105/Bluetooth-Headset/">1 New</a>
      from 
    </span>
    <a class="wt-link" href="/Games/Console/4-/105/Bluetooth-Headset/">
      <span class="price">
        <em>£34</em>
        .99
      </span>
      <span class="free-delivery"> FREE delivery</span>
    </a>
  </p>
  <p class="mt10">
    <a class="primary button" href="/Games/Console/4-/105/Bluetooth-Headset/">
      Product Details
      <span style="color: rgb(255, 255, 255); margin-left: 6px; font-size: 16px;">»</span>
    </a>
  </p>
</div>

メディアタイトルを抽出したい:

<p class="media-title">
    <a href="/Games/Console/4-/105/Bluetooth-Headset/">Bluetooth Headset</a>
    </p>

次の価格クラスも存在する場合のみ:

<span class="price">
    <em>£34</em>
    .99
    </span>

リストされている他の製品の多くには含まれていません。製品名と価格の両方を抽出するか、何も抽出せずに次の製品に進む必要があります。

これは、他の条件に関係なくすべての結果を取得するのに効果的な、現在使用しているコードのサンプルです。

$results=file_get_contents('SCRAPEDHTML.txt');

$html = new DOMDocument();
@$html->loadHtml($results);
$xpath = new DOMXPath($html);
$nodelist = $xpath->query('//p[@class="media-title"]|//span[@class="price"]');

foreach ($nodelist as $n){

$results2[]=$n->nodeValue;

}

これは正しい xpath クエリを使用して可能であると信じていますが、これまでのところ達成できていません。よろしくお願いします。

4

2 に答える 2

0

ごとに 1 つの「アイテム」しかないと仮定していますdiv.Gamesdb。そうでない場合は、ソース html に xpath だけを使用するための十分な構造がない可能性があります。おそらく、製品名を索引付けして、一致する製品名に近い価格を探す必要があります。

これは 1 つの巨大な XPath で実行できますが、複数の XPath を使用することをお勧めします。両方の方法を示します。

まず、DOMXPathクラス名に一致するヘルパーを作成して登録します。

// This helper is the equivalent to the XPath:
// contains(concat(' ',normalize-space(@attr),' '), ' $token ')
// It's not necessary, but it's a bit easier to read and more
// bulletproof than @ATTR="TOKEN"
function has_token($attr, $token)
{
    $attr = $attr[0];
    $regex = '/(?:^|\s)'.preg_quote($token,'/').'(?:\s|$)/Su';
    return (bool) preg_match($regex, $attr->value);
}

$xp = new DOMXPath($d);
$xp->registerNamespace("php", "http://php.net/xpath");
$xp->registerPHPFunctions("has_token");

次に、巨大な XPath を使用できます。

$xp_container = '/html/body//div[php:function("has_token", @class, "Gamesdb")]';
$xp_title = 'p[php:function("has_token", @class, "media-title")]';
$xp_price = '//span[php:function("has_token", @class, "price")]';

$xp_titles_prices = "$xp_container[{$xp_title}][{$xp_price}]/{$xp_title} | $xp_container[{$xp_title}][{$xp_price}]{$xp_price}";


$nodes = $xp->query($xp_items);

$items = array();

$i = 0; // enumerator
foreach ($nodes as $node) {
    $key = ($node->nodeName==='p') ? 'title' : 'price';
    $value = '';
    switch ($key) {
        case 'price':
            // remove inner whitespace
            $value = preg_replace('/\s+/Su', '', trim($node->textContent));
            break;
        case 'title':
            $value = preg_replace('/\s+/Su', ' ', trim($node->textContent));
            break;
    }
    $items[(int) floor($i/2)][$key] = $value;
    $i += 1;
}

ただし、全体的なコードは脆弱で不明瞭です。XPath ユニオン演算子 ( |) はドキュメントの順序でノードを返すため、リストを二分することはできません。PHP コードは nodelist 内のすべての項目を調べ、DOM を使用してこのデータに対応するフィールドを判別する必要があります。コードを拡張して 3 番目のアイテム (価格など) を収集する場合に必要な変更について考えてみてください。今から 3 か月後にこれらの変更を行うことを想像してみてください。このコードはもはや新鮮で​​はありません。

代わりに複数の XPath 呼び出しを使用し、XPath ではなく PHP で「価格とタイトルの両方のデータがありますか」チェックを行うことをお勧めします。

$xpitems = '/html/body//div[php:function("has_token", @class, "Gamesdb")]';
// below use $xpitems context:
$xptitle = 'normalize-space(p[php:function("has_token", @class, "media-title")])';
$xpprice = 'normalize-space(//span[php:function("has_token", @class, "price")])';

$nodeitems = $xp->query($xpitems);

$items = array();
foreach ($nodeitems as $nodeitem) {
    $item = array(
        'title' => $xp->evaluate($xptitle, $nodeitem),
        'price' => str_replace(' ', '', $xp->evaluate($xpprice, $nodeitem)),
    );
        // Only add this item if we have data for *all* fields:
    if (count(array_filter($item)) === count($item)) {
        $items[] = $item;
    }
}

これは読みやすく理解しやすく、将来拡張するのもはるかに簡単です。

于 2012-10-15T23:06:43.430 に答える
0

製品の名前とその価格の両方を返す単一の XPath を持つことはできません。私の提案はdiv、両方の情報を含むすべてのノードを最初に取得することです。

//div[p[@class='media-title'] and //span[@class='price']]

('クラスを持つ子ノードとクラスを持つ子孫ノードdivを持つすべてのノード'); 次に、返されたすべてのノードでループし、他の 2 つの XPath を使用して製品名と価格を抽出します。pmedia-titlespanprice

p[@class='media-title']

//span[@class='price']
于 2012-10-15T22:06:36.153 に答える