Scraping Howto

Um im Internet verstreute Daten zu sammeln, bedienen wir uns einer Technik namens Scraping, aber was ist das eigentlich und wie funktioniert’s?

Im World Wide Web sind Daten zwar reichlich vorhanden und können von Menschen gelesen, strukturiert und verknüpft werden – aber in den seltensten Fällen sind sie machinenlesbar, und in den meisten Fällen sind sie ziemlich verstreut: Ein PDF-Dokument da, eine dynamisch erstellte Liste dort, Zeitungsarchive im Web, und so weiter.

Wenn ich beispielsweise Veranstaltungsadressen auf der Seite der Zitty suchen möchte, gebe ich entweder den Suchbegriff ein oder klicke mich hier durch: zitty.de/list. Wenn ich aber alle Veranstaltungsorte einer Kategorie brauche, für einen interaktiven Stadtplan, zum Beispiel, wird es mit Klicken und Kopieren schwierig.

Scraping, zu deusch “abschaben”, ist eine Technik, die Websites als “Datenbanken” im weitesten Sinne behandelt und automatisiert Daten von einer oder mehreren Seiten ausliest und in ein maschinenlesbares Format, zum Beispiel eine Datenbank oder eine Textdatei, schreibt. (Gefunden müssen wir sie übrigens schon haben.) Dafür gibt es eine Reihe von Tools, die die Programmierarbeit vereinfachen.

Eines davon ist das ScraperWiki - nicht zu verwechseln mit einem echten Wiki! – das ein Framework für den Zugriff auf Websites und die Speicherung der Daten sowie gleich eine Online-Programmierumgebung anbietet. Voraussetzung für eine kostenlose Nutzung ist, dass ScraperWiki den Quellcode und das Ergebnis eines programmierten Scrapers der Community anbietet. Wenn wir uns damit einverstanden erklären, und noch dazu eine der drei Programmiersprachen PHP, Python oder Ruby einigermaßen beherrschen, können wir uns mühseliges manuelles Kopieren der Zitty-Adressenliste sparen.

ScraperWiki bietet feine Tutorials in allen drei Sprachen zur Einführung an, hier das in Python. Das Vorgehen beim Scrapen ist mehr oder weniger das selbe wie dort beschrieben:

  1. Eine HTML-Seite aus dem Web laden.
  2. Die HTML-Seite in eine bearbeitbare XML-Struktur aufsplitten (parsen)
  3. Aus dem XML die benötigten Daten auslesen und weiter bearbeiten
  4. Daten speichern

Also müssen wir vor dem Scrapen über die Struktur der HTML-Seite, die unsere Daten enthält, Bescheid wissen. Informationen darüber bekommt man zum Beispiel über das Firefox-Plugin Firebug.

Weiter im Beispiel. Zunächst laden wir die entsprechende Seite und parsen sie:

html = scraperwiki.scrape(http://www.zitty.de/anstalt-wedding-jugendgalerie.html))
leaf = lxml.html.fromstring(html)

Eine Veranstaltungsadresse in der Zitty-Liste hat auf ihrer Seite eine Kategorie, einen Namen/Titel und eine Adresse mit Postleitzahl, Straße, Hausnummer und altem Bezirk bzw. Landkreis (wenn in Brandenburg). Die HTML-Struktur: In einem eindeutigen <dl>-Container sind unsere Informationen aufgelistet:

<dl>
    <dt>Anschrift</dt>
    <dd>
        <ul>
            <li>Anstalt Wedding - Jugendgalerie</li>
            <li>Osloer Str. 103</li>
            <li>13359 Berlin (Wedding)</li>
        </ul>
        </dd>
        <dt>Internet</dt>
        <dd>
        <dt>Kontakt</dt>
        <dd>
</dl>

Da die Reihenfolge und Anzahl der <dt>-<dd>-Paare variiert, wird das jeweilige <dd>-Tag über den Text im vorigen <dt>-Tag identifiziert, in unserem Fall “Anschrift”. Dafür erzeugen wir eine Liste mit dem Inhalt der <dt>-Tags. In einem über lxml geparsten HTML-Dokument wählt man einzelne Elemente mit .cssselect(selstring), wobei selstring ein gültiger CSS-Selector-String sein muss.

keys = []
for dt in leaf.cssselect("dt"): keys.append(dt.text_content())

Über den Index jenes Elements, das den String “Anschrift” enthält, finden wir das dazugehörige <dd>-Tag, selektieren darin wieder die <li>-Tags und weisen deren Inhalte dem data-Dictionary zu. Später können wir die ausgelesenen Daten noch um Kontakt oder Weblinks erweitern.

i = 0
        for dd in leaf.cssselect("dd"):
            if keys[i] == "Anschrift":
                addr = dd.cssselect("li")
                data.update( { "name" : addr[0].text_content(), "address" : addr[1].text_content(), "district" : addr[2].text_content() } )
 #elif weitere Daten wie Kontakt und Web hinzufügbar
            i += 1

Die Geokoordinaten der Adresse sind anderswo im HTML-Code versteckt, glücklicherweise in eindeutigen <span>-Tags mit den Klassen latitude und longitude:

<span class="latitude">52.555730</span>
<span class="longitude">13.381410</span>

Und die können wir mit Kenntnis von CSS-Selektoren einfach auslesen und gleich unser data-Dictionary updaten:

for latitude in leaf.cssselect("span.latitude"): data.update( {"latitude" : latitude.text_content().strip()} )
for longitude in leaf.cssselect("span.longitude"): data.update( {"longitude" : longitude.text_content().strip()} )

Unser erster Datensatz ist damit vollständig und kann gespeichert werden:

scraperwiki.sqlite.save(unique_keys=['name'], data=data)

Um alle Datensätze aller Galerien zu bekommen müssen wir durch alle Seiten der Auflistung und durch die Elemente jeder Seite iterieren. Also packen wir zunächst eine Schleife um die Listenelemente einer Seite. Der Link zur Infoseite jeder Adresse ist verpackt in folgender HTML-Struktur:

<th class="teaser-cell" colspan="3">
    <span class="category">Galerien</span>
    <div class="title-wrapper">
         <h3>
             <a href="anstalt-wedding-jugendgalerie.html"> Anstalt Wedding - Jugendgalerie</a>
         </h3>
    </div>
</th>

Wir suchen uns also in einer Schleife in allen <th>-Tags mit der Klasse teaser-cell (CSS-Selector: th.teaser-cell) den Inhalt des href-Attributs im <a>-Tag und erzeugen damit die URL, die wir für das Scraping der einzelnen Adressenseiten verwenden:

from lxml.cssselect import CSSSelector
linkselector = CSSSelector("a")

for th in root.cssselect("th.teaser-cell"):
    for e in linkselector(th): innerhtml = "http://www.zitty.de/" + e.get('href').strip()
    leaf = lxml.html.fromstring(scraperwiki.scrape(innerhtml))
    #leaf wird wie oben gezeigt weiter verwendet...

Um diese Schleife packen wir noch eine Schleife, in der wir auf mittlerweile bekannte Weise die einzelnen Seiten der Liste scrapen und parsen. Der gesamte Source Code findet sich hier auf Scraperwiki.

One Response

  1. Tip-Veranstaltungsadressen: Von der kopierten Printausgabe zur Datenbank | GentriMap

    [...] Artikelnavigation ← Vorherige [...]

Leave a Reply