Skip to content

Instantly share code, notes, and snippets.

@GeertJohan
Created December 19, 2016 21:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GeertJohan/8ee050e7ae541cf40cb3e111c281b8b0 to your computer and use it in GitHub Desktop.
Save GeertJohan/8ee050e7ae541cf40cb3e111c281b8b0 to your computer and use it in GitHub Desktop.
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