Crawler Web simultané

Sep 18 2020

La description

En tant qu'exercice d'apprentissage des modèles de concurrence d'accès, j'ai également décidé de créer un robot d'exploration Web simultané.

J'ai utilisé le module argparse que j'ai mis en revue il y a quelque temps.

Je cherche des commentaires sur mon modèle de concurrence, mais tous les aspects du code sont ouverts à être flambés :)

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))   
}

Réponses

3 hjpotter92 Oct 06 2020 at 17:25

Avertissement: je n'ai pas eu beaucoup d'exposition au golang. J'essaie surtout de saisir la langue en passant par des projets aléatoires.

En passant en revue le code que vous avez fourni, il semble facile à suivre. Quelques conseils (questions? Préoccupations?), Qui pourraient être dus à mon manque de connaissances:

  1. Votre fonction min utilise une boucle for, où l'instruction conditionnelle appelle len(vars)à chaque itération. Cela semble inefficace. Plus tard dans votre code, vous avez utilisé la for _, value := range iterablesyntaxe de style. Je préférerais cela ici aussi; puisque nous ne nous intéressons qu'à la valeur et non à l'index.

  2. Lors de l'extraction de l' hrefattribut pour toutes les abalises, vous continuez à parcourir les attributs même si vous avez réussi à capturer href. Pause tôt?

     for _, a := range token.Attr {
         if a.Key == "href" {
             hrefs = append(hrefs, w.parseHref(a.Val))
             break
         }
     }
    
  3. La parseHreffonction utilise une instruction switch, sans secours default. Il devrait renvoyer une erreur si la valeur fournie ne satisfait ni l'un ni l'autre, ou si vous prévoyez de renvoyer la même valeur, alors un bloc de commutation semble écrasant.

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