Integration mit Scrapy und Tweepy

von Roland Rickborn

In diesem Posting gehe ich auf die Integration von Webinhalten mittels Scrapy und Tweepy ein. Ich zeige, wie man einen Scrapy-Spider bei Srapinghub betreibt und wie man Tweets auf Twitter postet.

Problemstellung

Eine Webseite (in diesem Fall im Internet) stellt Content zur Verfügung, der potentiell für eine breite Masse von Interesse ist. Die Usability der Webseite stellt eine hohe Hürde für viele User dar. Das beginnt beispielsweise am fehlenden Responsive Design, geht weiter über mangelnde Teilbarkeit und endet bei nicht vorhandener, moderner Funktionalität. Die Ursachen für die Mängel sind vielschichtig und reichen von nicht vorhandenem Budget bis zur falschen Wahl des verwendeten Frameworks bzw. veralteter Technologie. Ähnlich verhält es sich mit der Ablösung des Systems, die eigentlich folgerichtig wäre. Die üblichen Gründe für den Weiterbetrieb sind z.B. fehlendes Bewußtsein der Mängel, eingeschränktes KnowHow bzgl. neuer Technologien oder nicht vorhandenes Budget.

Das bestehende System wird trotz der Mängel vom Betreiber intensiv genutzt. Ziel des Projekts ist die einfache Integration der im Quellsystem zur Verfügung gestellten Inhalte in moderne Technologien, hier gezeigt anhand von Twitter. Die Vorteile der vorgestellten Lösung sind geringe Integrationskosten, cloudbasierte Lösung, die kaum Verwaltungsaufwand erfordert, bestehende Arbeitsprozesse seitens des Betreibers bleiben unverändert. Durch die Veröffentlichung auf Twitter wird der zur Verfügung gestellte Inhalt auf einfachste Weise einem breiten Publikum zugänglich gemacht. Zugleich lassen sich sämtliche Vorteile der Plattform nutzen (Teilbarkeit, Usability, Responsive Design).

Projektübersicht und Ablauf

Das Quellsystem verfügt über keinerlei Schnittstelle oder API, so dass entschieden wurde, die zur Verfügung gestellten Inhalte mit Screen Scraping Technologie auszulesen. Die Wahl fiel auf das Framework Scrapy [1], da es einfach in der Handhabung ist und es sich günstig in der Cloudlösung Scrapinghub [2] betreiben lässt.
Aus den oben genannten Gründen wurde für die Wiederveröffentlichung der erfassten Inhalte der Mikrobloggingdienst Twitter [3] gewählt. Durch die zuvor genannte Festlegung der Web Scraping Technologie (Scrapy benutzt Python) musste ein auf Python basiertes Tool gefunden werden. Hier bot sich die Verwendung von Tweepy [4] an, einer einfach zu benutzenden Python Bibliothek für die Twitter API.
Abgerundet wird die Lösung durch den Einsatz von TinyURL [5], einem Kurz-URL-Dienst, der sich sehr einfach in Python integrieren lässt. Dieser Schritt ist notwendig, um die 140-Zeichen Grenze von Twitter einzuhalten und dennoch sinnvolle Inhalte zu posten.

Der Scrapy-Spider wird nach seiner Erstellung in den Srapinghub geladen. Dort wird ein scheduled Job angelegt, der z.B. stündlich ausgeführt wird. Die durch den Spider erfassten Inhalte werden im Spider selbst anhand von Selektoren verarbeitet und in Items aufgeteilt. Alle Items werden anschließend an eine Pipeline übergeben. In der Pipeline sind weitere Bearbeitungsschritte möglich, wie z.B. Aufarbeitung von Inhalten oder Veröffentlichung als Tweet.

Im Spider wird der Request auf die angegebene URL ausgeführt und die erfassten Daten schrittweise verarbeitet. Im ersten Schritt werden die erfassten IDs mit den bereits bearbeiteten IDs verglichen. Items mit IDs, die bereits bekannt sind, werden ausgefiltert. Auf diese Weise wird Persistenz erreicht und es wird verhindert, dass bereits bearbeitete Inhalte erneut veröffentlicht werden.
Anschließend werden alle nicht gefilterten Items an die Pipeline durchgereicht, siehe Code Listing 1:

from scrapy.utils.project import data_path

class ItemsSpider(scrapy.Spider):
    name = "items"
    start_urls = (
        '<myStartUrl>',
    )

    def parse(self, response):

        mydata_path = data_path("runs.stat")
        myLastId = self.getLastRunId(mydata_path)

        responseSelector = scrapy.Selector(response)
        for sel in responseSelector.css('<myCssSelector>'):
            ...
            myScrapedId = <scrapedId>
            if myScrapedId in myLastId:
                break
            else:
                myLastId.append(myScrapedId)
            ...
            yield item

        self.setLastRunId(mydata_path, myLastId)
        
    def getLastRunId(self, myPath):
        try:
            f = open(myPath, "r")
            myLastRunId = f.read().splitlines()
            f.close()
            return myLastRunId
        except:
            return [0]
            
    def setLastRunId(self, myPath, myLastRunId):
        f = open(myPath, "w+")
        for item in myLastRunId:
            f.write("%s\n" % item)
        f.close()

Die Pipeline umfasst zwei Schritte.

  • Kurz-URL anlegen
    Für die Erzeugung der Kurz-URL bot sich TinyURL auch deswegen an, weil weder ein Account erforderlich ist, noch eine eigene Bibliothek installiert werden muss. Dies kann einfach mit den Modulen requests und jsonerledigt werden, siehe Code Listing 2:
     
import requests
import json

class UrlShortenerPipeline(object):

    def process_item(self, item, spider):
        if item['url']:
            url = 'http://tinyurl.com/api-create.php?url='+item['url']+item['id']
            payload = {'longUrl': url}
            headers = {'content-type': 'application/json'}
            r = requests.post(url, data=json.dumps(payload), headers=headers)
            item['url'] = r.text
        return item
  • Tweet posten
    Bevor der erfasste Inhalt getweetet werden kann, soll ein Hashtag hinzugefügt werden. Und natürlich muss die verkürzte URL zum eigentlichen Inhalt eingefügt werden. Schließlich muss noch kontrolliert werden, dass der gesamte Tweet nicht länger als 140 Zeichen lang ist. Sollte der Text die Grenze überschreiten, muss gekürzt werden, siehe Code Listing 3:
import tweepy

class TwitterPipeline(object):

    def process_item(self, item, spider):
        access_token = '<myAccessToken>'
        access_token_secret = '<myAccessTokenSecret>'
        consumer_key = '<myConsumerKey>'
        consumer_secret = '<myConsumerKeySecret>'
        auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
        auth.secure = True
        auth.set_access_token(access_token, access_token_secret)
        api = tweepy.API(auth)
        hashtag = '<myHashtag>'
        probe = "%s #%s %s" % (item['1'], hashtag, item['2'])
        if len(probe)>140:
            diff = len(probe)-136
            item['1'] = item['1'][0:-diff]+'...'
        msg = "%s #%s %s" % (item['1'], hashtag, item['2'])
        api.update_status(msg)
        return item

Hands on...

Zur Vorbereitung der Arbeitsumgebung gehört die Installation von Scrapy, Tweepy und weiteren Tools. Unter Linux also: 

sudo pip install Scrapy  
sudo pip install tweepy  
sudo pip install shub

shub [6] installiert den Scrapinghub Command Line Client. Der Client stellt ein praktisches Tool für die Entwicklung und das Deployment des Projekts auf Scrapinghub dar.Zuerst muss ein Projektverzeichnis angelegt werden. Mit dem Kommando: 

scrapy startproject myProject

erstellt Scrapy das komplette Projektverzeichnis inkl. aller zusätzlicher Dateien. Die Datei settings.py enthält alle Einstellungen auf Projektebene. Die Datei pipelines.py enthält die oben definierten Pipelines. Und in der Datei items.pywerden die Attribute unseres Items festgelegt.

Im nächsten Schritt wird mit dem folgenden Kommando ein Scrapy-Spider angelegt und seine Start-URL definiert:

scrapy genspider mySpider start.url

Scrapy erstellt in diesem Fall die Datei mySpider.py, mit einem Grundgerüst eines Scrapy-Spiders. Die Datei lässt sich anschließend in einem Editor bearbeiten. Für das Grundgerüst unterstützt Scrapy Templates und Profile. Mehr dazu ist in der Hilfe jedes Befehls zu finden (scrapy genspider -h).

Ist der Spider ausreichend an die eigenen Bedürfnisse angepasst, lässt er sich mit diesem Kommando starten:

scrapy crawl mySpider

Bei diesem Aufruf findet die Ausgabe mit dem DEBUG Loglevel auf stdout statt. Damit lässt sich also prima überprüfen, was der Spider im Detail macht. Dort sieht man insbesondere auch die verwendeten Umgebungsvariablen, die angewendeten Einstellungen (bestehend aus globalen Scrapy-Einstellungen, Projekteinstellungen und Spider-Einstellungen), die Attribute unserer Items und die verwendeten Pipelines.

Bevor das Projekt nach Scrapinghub geladen werden kann, müssen evtl. vorhandene Abhängigkeiten zu Fremd-Modulen berücksichtigt werden. Scrapinghub erlaubt die Möglichkeit, externe Bibliotheken zu definieren [7]. Ich habe mit dem folgenden Kommando die Abhängigkeiten in meinem Projekt ermittelt:

pip freeze > requirements.txt

Anschließend verweise ich in der Datei scrapinghub.yml auf meine soeben erstellte Datei requirements.txt, siehe Code Listing 4:

projects:
  default: <myProjectId>
requirements_file: requirements.txt

Jetzt sind alle Bedingungen erfüllt und das Projekt kann nach Scrapinghub geladen werden. Für das Deployment dient der Befehl:

shub deploy

Beim ersten Aufruf dieses Befehl wird ein Wizard gestartet, mit dessen Hilfe die benötigen Einstellungen für den Upload beu Scrapinghub vorgenommen werden. Nach erfolgtem Deployment sieht eine typische Ausgabe so aus:

Packing version 1.0
Deploying to Scrapy Cloud project "myProjectId"
{"status": "ok", "version": "1.0", "spiders": 1, "project": myProjectId}
Run your spiders at: https://app.scrapinghub.com/p/myProjectId/

Da das Deployment erfolgreich war, folgt jetzt der große Moment, in dem der Spider das erste Mal gestartet wird. Der Spider kann entweder über die Weboberfläche von Scrapinghub gestartet werden, oder wieder über den Command Line Client. Das Kommando dafür ist: 

shub schedule mySpider

Der Aufruf wird typischerweise quittiert durch die folgende Ausgabe, die Hinweise auf das Logging enthält:

Spider mySpider scheduled, job ID: myProjectId/1/1
Watch the log on the command line:
    shub log -f 1/1
or print items as they are being scraped:
    shub items -f 1/1
or watch it running in Scrapinghub's web interface:
    https://app.scrapinghub.com/p/myProjectId/job/1/1

Tipp: Persistenz zwischen Läufen eines Spiders

Häufig wird in der Scrapinghub Dokumentation und in Foren zum Thema Persistenz auf das Addon DeltaFetch [8] verwiesen. Dieses Addon ist dazu geeignet, das Erfassen von URLs zu vermeiden, die bereits früher analysiert wurden. Ist der eigene Spider also so angelegt, dass die zu erfassenden URLs aus der Start-URL gescrapt werden, dann ist DeltaFethc in der Lage zu unterscheiden, ob die eingelesenen URLs in einem vorigen Lauf schon ausgelesen wurden. In diesem Fall werden diese URLs nicht erneut bearbeitet.
Im aktuellen Projekt werden allerdings keine URLs erfasst, sondern IDs die sich in Div-Tags befinden und die keine eigene URL besitzen. Deshalb war es für die Persistenz erforderlich, eine eigene Lösung zu entwickeln. Als Basis dient das Addon DotScrapy [9], das die Funktion data_path() zur Verfügung stellt. Umgesetzt wurden die Funktionen setLastRunId() und getLastRunId(), siehe Code Listing 1.

Fazit

Mit der vorgestellten Lösung lassen sich Webinhalte von schwer zugänglichen Webseiten auf einfache, günstige und schnelle Weise einem großen Publikum zur Verfügung stellen. Die Lösung ist vollständig cloudbasiert, erfordert also keinerlei eigene Administration. Durch die Screen Scraping Technik müssen weder bestehende Prozesse, noch bestehende Systeme angepasst werden.

Kategorien: PythonResponsive Design

Zurück