Crawler Web simultané
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
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:
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é lafor _, 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.Lors de l'extraction de l'
hrefattribut pour toutes lesabalises, 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 } }La
parseHreffonction utilise une instruction switch, sans secoursdefault. 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 }