Crawler web simultaneo
Descrizione
Come esercizio di apprendimento dei modelli di concorrenza, ho deciso di creare anche un web crawler simultaneo.
Ho fatto uso del modulo argparse che ho messo in revisione qualche tempo fa.
Sto cercando feedback sul mio modello di concorrenza, ma tutti gli aspetti del codice sono aperti per essere fiammati :)
Codice
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))
}
Risposte
Disclaimer: non ho avuto molta esposizione al golang. Sto principalmente cercando di imparare la lingua passando attraverso progetti casuali.
Andando oltre il codice che hai fornito, sembra essere facilmente seguito. Alcuni suggerimenti (domande? Dubbi?), Che potrebbero essere dovuti alla mia mancanza di conoscenza:
La funzione min utilizza un ciclo for, in cui l'istruzione condizionale chiama
len(vars)ad ogni iterazione. Questo sembra inefficiente. Più avanti nel codice, hai utilizzato lafor _, value := range iterablesintassi dello stile. Preferirei anche quello qui; poiché siamo interessati solo al valore e non all'indice.Quando estrai l'
hrefattributo per tutti iatag, continui a iterare sugli attributi anche quando hai catturato con successo href. Pausa presto?for _, a := range token.Attr { if a.Key == "href" { hrefs = append(hrefs, w.parseHref(a.Val)) break } }La
parseHreffunzione utilizza un'istruzione switch, senza fallbackdefault. Dovrebbe restituire un errore se il valore fornito non soddisfa nessuno di questi, o se hai intenzione di restituire lo stesso valore, allora un blocco switch-case sembra schiacciante.func (w *Worker) parseHref(href string) string { url = href if strings.HasPrefix(href, "/") { url = w.base + href } return url }