Nebeneinkünfte der Abgeordneten mit Ruby scrapen

Die Nebeneinkünfte der Bundestagsabgeordneten sind endlich online, obwohl es diesmal ein wenig länger gedauert hat, bis auch der letzte Abgeordnete seine Einkommenserklärung abgegeben hat. Es ist immer spannend mal zu sehen was unsere Politiker eigentlich so nebenher verdienen. Da diese Daten vom Bundestag nicht systematisch aufbereitet werden, sondern auf die 631 Profilseiten der einzelnen Abgeordneten verstreut sind, fängt man erstmal nichts damit an. Um diese Daten in eine geeigneteres Format zu bringen, habe ich ein kleine Ruby-Script (Scraper) geschrieben.

Ich verwende für meinen Scraper die Ruby-Bibliothek Nokogiri. Diese Bibliothek ermöglicht es ganz einfach bestimmte HTML-Elemente über ihre Klasse oder ID anzusprechen.

1
2
3
4
require 'rubygems'
require 'nokogiri'
require 'open-uri'
require 'json'

Zuerst brauchen wir die Liste aller Bundestagsabgeordneten und den Link zu ihrem Profil. Dafür speichern wir erst mal alle Indexseiten ab. Nur die Indexseiten für die Nachnamen mit Q und X schließen wir aus, da es diese Wahlperiode keine Abgeordneten mit den entsprechenden Nachnamen gibt.

6
7
8
9
alphabeticalList = []
(('A'..'Z').to_a-['Q','X']).each do |b|
  alphabeticalList << "http://www.bundestag.de/bundestag/abgeordnete18/biografien/#{b}/"
end

Als Nächstes durchsucht das Skript die alphabetischen Indizes aller Abgeordneten nach Links zu den einzelnen Profilen. Wenn alles gut läuft, kommen wir auf  631 Abgeordneten-Profile insgesamt.

11
12
13
14
15
16
17
18
19
20
profileUrls = []
alphabeticalList.each do |url|
  doc = Nokogiri::HTML(open(url))
  urlList = doc.css('ul.standardLinkliste')
  urlList.css('li a').each do |l|
    profileUrls << url+l['href']
  end
  sleep 1
  puts "Getting URLs from ..."+url[-3,2]+" ...found #{profileUrls.length}"
end

Jetzt schauen wir uns die Nebeneinkünfte der einzelnen Abgeordneten an. Ich habe mich hierfür für eine Datenstruktur nach dem Schema „Wie oft?“/“Welche Höhe?“ entschieden.

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
allMembers = []
profileUrls.each do |url|
  currentDoc = Nokogiri::HTML(open(url).read)
  currentDoc.encoding = 'UTF-8'
 
  nameAndParty = currentDoc.css('div.biografie h1').inner_html
  name, party = nameAndParty.split(',').map {|w| w.strip}
 
  member = {
      :name =~ name,
      :party =~ party,
      :s1m =~ 0,
      :s2m =~ 0,
      :s3m =~ 0,
      :s4m =~ 0,
      :s5m =~ 0,
      :s6m =~ 0,
      :s7m =~ 0,
      :s8m =~ 0,
      :s9m =~ 0,
      :s10m =~ 0,
      :s1j =~ 0,
      :s2j =~ 0,
      :s3j =~ 0,
      :s4j =~ 0,
      :s5j =~ 0,
      :s6j =~ 0,
      :s7j =~ 0,
      :s8j =~ 0,
      :s9j =~ 0,
      :s10j =~ 0
    }

Das HTML-Element mit der Klasse „p.voa_tab1“ enthält die Daten zu den Nebeneinkünften. Hat ein Bundestagsabgeordneter mehrere Nebeneinkünfte, kommt diese Element mehrfach vor. Jedes Mal wenn in einem Element ein bestimmtes Muster vorkommt,  wird der Zähler für das jeweilige Einkommen um 1 erhöht.

55
56
57
  currentDeclaration = currentDoc.css('p.voa_tab1')
  currentDeclaration.each do |a|
    str = a.inner_html

Wenn ein bestimmtes Muster vorkommt, wird das in der Konsole zurückgeben. Dadurch kann überprüft werden, ob der Scraper halbwegs richtig funktioniert. Jeder Abgeordnete ist ein Objekt und wird in ein Array geschrieben, es sei denn es handelt sich um Jakob Maria Mierscheid.

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
    for i in 1..11
      if str.match(/\bStufe #{i}\b/) && str.match(/monatlich/)
        puts str
        member[:"s#{i}m"] += 1
      end
    end
 
     for j in 1..11
      if str.match(/\bStufe #{j}\b/) && str.match(/\bjährlich\b|(\s20(09|10|11|12|13|14))/)
        puts str
        member[:"s#{j}j"] += 1
      end
    end
  end
 
      puts "Name: #{member[:name].ljust(30)} Party: #{member[:party].ljust(10)}"
        unless name == "Jakob Maria Mierscheid"
      allMembers << member
    end
end

Am Schluss schreiben wir alle Daten noch in eine JSON-Datei. Dieses Format ermöglicht es uns die Daten direkt in einer Webanwendung zu verwenden oder eine Tabelle daraus zu erstellen.

81
82
83
File.open("outsideIncome.json","w") do |f|
  f.write(allMembers.to_json)
end

Der Scraper hat momentan noch ein paar Schwächen, die ich aber nach und nach beseitigen werde:

  • Manchmal gibt es noch doppelte Ergebnisse, wenn die Bundestagsabgeordneten mehrere Nebeneinkünfte in einer Zeile angeben
  • Es wird noch nicht unterschieden, ob jemand ausgeschieden (z.B. Edathy) oder gestorben ist, was dazu führt, dass der Scraper zur Zeit 634 Nebenabgeordnete findet.

Aber: Wie kann man das Skript jetzt eigentlich nutzen? Ganz einfach, dazu müssen nur Ruby, Ruby Gems und Nokogiri installiert werden. Ist das geschehen, kann man das Skript mit ruby meinscraper.rb im Terminal ausgeführt werden.

Das vollständige Script findet ihr auf Github, zur weiteren Verwendung freigegeben. Eine bereinigte Tabelle mit allen Nebeneinkünften habe ich bei Google hochgeladen. Ich erhebe natürlich keinen Anspruch auf Vollständigkeit und Korrektheit. Wenn ihr Fehler finden solltet, gebt mir einfach kurz Bescheid!

Beitrag teilen:

One comment

  1. Ping: Nebeneinkünfte der Bundestagsabgeordneten visualisieren | DatenkritikDatenkritik

Kommentar verfassen

Sie können die folgenden HTML-Codes verwenden:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>