Drei Quickies für Newton

Veröffentlicht von Ramon Voges am 07.11.2017 5 Minuten zum Lesen

Für einen Vortrag benötigte ich eine ganze Reihe von Seiten aus Isaac Newtons Principia Mathematica. Zwar gibt es von ihr mehrere Ausgaben online. Wenn überhaupt, dann lassen sich aber nur Seiten einzeln herunterladen. In diesem Beitrag zeige ich drei Möglichkeiten, wie sich mithilfe von Ruby und der Kommandozeile diese lästige Arbeit automatisieren lässt.

Problemstellung

Es geht darum, eine Reihe von einzelnen Seiten eines online zugänglichen Digitalisats herunterzuladen. Dafür schauen wir uns zunächst die Möglichkeiten an, die uns geboten werden. In unserem Beispielfall gibt es eine schönes Digitalisat in der Digitalen Bibliothek der University of Cambridge. Sie bietet zahlreiche Metadaten zum Dokument an. Ein Inhaltsverzeichnis kann angezeigt ebenso werden wie eine Vorschau der einzelnen Seiten. Darüber hinaus erklärt uns Simon Schaffer, warum Newtons Werk bedeutsam sei.

Das Problem ist nur: Es lassen sich leider nur einzelne Seiten herunterladen, nicht das ganze Werk. Wenn wir nämlich auf den Reiter ‘View more options’ klicken, können wir, nachdem wir unser Einverständnis mit der Creative Commons-Lizenz signalisiert haben, die gerade angezeigte Seite speichern. Dies geschieht, indem wir auf eine neue Seite umgeleitet werden. Die entsprechende URL lautet für das Titeblatt http://image01.cudl.lib.cam.ac.uk/content/images/PR-ADV-B-00039-00001-000-00009.jpg. Die nächste Seite hat die URL http://image01.cudl.lib.cam.ac.uk/content/images/PR-ADV-B-00039-00001-000-00010.jpg. Es lässt sich also erkennen, dass die einzelnen Bilddateien fortlaufend nummeriert sind und unter der allgemeinen Signatur “PR-ADV-B-00039-00001-000” im Verzeichnes “images” auf dem Server gespeichert sind.

Um die Seiten nicht händisch herunterzuladen, brauchen wir also ein Skript, dass die URLs um die jeweiligen Seitenzahlen ergänzt. Erschwerend kommt hinzu, dass diese Zahlen fünfstellig sein müssen und dem Format “00001” folgen. Die dreiundzwanzigste Seite hat also die Nummer “00023”.

Variante 1: URLs extrahieren

Der erste Ansatz geht auf einen Beitrag vom Programming Historian zurück. Wir zerlegen dafür zunächst die URL in drei Teile: in einen Hauptteil, die Dateiendung und die entsprechende Seitenzahl. Da Hauptteil und Endung immer gleich bleiben, legen wir sie jeweils in einer Variabel ab.

url = "http://image01.cudl.lib.cam.ac.uk/content/images/PR-ADV-B-00039-00001-000-"
ending = ".jpg"

Anschließend erstellen wir eine Datei mit dem Namen ursl.txt, in der wir die zu erstellenden URLs speichern. Dafür schaffen wir eine Reihe von beispielsweise 9 bis 45, die das Titelblatt und die grundlegenden Definitionen umfasst. Aus dieser Reihe entnehmen wir mit each jeweils eine Zahl und setzen sie in unsere URL ein, die wir obendrein in die geschaffenen Datei schreiben.

File.open('urls.txt', 'w') do |file|
  (9..45).each do |number|
    file.printf url + "%05d" + ending + "\n", number
  end
end

Für die Nummerierung greifen wir auf die Methode printf zurück. Ihr übergeben wir zunächst den Hauptteil der URL, der in der Variabel url gespeichert ist. Dann weisen wir printf mit “%05d” an, eine Zahl (d) an die Stelle (%) zu schreiben, die fünf (5) Stellen umfassen und Leerräume mit Nullen (0) auffüllen soll. Daraufhin folgen die Dateiendung, hier abgelegt in der Variabel ending, und ein Zeilenumbruch mit "\n". Das Ende der Zeile übergibt mit “, number” printf die Zahl, die wir gerade aus unserer Reihe herausgezogen haben.

Wenn wir das Programm mit ruby download.rb aufrufen, erstellt es für uns eine Text-Datei, in der alle URLs vollständig mit der korrekten Zählung aufgelistet sind. Diese Datei urls.txt können wir dann in Verbindung mit wget nutzen, einem Kommandozeilenprogramm, mit dem sich Dateien über ein Netzwerk herunterladen lassen.

wget -i urls.txt --np --limit-rate=20k

Damit weisen wir wget an, aus der Datei urls.txt die Adressen herauszuziehen, in kein Elternverzeichnis zu wechseln (-np) und mit einer Maximalgeschwindigkeit von 20KB die Dateien zu laden, um den Server nicht allzu sehr zu strapazieren.

Variante 2: Ceci n’est pas une pipe!

Diese Variante funktioniert einwandfrei. Sie ist nur etwas umständlich, weil wir zunächst eine zusätzliche Datei erstellen müssen, die wir womöglich hinterher gar nicht mehr benötigen. Um diesen Schritt zu umgehen, nutzen wir im Folgenden eine sogenannte Pipe “|”, mit der sich verschiedene Kommandozeilenprogramme hintereinander schalten lassen. Wir kürzen dafür unser Ruby-Programm, bis es nur noch diese Zeilen enthält:

url = "http://image01.cudl.lib.cam.ac.uk/content/images/PR-ADV-B-00039-00001-000-"
ending = ".jpg"

(9..45).each do |n|
  printf url + '%05d' + ending + "\n", n
end

Wir streichen also den Block, in dem wir in die Datei urls.txt geschrieben haben. Wenn wir das Programm aufrufen, schreibt uns der Ruby-Interpreter die gesuchten URLs auf den Bildschirm. Diese Eigenschaft können wir nutzen, um die Ausgabe unseres Programms mithilfe der Pipe an wget weiterzugeben. Dafür rufen wir wget wie folgt auf:

ruby download.rb | wget -np --limit-rate=20k -i -

An die Stelle einer Datei tritt dabei die mit “-“ gekennzeichnete Ausgabe unseres Programms. Kürzer und eleganter!

Variante 3: Einzeiler

Um das Ganze allerdings auf die Spitze zu treiben, ließe sich schließlich selbst das Ruby-Programm umgehen. Dafür müssten wir nur den Ruby-Interpreter von der Kommandozeile starten und ihn anweisen, einen Einzeiler an wget weiterzugeben. Das wird dann allerdings etwas unübersichtlich, weil wir auch die Variablen dafür einbauen müssen:

ruby -e '(9..45).each { |n| printf "http://image01.cudl.lib.cam.ac.uk/content/images/PR-ADV-B-00039-00001-000-%05d.jpg\n", n}' | wget -np --limit-rate=20k -i -

Fazit

Dank Ruby und seiner Flexibilität auf der Kommandozeile lassen sich gleich drei kurze und knappe Lösungen nutzen, um sich lästige Klickarbeit zu sparen. Die letzte Variante sei aber vor allem der Vollständigkeit halber aufgeführt. Für einen praktischen Einsatz scheint sie mir doch allzu fehleranfällig zu sein. Meine Lieblingslösung ist Variante 2.