21

これは、週に 1 度か 2 度ポップアップする類似の (ただし、具体的な質問が多すぎてターゲット候補にならない) すべてに対して標準的な Q&A を提供することを目的としています。

テーブルを含む Web サイトを解析する必要があるアプリケーションを開発しています。Web ページをスクレイピングするための XPath 式の導出は退屈でエラーが発生しやすい作業であるため、これには Firebug (または他のブラウザーの同様のツール)のXPath 抽出機能を使用したいと考えています。

入力例は次のようになります。

<!-- snip -->
<table id="example">
  <tr>
    <th>Example Cell</th>
    <th>Another one</th>
  </tr>
  <tr>
    <td>foobar</td>
    <td>42</td>
  </tr>
</table>
<!-- snip -->

最初のデータ セル ("foobar") を抽出したい。Firebug は XPath 式を提案します

//table[@id="example"]/tbody/tr[2]/td[1]

これは、どの XPath テスター プラグインでも正常に動作しますが、自分のアプリケーションでは正常に動作しません (結果が見つかりません)。クエリを に削減すると//table[@id]、再び機能します。

何がうまくいかないのですか?

4

2 に答える 2

45

問題: DOM には<tbody/>タグが必要

Firebug、Chrome の開発者ツール、JavaScript の XPath 関数などは、基本的なHTML ソース コードではなく、 DOM上で動作します。

<thead/>HTML 用の DOM では、フッターのテーブル ヘッダー ( , ) に含まれていないすべてのテーブル行が<tfoot/>、テーブル ボディ タグに含まれている必要があります<tbody/>。したがって、(X)HTML の解析中にこのタグが見つからない場合、ブラウザーはこのタグを追加します。たとえば、Microsoft の DOM ドキュメントには次のように書かれています

テーブルで要素が明示的に定義されていない場合でも、tbody要素はすべてのテーブルで公開されますtbody

stackoverflow の別の回答に詳細な説明があります。

一方、HTML では必ずしもそのタグを使用する必要はありません

TBODYテーブルにテーブル本体が 1 つだけ含まれていて、テーブルのヘッドまたはフッター セクションがない場合を除き、開始タグは常に必要です。

ほとんどの XPath プロセッサは生の XML で動作します

JavaScript を除いて、ほとんどの XPath プロセッサは DOM ではなく生の XML で動作するため、<tbody/>タグを追加しません。などの HTML パーサー ライブラリは、「DOM-HTML」ではなく、XHTML のみを出力します。

これは、PHP、Ruby、Python、Java、C#、Google ドキュメント (スプレッドシート) などの Stackoverflow に投稿される一般的な問題です。Selenium はブラウザー内で実行され、DOM で動作します。つまり、影響を受けません。

問題の再現

Firebug (または Chrome の開発ツール) によって表示されるソースを、右クリックして [ページ ソースを表示] を選択する (またはブラウザーで呼び出されるもの) かcurl http://your.example.org、コマンド ラインで使用することによって得られるソースと比較します。後者はおそらく要素を含まず<tbody/>(めったに使用されません)、Firebug は常にそれらを表示します。


/tbody解決策 1:軸ステップを削除する

立ち往生しているテーブルに本当に要素が含まれていないかどうかを確認してください<tbody/>(最後の段落を参照)。もしそうなら、おそらく別の種類の問題があります。

軸ステップを削除する/tbodyと、クエリは次のようになります

//table[@id="example"]/tr[2]/td[1]

<tbody/>解決策 2:タグをスキップする

これはかなり汚い解決策であり、ネストされたテーブルでは失敗する可能性があります (内部テーブルにジャンプする可能性があります)。非常にまれなケースでのみこれをお勧めします。

/tbody軸ステップを子孫または自己ステップに置き換えます。

//table[@id="example"]//tr[2]/td[1]

<tbody/>解決策 3: タグ付きとタグなしの両方の入力を許可する

テーブルまたはクエリを「HTML ソース」と DOM コンテキストの両方で使用するかどうかが事前にわからない場合。ソリューション 2 のハックを使用したくない/使用できない、代替クエリを提供する (XPath 1.0 の場合)、または「オプションの」軸ステップ (XPath 2.0 以降) を使用する。

  • XPath 1.0 :
    //table[@id="example"]/tr[2]/td[1] | //table[@id="example"]/tbody/tr[2]/td[1]
  • XPath 2.0 ://table[@id="example"]/(tbody, .)/tr[2]/td[1]
于 2013-08-14T19:53:01.410 に答える
2

同じ問題に遭遇しました。すべての tbody タグが存在するかどうかを確認し、そのように dom をトラバースする再帰関数をほぼ書きましたが、正規表現を知っていることを思い出しました。:)

解析する前に、html を文字列として取得します。欠落している<tbody></tbody>タグを正規表現で挿入し、それを DOMDocument オブジェクトにロードし直します。

Jens Eratは良い説明をしていますが、ここにあります

解決策 4: HTML ソース<tbody>に正規表現のタグが常に含まれていることを確認する

JavaScript
    var html = '<html><table><tr><td>foo</td><td>bar</td></tr></table></html>';
    html.replace(/(<table([^>]+)?>([^<>]+)?)(?!<tbody([^>]+)?>)/g,"$1<tbody>").replace(/(<(?!(\/tbody))([^>]+)?>)(<\/table([^>]+)?>)/g,"$1</tbody>$4");

PHP
    $html = $dom->saveHTML();
    $html = preg_replace(array('/(<table([^>]+)?>([^<>]+)?)(?!<tbody([^>]+)?>)/','/(<(?!(\/tbody))([^>]+)?>)(<\/table([^>]+)?>)/'),array('$1<tbody>','$1</tbody>$4'),$html);
    $dom->loadHTML($html);

正規表現のみ:

matches `<table>` tag with whatever else junk inside the tag and between this and the next tag if the next tag is NOT `<tbody>` also with stuff inside the tag

    /(<table([^>]+)?>([^<>]+)?)(?!<tbody([^>]+)?>)/

replace with

    $1<tbody>

the $1 referencing the captured `<table>` tag with contents.
Do the same for the closing tag like this:

    /(<(?!(\/tbody))([^>]+)?>)(<\/table([^>]+)?>)/

replace with

    $1</tbody>$4

このようにして、dom は常に<tbody>必要な場所にタグを持ちます。

于 2015-01-30T18:33:42.050 に答える