Created
December 19, 2016 21:52
-
-
Save GeertJohan/8ee050e7ae541cf40cb3e111c281b8b0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/csv" | |
"fmt" | |
"log" | |
"net/http" | |
"os" | |
"regexp" | |
"strconv" | |
"strings" | |
"sync" | |
"gopkg.in/xmlpath.v2" | |
) | |
type kamerlidActieType uint | |
const ( | |
kamerlidActieSchriftelijkeVragen = kamerlidActieType(iota) | |
kamerlidActieMondelingeVragen | |
kamerlidActieInterpellatieVragen | |
kamerlidActieAmendementen | |
kamerlidActieMoties | |
kamerlidActieWetsvoorstellen | |
) | |
var alleKamerlidActies = []kamerlidActieType{ | |
kamerlidActieSchriftelijkeVragen, | |
kamerlidActieMondelingeVragen, | |
kamerlidActieInterpellatieVragen, | |
kamerlidActieAmendementen, | |
kamerlidActieMoties, | |
kamerlidActieWetsvoorstellen, | |
} | |
var outputJaartallen = []string{"2016", "2015", "2014", "2013", "2012", "2011", "2010", "2009", "2008", "2007", "2006"} | |
const ( | |
urlBaseTK = `https://www.tweedekamer.nl` | |
urlAlleKamerleden = urlBaseTK + `/kamerleden/alle_kamerleden` | |
) | |
var ( | |
xpathSimpleHref = xmlpath.MustCompile(`@href`) | |
xpathAnchorPersoonlijkPagina = xmlpath.MustCompile(`//*[@id="content-reader"]/table/tbody/tr/td/div/a/@href`) | |
xpathKamerlidNaamEnPartij = xmlpath.MustCompile(`//*[@id="passport"]/dl/dd[1]`) | |
xpathKamerlidAnniciteit = xmlpath.MustCompile(`//*[@id="passport"]/dl/dd[5]`) | |
xpathKamerlidAnniciteitGeert = xmlpath.MustCompile(`//*[@id="passport"]/dl/dd[4]`) | |
xpathKamerlidActieLinks = xmlpath.MustCompile(`//*[@id="header"]/div/div[5]/div[3]/ul/li/a`) | |
xpathKamerlidItems = xmlpath.MustCompile(`//*[@id="content-main"]/ul/li`) | |
xpathKamerlidItemDate = xmlpath.MustCompile(`div[2]/p[2]`) | |
regexpNaamEnPartij = regexp.MustCompile(`^(.*)\((.*)\)$`) | |
) | |
func main() { | |
// fetch and parse html for 'alle kamerleden' page | |
respAlleKamerleden, err := http.Get(urlAlleKamerleden) | |
if err != nil { | |
log.Fatalf("error getting alle kamerleden: %v", err) | |
} | |
nodeAlleKamerleden, err := xmlpath.ParseHTML(respAlleKamerleden.Body) | |
respAlleKamerleden.Body.Close() | |
if err != nil { | |
log.Fatalf("error parsing alle kamerleden: %v", err) | |
} | |
// create iterator and iterate over anchors to detail pages for 'kamerleden' | |
iterAlleKamerledenAnchors := xpathAnchorPersoonlijkPagina.Iter(nodeAlleKamerleden) | |
var kamerleden []*kamerlid | |
var wgKamerledenFetch = sync.WaitGroup{} | |
for iterAlleKamerledenAnchors.Next() { | |
// create a new kamerlid and fetch information in a goroutine | |
k := newKamerlid(iterAlleKamerledenAnchors.Node().String()) | |
kamerleden = append(kamerleden, k) | |
wgKamerledenFetch.Add(1) | |
go func() { | |
k.fetch() | |
wgKamerledenFetch.Done() | |
}() | |
} | |
wgKamerledenFetch.Wait() | |
log.Printf("Scanned %d kamerleden", len(kamerleden)) | |
// create a new output csv file | |
outputFile, err := os.Create("output.csv") | |
if err != nil { | |
log.Fatalf("error opening output file: %v", err) | |
} | |
csvWriter := csv.NewWriter(outputFile) | |
// create and write a header in the csv | |
var headerRecord []string | |
headerRecord = append(headerRecord, `naam`) | |
headerRecord = append(headerRecord, `partij`) | |
headerRecord = append(headerRecord, `dagen`) | |
for _, jaartal := range outputJaartallen { | |
headerRecord = append(headerRecord, jaartal+" wetsvoorstellen") | |
headerRecord = append(headerRecord, "ammendementen") | |
headerRecord = append(headerRecord, "moties") | |
headerRecord = append(headerRecord, "schriftelijke vragen") | |
headerRecord = append(headerRecord, "mondelinge vragen") | |
headerRecord = append(headerRecord, "interpellatie vragen") | |
} | |
csvWriter.Write(headerRecord) | |
for _, k := range kamerleden { | |
// create new row and add basic info | |
var record []string | |
record = append(record, k.naam) | |
record = append(record, k.partij) | |
record = append(record, strconv.FormatUint(k.dagen, 10)) | |
// add counts for each year | |
for _, jaartal := range outputJaartallen { | |
record = append(record, strconv.Itoa(k.acties[kamerlidActieWetsvoorstellen][jaartal])) | |
record = append(record, strconv.Itoa(k.acties[kamerlidActieAmendementen][jaartal])) | |
record = append(record, strconv.Itoa(k.acties[kamerlidActieMoties][jaartal])) | |
record = append(record, strconv.Itoa(k.acties[kamerlidActieSchriftelijkeVragen][jaartal])) | |
record = append(record, strconv.Itoa(k.acties[kamerlidActieMondelingeVragen][jaartal])) | |
record = append(record, strconv.Itoa(k.acties[kamerlidActieInterpellatieVragen][jaartal])) | |
} | |
csvWriter.Write(record) | |
} | |
// flush and close file | |
csvWriter.Flush() | |
err = outputFile.Close() | |
if err != nil { | |
log.Fatalf("error closing output file: %v", err) | |
} | |
} | |
type kamerlid struct { | |
url string | |
naam string | |
partij string | |
dagen uint64 | |
acties map[kamerlidActieType]map[string]int // mapping jaartal to number of occurences | |
} | |
func newKamerlid(url string) *kamerlid { | |
// basic init (make-ing maps) | |
k := &kamerlid{ | |
url: url, | |
acties: make(map[kamerlidActieType]map[string]int), | |
} | |
for _, actie := range alleKamerlidActies { | |
k.acties[actie] = make(map[string]int) | |
} | |
return k | |
} | |
func (k *kamerlid) fetch() { | |
// fetch and parse 'kamerlid' detail page | |
var urlKamerlid = urlBaseTK + k.url | |
respKamerlid, err := http.Get(urlKamerlid) | |
if err != nil { | |
log.Fatalf("error getting kamerlid: %v", err) | |
} | |
nodeKamerlid, err := xmlpath.ParseHTML(respKamerlid.Body) | |
respKamerlid.Body.Close() | |
if err != nil { | |
log.Fatalf("error parsing kamerlid: %v", err) | |
} | |
// extract 'naam' and 'partij' | |
naamEnPartijText, ok := xpathKamerlidNaamEnPartij.String(nodeKamerlid) | |
if !ok { | |
log.Fatalf("no naamEnPartij with url %s", k.url) | |
} | |
naamEnPartijFields := regexpNaamEnPartij.FindStringSubmatch(naamEnPartijText) | |
if len(naamEnPartijFields) != 3 { | |
log.Fatalf("invalid naamEnPartij `%s` !?", naamEnPartijText) | |
} | |
k.naam = strings.TrimSpace(naamEnPartijFields[1]) | |
k.partij = naamEnPartijFields[2] | |
// extract 'anciënniteit' | |
var dagenText string | |
if strings.Contains(k.naam, "Wilders") { | |
dagenText, ok = xpathKamerlidAnniciteitGeert.String(nodeKamerlid) | |
} else { | |
dagenText, ok = xpathKamerlidAnniciteit.String(nodeKamerlid) | |
} | |
if !ok { | |
log.Fatalf("no dagen !?") | |
} | |
dagenText = strings.Replace(dagenText, ` dagen`, ``, -1) | |
dagenText = strings.TrimSpace(dagenText) | |
k.dagen, err = strconv.ParseUint(dagenText, 10, 64) | |
if err != nil { | |
log.Fatalf("invalid dagen for %s: `%s`", k.naam, dagenText) | |
} | |
fmt.Printf("%s (%s), %d dagen, %s\n", k.naam, k.partij, k.dagen, urlKamerlid) | |
// iterate over 'actie links' | |
iterKamerlidActieLinks := xpathKamerlidActieLinks.Iter(nodeKamerlid) | |
for iterKamerlidActieLinks.Next() { | |
kamerlidActieText := iterKamerlidActieLinks.Node().String() | |
fmt.Printf("\t %s\n", kamerlidActieText) | |
// get typed kamerlid actie | |
var kamerlidActie kamerlidActieType | |
switch kamerlidActieText { | |
case "Schriftelijke vragen": | |
kamerlidActie = kamerlidActieSchriftelijkeVragen | |
case "Mondelinge vragen": | |
kamerlidActie = kamerlidActieMondelingeVragen | |
case "Interpellatie vragen": | |
kamerlidActie = kamerlidActieInterpellatieVragen | |
case "Amendementen": | |
kamerlidActie = kamerlidActieAmendementen | |
case "Moties": | |
kamerlidActie = kamerlidActieMoties | |
case "Wetsvoorstellen": | |
kamerlidActie = kamerlidActieWetsvoorstellen | |
default: | |
log.Fatalf("unknown kamerlid actie: `%s`", kamerlidActieText) | |
} | |
// fetch and parse acties list | |
urlKamerlidActies, ok := xpathSimpleHref.String(iterKamerlidActieLinks.Node()) | |
if !ok { | |
log.Fatalf("item does not contain link?") | |
} | |
urlKamerlidActies = urlBaseTK + urlKamerlidActies | |
urlKamerlidActies = strings.Replace(urlKamerlidActies, "dpp=15", "dpp=1000000", -1) // crank up the volume, I'm not feeling like doing pagination today.. | |
respKamerlidActies, err := http.Get(urlKamerlidActies) | |
if err != nil { | |
log.Fatalf("error getting kamerlid acties: %v", err) | |
} | |
nodeKamerlidActies, err := xmlpath.ParseHTML(respKamerlidActies.Body) | |
respKamerlidActies.Body.Close() | |
if err != nil { | |
log.Fatalf("error parsing kamerlid acties: %v", err) | |
} | |
// iterate over acties and keep count per year | |
nodeKamerlidActiesItems := xpathKamerlidItems.Iter(nodeKamerlidActies) | |
for nodeKamerlidActiesItems.Next() { | |
actieDate, ok := xpathKamerlidItemDate.String(nodeKamerlidActiesItems.Node()) | |
if !ok { | |
log.Fatalf("no date!?") | |
} | |
k.acties[kamerlidActie][actieDate[6:]]++ | |
} | |
for jaartal, aantal := range k.acties[kamerlidActie] { | |
fmt.Printf("\t\t %s: %d\n", jaartal, aantal) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment