Abstimmungsverhalten im Grossen Rat anhand von 1502 PDF-Dokumenten

cssnqtvweaahcls

Der Basler Wahlkampf 2016 ist geprägt vom Bürgerlichen Schulterschluss mit der SVP. Die Bürgerlichen betonen, dass die Basler SVP nichts mit den nationalen Entgleisungen ihrer Mutterpartei zu tun habe – die Linken behaupten das Gegenteil. Unter dem Titel „Fakten statt Wahlversprechen“ habe ich am 19. September eine datenjournalistische Analyse des Wahlverhaltens der Parteien im Grossen Rat seit 2013 publiziert. Dafür habe ich die 1502 Abstimmungen der laufenden Legislatur analysiert. Als Gedankenstütze für mich, als Inspiration für Kollegen sowie für Transparenz gegenüber Lesern und Politikern veröffentliche ich hier die Anleitung:

Wie alle meine datenjournalistischen Projekte in letzter Zeit habe ich den Website-Scraper mit PHP als WordPress-Template erstellt. Das erleichtert den Umgang mit Datenbanken enorm, zudem können kleinere Anpassungen via Web-Backend von WordPress gemacht werden, was praktisch ist, wenn am Büro-Computer keine Programme installiert werden können.

Die Rohdaten liegen auf dem Server von bs.ch, geordnet nach Amtsjahr. Mit einem Loop (Jahr+1) und den beiden PHP-Libraries scraperwiki.php und simple_html_dom.php habe ich die URLS der einzelnen PDF-Dateien (hier ein Beispiel) erzeugt. Eine grosse Hilfe beim Umwandeln der PDF-Dateien in Text war der Smalot PDF-Parser. Den ganzen Text-Inhalt der PDFs habe ich danach in eine MySQL-Datei abgespeichert; das brachte viel Zeitersparnis, denn während das Herunterladen und Umwandeln der PDFs rund fünf Sekunden pro Dokument und entsprechend für alle Dokumente mehr als eine Stunde dauert, geschieht die Analyse aus der MySQL-Datenbank in Sekundenbruchteilen, auch wenn darin der ganze Text-Inhalt gespeichert ist.

Der Quellcode fürs Einlesen der PDF-Inhalte in die Datenbank lautet:

/*Links zu pdf-Dateien in Datenbank

require_once($_SERVER['DOCUMENT_ROOT'].'/wp-content/themes/twentysixteen/inc/scraperwiki.php');
    require_once($_SERVER['DOCUMENT_ROOT'].'/wp-content/themes/twentysixteen/inc/simple_html_dom.php');

set_time_limit(30000);

//dieser teil geht die website grosserrat.bs.ch durch und genertiert einträge in die datenbank gr_import
$jahr = 2013;
while($jahr<2017){//muss 2017 sein!
    $jahrplus1 = $jahr+1;
    $url = "http://abstimmungen.grosserrat-basel.ch/index_archiv2_v2.php?path=archiv/Amtsjahr_".$jahr."-".$jahrplus1;
    $jahr++;

    $html_content = scraperwiki::scrape($url);
    $html = str_get_html($html_content);

    $i = 0;

    foreach ($html->find("a") as $el) {
        if($i > 1){
            $suburl = "http://abstimmungen.grosserrat-basel.ch/".$el->href;
            $subhtml_content = scraperwiki::scrape($suburl);
            $subhtml = str_get_html($subhtml_content);

            $subi = 0;
            foreach($subhtml->find("a") as $subel){
                if($subi > 3    ){

                    //pdf holen und als text in die db
                    include 'autoload.php';

                    // Parse pdf file and build necessary objects.
                    $parser = new \Smalot\PdfParser\Parser();
                    $pdf    = $parser->parseFile("http://abstimmungen.grosserrat-basel.ch/".$subel->href);

                    $text = $pdf->getText();

                    $wpdb->insert(
                        'gr_import',
                        array(
                            'url' => "http://abstimmungen.grosserrat-basel.ch/".$subel->href,
                            'content' => $text
                        ),
                        array(
                            '%s',
                            '%s'
                        )
                    );
                    $durchzaehler++;
                    echo $durchzaehler."<br/>";

                }
                $subi++;

            }
        }
        $i++;

        //echo $suburl."<br>";
    }
}

*/

Für die Analyse der einzelnen PDFs habe ich, kurz zusammengefasst, zwei Durchgänge gemacht. Beim ersten Durchgang habe ich anhand der einzelnen Grossräte das Ergebnis der einzelnen Fraktionen gezählt. Dies wäre zwar auch im PDF erfasst, jedoch schwieriger auslesbar als die einzelnen Stimmen der Grossräte. Zudem lässt sich mit der gewählten Methode auch das Abstimmungsverhalten der einzelnen Grossräte (oder weitere Verhaltensweisen wie das Abwesenheitsverhalten) eruieren. Die Resultete der Fraktionen habe ich in einem mehrdimensionalen Array gespeichert (php gibt zwar einen Fehler aus, lässt dies aber zu – für mich eine super Hilfe beim Analysieren solcher Daten)

$parteien_hilfsarray["N"][$partei]++;

Danach als Doppel-Loop analysiert, welche Fraktionen einander widersprochen haben:

foreach($fraktionen as $fraktion2){
    //doppel-loop für matrix
    //=wenn die aktuelle partei (erster loop) "N" gesagt hat
    if ($parteien_hilfsarray["N"][$fraktion] > $parteien_hilfsarray["J"][$fraktion]) {
        //=wenn die zweite-loop-partei "J" gesagt hat

        if ($parteien_hilfsarray["J"][$fraktion2] > $parteien_hilfsarray["N"][$fraktion2]) {
            //dann "umstritten" zwischen diesen zwei parteien Nein - Ja
            $parteienmatrix[$fraktion][$fraktion2]++;
        }
    }
}

Und dies schliesslich ausgegeben:

echo "<table border=1><tr><td>Uneinigkeit</td>";
foreach($fraktionen as $fraktion){
    echo "<td>".$fraktion."</td>";
}
echo "</tr>";
foreach($fraktionen as $fraktion){
    echo "<tr><td>".$fraktion."</td>";
    foreach($fraktionen as $fraktion2){
            $parteienabweichung = $parteienmatrix[$fraktion][$fraktion2]+$parteienmatrix[$fraktion2][$fraktion];
        echo "<td>".$parteienabweichung."</td>";
    }
    echo "</tr>";

}
echo "</table>";

Was folgende Ausgabe ergibt:

Uneinigkeit CVP/EVP SP LDP FDP SVP GB GLP
CVP/EVP 0 450 200 207 338 504 210
SP 450 0 548 573 712 144 384
LDP 200 548 0 139 251 617 277
FDP 207 573 139 0 234 618 266
SVP 338 712 251 234 0 745 385
GB 504 144 617 618 745 0 417
GLP 210 384 277 266 385 417 0

aufgearbeitet für Online:

und für Print:
cssnqtvweaahcls

Autor: Samuel Hufschmid

Jounalist bei bz Basel, Papi, Organisator Swiss Kubb Open, mit Interesse an Datenjournalismus.

Kommentar verfassen