LastBoyScout: Daten aus einer HTML- Seite Extrahieren

Hallo zusammen,

Habe nach längerer Zeit mal wieder ein Problem...

Ich möchte gerne mittels PHP preg_match Daten aus einer HTML- Seite extrahieren. Da ich bei regex immer wie das sprichwörtliche Schwein ins Uhrwerk schaue, habe ich noch nicht mal die kleinste Idee wie das Suchmuster dafür erstellt werden muss!?

<html>
<head>
<title>Tabellenwerte Extrahieren</title>
</head>
<body>
<div class="navigation"><a href="javascript:self.close();">schließen</a></div>
...
<div class="content">
<img src="http://localhost/img/Gesamtbild.gif" border="0" title="Gesamtbild" alt="Gesamtbild" />
<br />&nbsp;
<table cellpadding="3" cellspacing="0" bgcolor="#ffffff" border="0" style="background:#fff;">
<tr class="th"><th>Nummer</th><th>Bild</th><th>Typ</th><th>Bezeichnung</th></tr>
<tr class="odd"><td>1</td><td><img src="http://localhost/img/Bild1.gif" alt="A" /></td><td><strong>I</strong></td><td>Eins</td></tr>
<tr class="even"><td>2</td><td><img src="http://localhost/img/Bild2.gif" alt="B" /></td><td><strong>II</strong></td><td>Zwei</td></tr>
<tr class="odd"><td>3</td><td><img src="http://localhost/img/Bild3.gif" alt="C" /></td><td><strong>III</strong></td><td>Drei</td></tr>
</table>
</div>
...
</body>
</html>
Array
(
    [0] => http://localhost/img/Gesamtbild.gif
    [1] => Array
        (
            [Nummer] => 1
            [Bild] => http://localhost/img/Bild1.gif
            [Typ] => I
            [Bezeichnung] => Eins
        )
    [2] => Array
        (
            [Nummer] => 2
            [Bild] => http://localhost/img/Bild2.gif
            [Typ] => II
            [Bezeichnung] => Zwei
        )
    [3] => Array
        (
            [Nummer] => 3
            [Bild] => http://localhost/img/Bild3.gif
            [Typ] => III
            [Bezeichnung] => Drei
        )
)

Vermutlich müsste man als erstes mal alles bis auf <div class="content">...</div> rausschmeißen. Dann die URL des "Gesamtbild" extrahieren (wobei diese nicht unbedingt mit in das gleiche Array muss). Und zum Schluss die Tabellenzeilen identifizieren und deren Feldinhalte rausholen.

Für den ersten Punkt hab ich noch ne Idee: "/<div class="content">(.*?)</div>/" aber dann ist bei mir gähnende Leere... Hoffe Ihr könnt mir Helfen.

akzeptierte Antworten

  1. Tach,

    Ich möchte gerne mittels PHP preg_match Daten aus einer HTML- Seite extrahieren.

    das ist keine gute Idee, HTML ist nicht regulär und deshalb sind Regexp nicht der beste Weg es zu handhaben; verwende besser einen HTML/XML-Parser: z.B. http://php.net/manual/en/domdocument.loadhtml.php

    mfg
    Woodfighter

    1. das ist keine gute Idee, HTML ist nicht regulär und deshalb sind Regexp nicht der beste Weg es zu handhaben; verwende besser einen HTML/XML-Parser

      Alternativ hatte ich mir ja schon den "Simple HTML DOM Parser" angeschaut, leider ist die HTML jedoch nicht valide, hat aber zumindest immer genau die gleiche Struktur für quick and dirty.

      1. Hallo

        das ist keine gute Idee, HTML ist nicht regulär und deshalb sind Regexp nicht der beste Weg es zu handhaben; verwende besser einen HTML/XML-Parser

        Alternativ hatte ich mir ja schon den "Simple HTML DOM Parser" angeschaut, leider ist die HTML jedoch nicht valide, hat aber zumindest immer genau die gleiche Struktur für quick and dirty.

        Dass der HTML-Code nicht valide ist, sollte den von woodfighter vorgeschlagenen Parser nicht stören. Wenn der Code zudem immer gleich falsch ist, sollte dein Regelwerk, welche Elemente als Kinder bestimmter anderer Elemente zu finden sind, funktionieren.

        Zitat aus der von woodfighter verlinkten Doku-Seite: „The function parses the HTML contained in the string source. Unlike loading XML, HTML does not have to be well-formed to load.“

        Tschö, Auge

        --
        Wo wir Mängel selbst aufdecken, kann sich kein Gegner einnisten.
        Wolfgang Schneidewind *prust*
  2. Ich möchte gerne mittels PHP preg_match Daten aus einer HTML-Seite extrahieren.

    Für den ersten Punkt hab ich noch ne Idee: "/<div class="content">(.*?)</div>/" aber dann ist bei mir gähnende Leere... Hoffe Ihr könnt mir Helfen.

    Nur mal als Verdeutlichung, wie umständlich dein Vorhaben mit preg_match ist – das Ganze mit XPath:

    html = lxml.html.fromstring(htmlquellcode)
    
    gesamtbildurl = html.xpath('//div[@class="content"]/img[@title="Gesamtbild"]/@src')[0]
    
    tabelle = list()
    for spalten in (tr.xpath('td') for tr in html.xpath('//div[@class="content"]/table[1]/tr[position()>1]')):
        '''
        Doppelt verschachtelte Schleife:
        Je tr-Zeile ein Durchlauf (aus tr in html.xpath('//…tr')), wobei in spalten 
        eine Liste mit jeweiligen td-Elementen steckt (aus tr.xpath('td')).
        '''
        tabelle.append({
            "Nummer": spalten[0].text_content(), 
            "Bild": spalten[1].xpath("img/@src")[0], 
            "Typ": spalten[2].text_content(), 
            "Bezeichnung": spalten[3].text_content()
        })
    

    Ergibt in tabelle:

    [   {   'Bezeichnung': 'Eins',
            'Bild': 'http://localhost/img/Bild1.gif',
            'Nummer': '1',
            'Typ': 'I'},
        {   'Bezeichnung': 'Zwei',
            'Bild': 'http://localhost/img/Bild2.gif',
            'Nummer': '2',
            'Typ': 'II'},
        {   'Bezeichnung': 'Drei',
            'Bild': 'http://localhost/img/Bild3.gif',
            'Nummer': '3',
            'Typ': 'III'}
    ]
    

    Das ist jetzt zwar Python und lxml, aber der Punkt ist, dass du nur zweieinhalb recht einfache XPath-Aufrufe brauchst und dazu zwei Schleifen, um die Daten auszulesen. Und mit PHP geht das doch ganz bestimmt genauso einfach :>

  3. Dank woodfighter`s Denkanstoß habe ich es zumindest schon mal geschafft die Tabellenwerte herauszufiltern:

    $daten = new DOMDocument();
    $daten->loadHTML($string);
    $zeilen = $daten->getElementsByTagName('tr');
    foreach ($zeilen as $zeile) {
        $felder = $zeile->getElementsByTagName('th');
        foreach ($felder as $feld) {
            $schluessel[] = $feld->nodeValue;
        }
        $felder = $zeile->getElementsByTagName('td');
        $nr = 0;
        foreach ($felder as $feld) {
            $wert[$schluessel[$nr++]] = $feld->nodeValue;
        }
        $werte[] = $wert;
    }
    print_r($werte);
    

    Jetzt gibt es nur noch drei Probleme:

    1. Wie kann ich es auf das div mit class="content" begrenzen?
    2. Wie komme ich an die URL des Gesamtbild?
    3. Wie komme ich an die URL`s der Einzelbilder (hier liefert mein Script nur leere Felder)?
    1. Jetzt gibt es nur noch drei Probleme:

      1. Wie kann ich es auf das div mit class="content" begrenzen?
      2. Wie komme ich an die URL des Gesamtbild?
      3. Wie komme ich an die URL`s der Einzelbilder (hier liefert mein Script nur leere Felder)?

      Es gibt noch ein viertes:

      4. Was machst Du wenn der Eigentümer/Betreiber der Webseite die Struktur, z.B. durch ein anderes Template, ändert?

      4.a.) Wenn Du der Eigentümer/Betreiber sein solltest: Warum lieferst Du nicht kurzerhand die Daten so aus wie Du sie brauchst (z.B. als gültiges XML oder als JSON?)

    2. Tach,

      1. Wie kann ich es auf das div mit class="content" begrenzen?

      getAttribute

      2. Wie komme ich an die URL des Gesamtbild?

      getAttribute

      3. Wie komme ich an die URL`s der Einzelbilder (hier liefert mein Script nur leere Felder)?

      Du benutzt nodeValue, das ist der Inhalt des Textknotens dess Elements, ein img-Element hat keinen solchen; (nicht nur) um das Muster aufrecht zu erhalten, brauchst du auch hier getAttribute.

      mfg
      Woodfighter

      1. Tach,

        1. Wie kann ich es auf das div mit class="content" begrenzen?

        getAttribute

        Das liefert aber nur das Attribut selbst. Er will aber offenbar das div-Element mit dem class-Attribut class="content". Das Mittel der Wahl dafür wäre für mich eine Suche mittels XPath.

        1. Tach,

          1. Wie kann ich es auf das div mit class="content" begrenzen?

          getAttribute

          Das liefert aber nur das Attribut selbst.

          ja, man holt sich alle div-Elemente und wählt dann diejenigen mit dem passenden Attribut und -wert.

          Er will aber offenbar das div-Element mit dem class-Attribut class="content". Das Mittel der Wahl dafür wäre für mich eine Suche mittels XPath.

          Wenn er mit SimpleXML arbeiten würde, wäre das möglich, hier müsste OP auf DOMXPath zurückgreifen. Das geht, setzt dann aber voraus, dass man sich zusätzlich noch mit XPath auseinandersetzt.

          mfg
          Woodfighter

          1. Ja Genau, hab es so gemacht:

            $daten = new DOMDocument();
            $daten->loadHTML($string);
            $seite = $daten->getElementsByTagName('div');
            foreach ($seite as $inhalt) {
                if ($inhalt->getAttribute('class') == "content") {
                    $zeilen = $inhalt->getElementsByTagName('tr');
                    foreach ($zeilen as $zeile) {
                        $felder = $zeile->getElementsByTagName('th');
                        foreach ($felder as $feld) {
                            $schluessel[] = $feld->nodeValue;
                        } 
                        $felder = $zeile->getElementsByTagName('td');
                        $nr = 0;
                        foreach ($felder as $feld) {
                            $zelle = $feld->nodeValue;
                            if (empty($zelle)) {
                                $zelle = $feld->firstChild->getAttribute('src');
                            } 
                            $wert[$schluessel[$nr++]] = $zelle;
                        } 
                        $werte[] = $wert;
                    } 
                    $werte[0] = $inhalt->getElementsByTagName('img')->item(0)->getAttribute('src');
                } 
            } 
            print_r($werte);
            
            1. Tach,

              eine Lösung mit XPath könnte z.B. so aussehen:

              $xpath = new DOMXpath(DOMDocument::loadHTML ($html));
              $keys=$xpath->query("*/div[@class='content']//tr/th");
              $cells=$xpath->query("*/div[@class='content']//tr/td");
              $values[]=$xpath->query("*/div[@class='content']/img")->item(0)->getAttribute('src');
              
              $i=0;
              foreach($cells as $cell){
                  $value[$keys->item($i%$keys->length)->nodeValue]=empty($cell->nodeValue) ? $cell->firstChild->getAttribute('src') : $cell->nodeValue;
                  if($i%$keys->length==$keys->length-1){
                    $values[]=$value;
                    unset($value);   
                  }
                  $i++;
              }
              

              funktioniert (unter ähnlichen Annahmen wie LastBoyScouts Lösung), ist aber auch nicht schön.

              mfg
              Woodfighter

              1. funktioniert (unter ähnlichen Annahmen wie LastBoyScouts Lösung), ist aber auch nicht schön.

                Bleibe aber dennoch bei meiner Lösung, weil ich mich mit XPhat nicht auskenne...

                Da die id- Attribute fehlen, wird es eben nunmal leider nicht eleganter gehen!? ;-)

      2. getAttribute

        Danke (euch allen), dass was der richte Ansatz... jetzt funktioniert es!