Gleichzeitiger Webcrawler

Sep 18 2020

Beschreibung

Als Übung zum Erlernen von Parallelitätsmustern habe ich beschlossen, auch einen gleichzeitigen Webcrawler zu erstellen.

Ich habe das Argparse-Modul verwendet, das ich vor einiger Zeit zur Überprüfung bereitgestellt habe .

Ich suche nach Feedback zu meinem Parallelitätsmuster, aber jeder Aspekt des Codes kann geflammt werden :)

Code

package main

import (
    "fmt"
    "sync"
    "net/http"
    "io"
    "golang.org/x/net/html"
    "strings"
    "sort"
    "argparse"
)

func min(vars ...int) int {
    m := vars[0]
    for i := 1; i < len(vars); i++ {
        if vars[i] < m {
            m = vars[i]
        }
    }
    return m
}

type Crawler struct {
    base string
    pop chan []string
    push chan string
    wg *sync.WaitGroup
    visited map[string]bool
    hrefs []string
    queue []string
    maxChannels int
}

func newCrawler(base string, maxChannels int) Crawler {
    c := Crawler {
        base: base,
        maxChannels: maxChannels,
        pop: make(chan []string, maxChannels),
        push: make(chan string, maxChannels),
        wg: new(sync.WaitGroup),
        visited: make(map[string]bool),
        queue: make([]string, 1),
    }

    c.queue[0] = base
    c.visited[base] = true

    return c
}

func (c *Crawler) run() []string {
    defer func() {
        c.wg.Wait()
    }()

    for len(c.queue) > 0 {
        l := min(len(c.queue), c.maxChannels)
        
        for i := 0; i < l; i++ {
            url := c.queue[0]
            c.queue = c.queue[1:]
            c.hrefs = append(c.hrefs, url)
            c.runWorker(url)
            c.push <- url
        }

        for i := 0; i < l; i++ {
            hrefs := <- c.pop
            c.filterHrefs(hrefs)
        }
    }
    return c.hrefs
}

func (c *Crawler) filterHrefs(hrefs []string) {
    for _, href := range hrefs {
        if _, f := c.visited[href]; !f && strings.Contains(href, c.base) {
            c.visited[href] = true
            c.queue = append(c.queue, href)
        }
    }
} 

func (c *Crawler) runWorker(url string) {
    w := Worker {
        base: c.base,
        push: c.pop,
        pop: c.push,
        wg: c.wg,
    }
    c.wg.Add(1)
    go w.run()
}

type  Worker struct {
    base string
    push chan []string
    pop chan string
    wg *sync.WaitGroup
}

func (w *Worker) parseHref(href string) string {
    var url string
    switch {
        case strings.HasPrefix(href, "/"):
            url = w.base + href
        case strings.HasPrefix(href, "http"):
            url = href
    }
    return url
}

func (w *Worker) getAllHrefs(body io.Reader) []string {
    hrefs := make([]string, 0)
    page := html.NewTokenizer(body)
    for page.Next() != html.ErrorToken {
        token := page.Token()
        if token.Data == "a" {
            for _, a := range token.Attr {
                if a.Key == "href" {
                    hrefs = append(hrefs, w.parseHref(a.Val))
                }
            }
        }
    }
    return hrefs
}

func (w *Worker) fetch(url string) (io.Reader, error) {
    resp, err := http.Get(url)
    if err != nil {
        return nil, err
    }
    return resp.Body, nil
}

func(w *Worker) run() {
    defer func() {
        w.wg.Done()
    }()

    url := <- w.pop
    hrefs := make([]string, 0)
    body, err := w.fetch(url)
    if err == nil {
        hrefs = w.getAllHrefs(body)
    }
    w.push <- hrefs
}

func parseArguments() map[string]interface{} {
    parser := argparse.Argparse {
        Description: "Site crawler by @Ludisposed",
    }

    parser.AddArgument(
        argparse.Argument {
            ShortFlag: "b", LongFlag: "base", Type: "string", 
            Required: true, Help: "The base of the url",
        },
    )

    parser.AddArgument(
        argparse.Argument {
            ShortFlag: "m", LongFlag: "max", Type: 10, 
            Help: "Max amount of channels", Default: 10,
        },
    )

    return parser.Parse()
}

func main() {
    args := parseArguments()

    crawler := newCrawler(
        args["base"].(string), 
        args["max"].(int),
    )
    hrefs := crawler.run()

    sort.Strings(hrefs) // Sorting because pretty
    for _, h := range hrefs {
        fmt.Println(h)
    }
    fmt.Println("\n[+] Total unique urls found:", len(hrefs))   
}

Antworten

3 hjpotter92 Oct 06 2020 at 17:25

Haftungsausschluss: Ich hatte nicht viel Kontakt mit Golang. Ich versuche hauptsächlich, die Sprache durch zufällige Projekte zu lernen.

Wenn Sie den von Ihnen angegebenen Code durchgehen, scheint er leicht zu befolgen zu sein. Ein paar Hinweise (Fragen? Bedenken?), Die möglicherweise auf mein mangelndes Wissen zurückzuführen sind:

  1. Ihre min-Funktion verwendet eine for-Schleife, in der die bedingte Anweisung len(vars)jede Iteration aufruft . Dies scheint ineffizient. Später in Ihrem Code haben Sie die Stilsyntax verwendet for _, value := range iterable. Ich würde das auch hier vorziehen; da wir nur am Wert interessiert sind und nicht am Index.

  2. Wenn Sie das hrefAttribut für alle aTags extrahieren , iterieren Sie auch dann über Attribute, wenn Sie href erfolgreich erfasst haben. Früh brechen?

     for _, a := range token.Attr {
         if a.Key == "href" {
             hrefs = append(hrefs, w.parseHref(a.Val))
             break
         }
     }
    
  3. Die parseHrefFunktion verwendet eine switch-Anweisung ohne Fallback default. Es sollte ein Fehler zurückgegeben werden, wenn der angegebene Wert keinen dieser Werte erfüllt oder wenn Sie vorhaben, denselben Wert zurückzugeben, dann scheint ein Switch-Case-Block überwältigend.

     func (w *Worker) parseHref(href string) string {
         url = href
         if strings.HasPrefix(href, "/") {
             url = w.base + href
         }
         return url
     }