Blog der Heimetli Software AG

Graphische Sitemap der heimetli-Domains

Anstatt die Sitemap durch bash-Scripts und lynx zu erstellen wurde diesmal ein go-Programm benutzt.

Die Visualisierung erfolgte wiederum durch Gephi, weil es schlicht die beste Wahl ist.

Ein positiver Nebeneffekt dieses Projektes: es gibt jetzt weniger Fehler in den Links ;-)

Parameter dieser Darstellung

Das go-Programm sammelt nur die Links und schreibt sie in ein .CSV. Gephi importiert das problemlos, aber die Labels der Knoten fehlen. Um diese zu erzeugen geht man in den Data View und kopiert die Id der Knoten in die Labels. Ohne diese Aktion steht man ziemlich ratlos vor den vielen Punkten...

Als Algorithmus für das Layout wurde "Force Atlas" gewählt, und die Knoten mit dem Page Rank eingefärbt.

Der Spider

Zur Abwechslung mal ein go-Programm:

package main

import (
   "container/list"
   "fmt"
   "golang.org/x/exp/slices"
   "golang.org/x/net/html"
   "io"
   "net/http"
   "net/url"
   "os"
   "regexp"
   "strings"
   "time"
)

var csv *os.File

func valid(value []byte) bool {
   suffixes := []string{".gif", ".jpg", ".pdf", ".java", ".c", ".cpp", ".zip", ".bin", ".wav", ".war"}

   str := string(value)

   for _, suffix := range suffixes {
      if strings.HasSuffix(str, suffix) {
         return false
      }
   }

   if strings.Contains(str, "mailto:") {
      return false
   }

   return true
}

func notInList(lst *list.List, str string) bool {
   for ptr := lst.Front(); ptr != nil; ptr = ptr.Next() {
      if ptr.Value == str {
         return false
      }
   }

   return true
}

func processPage(base string, page string, scheme *regexp.Regexp, domain *regexp.Regexp) ([]string, error) {
   result := []string{}

   path, err := url.JoinPath(base, page)

   if err != nil {
      return result, err
   }

   fmt.Println(path)

   resp, err := http.Get(path)

   if err != nil {
      return result, err
   }

   defer resp.Body.Close()

   var link string
   tokenizer := html.NewTokenizer(resp.Body)

   for {
      token := tokenizer.Next()
      switch token {
      case html.ErrorToken:
         err := tokenizer.Err()

         if err == io.EOF {
            return result, nil
         } else {
            return result, err
         }
      case html.StartTagToken:
         name, _ := tokenizer.TagName()
         if string(name) == "a" {
            for {
               key, value, more := tokenizer.TagAttr()

               if string(key) == "href" {
                  if valid(value) {
                     if scheme.Match(value) {
                        if domain.Match(value) {
                           link := string(value)
                           var err error

                           if !strings.HasSuffix(link, ".html") {
                              link, err = url.JoinPath(link, "index.html")
                           }

                           if err == nil {

                              result = append(result, link)
                              fmt.Fprintf(csv, "%s,%s\n", path, link)
                           } else {
                              fmt.Println("JoinPath", err)
                           }
                        } else {
                           fmt.Println("Ignored", string(value))
                        }
                     } else {
                        link, err = url.JoinPath(base, string(value))

                        if err == nil {
                           result = append(result, link)
                           fmt.Fprintf(csv, "%s,%s\n", path, link)
                        } else {
                           fmt.Println("JoinPath", err)
                        }
                     }
                  }
               }

               if !more {
                  break
               }
            }
         }
      }
   }
}

func processDomain(start string, scheme, domain *regexp.Regexp) {
   todo := list.New()
   todo.PushBack(start)

   done := []string{}
   links := []string{}
   var err error

   for todo.Front() != nil {
      page := todo.Remove(todo.Front()).(string)
      done = append(done, page)
      pos := strings.LastIndexByte(page, '/')
      links, err = processPage(page[0:pos], page[pos+1:], scheme, domain)

      if err != nil {
         fmt.Println(err)
      } else {

         for _, link := range links {
            if !slices.Contains(done, link) && notInList(todo, link) {
               todo.PushBack(link)
            }
         }

         time.Sleep(10 * time.Second)
      }
   }
}

func main() {
   var err error
   csv, err = os.Create("network.csv")

   if err != nil {
      fmt.Println(err)
      os.Exit(1)
   }

   defer csv.Close()

   fmt.Fprintf(csv, "Source,Target\n")

   scheme, _ := regexp.Compile("^http[s]?:")
   domain, _ := regexp.Compile("www\\.ffhs\\.ch")

   processDomain("https://www.ffhs.ch/index.html", scheme, domain)
}

Bemerkenswert ist die url.JoinPath-Methode. Die löst elegant einige knifflige Spezialfälle beim Zusammensetzen der URLs.