main.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /*
  2. table generator for
  3. https://wiki.freifunk.net/index.php?title=Hamburg/Fl%C3%BCchtlinge#Liste_von_Unterk.C3.BCnften
  4. TODO
  5. 1. consolidate existing data (status, doku links, contact person)
  6. 2. intermediate datastorage to detect upstream changes
  7. */
  8. package main
  9. import (
  10. "flag"
  11. "fmt"
  12. "log"
  13. "os"
  14. "regexp"
  15. "strings"
  16. "sync"
  17. "text/template"
  18. "time"
  19. "github.com/PuerkitoBio/goquery"
  20. )
  21. // from: https://www.hamburg.de/fluechtlinge-unterbringung-standorte/
  22. var urls = []string{
  23. "https://www.hamburg.de/fluechtlinge-unterbringung-standorte/4372596/unterbringung-altona/",
  24. "https://www.hamburg.de/fluechtlinge-unterbringung-standorte/4373118/unterbringung-bergedorf/",
  25. "https://www.hamburg.de/fluechtlinge-unterbringung-standorte/4373120/unterbringung-eimsbuettel/",
  26. "https://www.hamburg.de/fluechtlinge-unterbringung-standorte/4373122/unterbringung-harburg/",
  27. "https://www.hamburg.de/fluechtlinge-unterbringung-standorte/4373128/unterbringung-mitte/",
  28. "https://www.hamburg.de/fluechtlinge-unterbringung-standorte/4373126/unterbringung-nord/",
  29. "https://www.hamburg.de/fluechtlinge-unterbringung-standorte/4373124/unterbringung-wandsbek/",
  30. }
  31. var re = regexp.MustCompile(`\(Stand ([0-9]+\.[0-9]+\.[0-9]+)\)`)
  32. type result struct {
  33. Bezirk, Stadtteil string
  34. Strasse, Plaetze string
  35. Wohnart string
  36. Date time.Time
  37. err error
  38. }
  39. var csvFname = flag.String("fname", "list.tbl", "filename to write to")
  40. var mediaWikiTpl = template.Must(template.New("mediaTable").Parse(`
  41. {| class="mw-datatable sortable toptextcells"
  42. ! '''Bezirk'''
  43. ! '''Stadtteil'''
  44. ! '''Straße'''
  45. ! '''Plätze'''
  46. ! '''Wohnart'''
  47. ! '''Anprechpartner*'''
  48. ! '''Doku'''
  49. ! '''Status**'''
  50. {{range .}}
  51. |-
  52. | {{.Bezirk}}
  53. | {{.Stadtteil}}
  54. | {{.Strasse}}
  55. | {{.Plaetze}}
  56. | {{.Wohnart}}
  57. | TODOKontakt
  58. | TODODoku
  59. | TODOStatus{{end}}
  60. |}
  61. `))
  62. func main() {
  63. flag.Parse()
  64. w, err := os.Create(*csvFname)
  65. check(err)
  66. var rs []result
  67. for r := range fetchAndParse(urls) {
  68. check(r.err)
  69. rs = append(rs, r)
  70. }
  71. err = mediaWikiTpl.Execute(w, rs)
  72. check(err)
  73. err = w.Close()
  74. check(err)
  75. }
  76. func fetchAndParse(urls []string) <-chan result {
  77. c := make(chan result)
  78. go func() {
  79. var wg sync.WaitGroup
  80. wg.Add(len(urls))
  81. for _, u := range urls {
  82. go func(u string) {
  83. defer wg.Done()
  84. var r result
  85. doc, err := goquery.NewDocument(u)
  86. if err != nil {
  87. r.err = err
  88. c <- r
  89. return
  90. }
  91. // strip out the Bezirk from the url
  92. var p = "unterbringung-"
  93. i := strings.LastIndex(u, p)
  94. r.Bezirk = strings.Title(u[i+len(p) : len(u)-1])
  95. introText := doc.Find("p.intro").Text()
  96. m := re.FindStringSubmatch(introText)
  97. if len(m) != 2 {
  98. r.err = fmt.Errorf("rce: did not find (Stand $date) in html document")
  99. c <- r
  100. return
  101. }
  102. r.Date, err = time.Parse("02.01.2006", m[1])
  103. if err != nil {
  104. r.err = err
  105. c <- r
  106. return
  107. }
  108. tables := doc.Find("div.richtext table")
  109. if tables.Length() != 2 {
  110. r.err = fmt.Errorf("rce: expected 2 tables on Stadtteil-page")
  111. c <- r
  112. return
  113. }
  114. t := tables.First()
  115. if t == nil {
  116. r.err = fmt.Errorf("rce: expected non-nil selection")
  117. c <- r
  118. return
  119. }
  120. rows := t.Find("tr")
  121. log.Printf("%s: Bestehende Standorte: %d (Stand: %s)", r.Bezirk, rows.Length()-1, r.Date.Format("02.01.2006"))
  122. rows.Each(func(i int, row *goquery.Selection) {
  123. if i == 0 {
  124. // skip header row
  125. return
  126. }
  127. fields := row.Find("td")
  128. r.Stadtteil = fields.Eq(0).Text()
  129. r.Strasse = fields.Eq(1).Text()
  130. r.Plaetze = fields.Eq(2).Text()
  131. r.Wohnart = fields.Eq(3).Text()
  132. c <- r
  133. })
  134. }(u)
  135. }
  136. wg.Wait()
  137. close(c)
  138. }()
  139. return c
  140. }
  141. func check(err error) {
  142. if err != nil {
  143. log.Fatal(err)
  144. }
  145. }