2017-09-24 8 views
1

私は、ウェブサイトのリンクをクロールするための単純なWebクローラーであるgoプロジェクトを作成しています。私は、ゴルーチンやチャンネルなどの同時機能を試してみたいと思います。しかし、私がそれを実行すると、それは通過しませんでした。全く何も起こっていないかのように示されていません。何が間違っているのか分かりません。誰かが私のためにそれを指摘できますか?golangの同時コンテキストでのチャネルの正しい使用の理解

チャネルロジックを削除すると、すべてのクロールされたリンクが表示されますが、リンクをバッファされたチャネルに送信してから、プログラムを終了する前にリンクを表示する必要があります。このプログラムは、プログラムで指定されている深度まで行くことができます。現在、深さが更新1.

package main 

import (
    "fmt" 
    "log" 
    "net/http" 
    "os" 
    "strings" 
    "time" 

    "golang.org/x/net/html" 
) 

// Link type to be sent over channel 
type Link struct { 
    URL string 
    ok bool 
} 

func main() { 
    if len(os.Args) != 2 { 
     fmt.Println("Usage: crawl [URL].") 
    } 

    url := os.Args[1] 
    if !strings.HasPrefix(url, "http://") { 
     url = "http://" + url 
    } 

    ch := make(chan *Link, 5) 
    crawl(url, 1, ch) 

    visited := make(map[string]bool) 

    time.Sleep(2 * time.Second) 

    for link := range ch { 
     if _, ok := visited[link.URL]; !ok { 
      visited[link.URL] = true 
     } 
    } 

    close(ch) 
    for l := range visited { 
     fmt.Println(l) 
    } 
} 

func crawl(url string, n int, ch chan *Link) { 
    if n < 1 { 
     return 
    } 
    resp, err := http.Get(url) 
    if err != nil { 
     log.Fatalf("Can not reach the site. Error = %v\n", err) 
     os.Exit(1) 
    } 

    b := resp.Body 
    defer b.Close() 

    z := html.NewTokenizer(b) 

    nextN := n - 1 
    for { 
     token := z.Next() 

     switch token { 
     case html.ErrorToken: 
      return 
     case html.StartTagToken: 
      current := z.Token() 
      if current.Data != "a" { 
       continue 
      } 
      result, ok := getHrefTag(current) 
      if !ok { 
       continue 
      } 

      hasProto := strings.HasPrefix(result, "http") 
      if hasProto { 
       go crawl(result, nextN, ch) 
       ch <- &Link{result, true} 
      } 
     } 
    } 

} 

func getHrefTag(token html.Token) (result string, ok bool) { 
    for _, a := range token.Attr { 
     if a.Key == "href" { 
      result = a.Val 
      ok = true 
      break 
     } 
    } 
    return 
} 

です:私は、しかし、私はまだそのURLをクロールしないようにする方法がわからない、データ競合を削除するようにコードを変更すること考え出しいくつかのあいた後

私は別の質問をしなければならないかもしれません:

package main 

import (
    "fmt" 
    "log" 
    "net/http" 
    "os" 
    "strings" 

    "golang.org/x/net/html" 
) 

func main() { 
    if len(os.Args) != 2 { 
     fmt.Println("Usage: crawl [URL].") 
    } 

    url := os.Args[1] 
    if !strings.HasPrefix(url, "http://") { 
     url = "http://" + url 
    } 

    for link := range newCrawl(url, 1) { 
     fmt.Println(link) 
    } 
} 

func newCrawl(url string, num int) chan string { 
    ch := make(chan string, 20) 

    go func() { 
     crawl(url, 1, ch) 
     close(ch) 
    }() 

    return ch 
} 

func crawl(url string, n int, ch chan string) { 
    if n < 1 { 
     return 
    } 
    resp, err := http.Get(url) 
    if err != nil { 
     log.Fatalf("Can not reach the site. Error = %v\n", err) 
     os.Exit(1) 
    } 

    b := resp.Body 
    defer b.Close() 

    z := html.NewTokenizer(b) 

    nextN := n - 1 
    for { 
     token := z.Next() 

     switch token { 
     case html.ErrorToken: 
      return 
     case html.StartTagToken: 
      current := z.Token() 
      if current.Data != "a" { 
       continue 
      } 
      result, ok := getHrefTag(current) 
      if !ok { 
       continue 
      } 

      hasProto := strings.HasPrefix(result, "http") 
      if hasProto { 
       done := make(chan struct{}) 
       go func() { 
        crawl(result, nextN, ch) 
        close(done) 
       }() 
       <-done 
       ch <- result 
      } 
     } 
    } 
} 

func getHrefTag(token html.Token) (result string, ok bool) { 
    for _, a := range token.Attr { 
     if a.Key == "href" { 
      result = a.Val 
      ok = true 
      break 
     } 
    } 
    return 
} 
+0

メインゴルーチンが(crawl' 'への呼び出しで)チャンネルに送り、後でチャンネルから受け取ります。 5つ以上のリンクが送信された場合、プログラムはデッドロックします。 –

+0

@CeriseLimónありがとう。私は150に変更しようとしましたが、まだデッドロックです。助言がありますか?私は普通のウェブサイトがホームページに150以上のリンクを持っているとは思わない。 – newguy

+0

mainから 'go craw(url、1、ch)'を呼び出すことでこの問題を修正しました。次の問題は 'ch'の' main'ブロックです。メインがリンクを印刷し続けるために何かが 'ch'を閉じる必要があります。 –

答えて

0

私はgoroutinesの再帰呼び出しは良い考えではないと思います。単にそれを暴走..私はこのような、よりフラットなモデルを好むことができます。

package main 

import (
    "fmt" 
    "log" 
    "net/http" 
    "os" 
    "strings" 
    "sync" 

    "golang.org/x/net/html" 
) 

func main() { 

    if len(os.Args) != 2 { 
     fmt.Println("Usage: crawl [URL].") 
    } 

    url := os.Args[1] 
    if !strings.HasPrefix(url, "http://") { 
     url = "http://" + url 
    } 

    wg := NewWorkGroup(1) 
    wg.Crawl(url) 
    for k, v := range wg.urlMap { 
     fmt.Printf("%s: %d\n", k, v) 
    } 
} 

// represents single link and its deph 
type Link struct { 
    url string 
    deph uint32 
} 

// wraps all around to group 
type WorkGroup struct { 
    *sync.WaitGroup 
    maxDeph uint32 
    numW int 
    pool chan *Worker 
    linkQ chan Link 
    urlMap map[string]uint32 
} 

type Worker struct { 
    result chan []Link 
} 

func newWorker() *Worker { 
    return &Worker{ 
     result: make(chan []Link), 
    } 
} 

func NewWorkGroup(maxDeph uint32) *WorkGroup { 
    numW := int(maxDeph) 
    if maxDeph > 10 { 
     numW = 10 
    } 
    return &WorkGroup{ 
     WaitGroup: new(sync.WaitGroup), 
     maxDeph: maxDeph, 
     numW:  numW, 
     pool:  make(chan *Worker, numW), 
     linkQ:  make(chan Link, 100), 
     urlMap: make(map[string]uint32), 
    } 
} 

// dispatch workers -> filter visited -> send not visited to channel 
// pool + dispatcher keep order so workers go level by level 
func (wg *WorkGroup) spawnDispatcher() { 
    wg.Add(1) 
    go func() { 
     defer wg.Done() 
     defer close(wg.linkQ) 

     for w := range wg.pool { 
      links := <-w.result 
      for i := 0; i < len(links); i++ { 
       if _, ok := wg.urlMap[links[i].url]; !ok { 
        wg.urlMap[links[i].url] = links[i].deph 

        // dont process links that reach max deph 
        if links[i].deph < wg.maxDeph { 
         select { 
         case wg.linkQ <- links[i]: 
          // goes well 
          continue 
         default: 
          // channel is too short, protecting possible deadlock 
         } 
         // drop rest of links 
         break 
        } 
       } 
      } 
      // empty link channel + nothing in process = end 
      if len(wg.linkQ) == 0 && len(wg.pool) == 0 { 
       return 
      } 
     } 
    }() 
} 

//initialize goroutines and crawl url 
func (wg *WorkGroup) Crawl(url string) { 
    defer close(wg.pool) 
    wg.spawnCrawlers() 
    wg.spawnDispatcher() 
    wg.linkQ <- Link{url: url, deph: 0} 
    wg.Wait() 
} 

func (wg *WorkGroup) spawnCrawlers() { 
    // custom num of workers, used maxDeph 
    for i := 0; i < wg.numW; i++ { 
     wg.newCrawler() 
    } 
} 

func (wg *WorkGroup) newCrawler() { 
    wg.Add(1) 
    go func(w *Worker) { 
     defer wg.Done() 
     defer close(w.result) 

     for link := range wg.linkQ { 
      wg.pool <- w 
      w.result <- getExternalUrls(link) 
     } 
    }(newWorker()) 
} 

// default sligtly modified crawl function 
func getExternalUrls(source Link) []Link { 
    resp, err := http.Get(source.url) 
    if err != nil { 
     log.Printf("Can not reach the site. Error = %v\n", err) 
     return nil 
    } 

    b := resp.Body 
    defer b.Close() 

    z := html.NewTokenizer(b) 

    links := []Link{} 

    for { 
     token := z.Next() 

     switch token { 
     case html.ErrorToken: 
      return links 
     case html.StartTagToken: 
      current := z.Token() 
      if current.Data != "a" { 
       continue 
      } 
      url, ok := getHrefTag(current) 
      if ok && strings.HasPrefix(url, "http") { 
       links = append(links, Link{url: url, deph: source.deph + 1}) 
      } 
     } 
    } 
    return links 
} 

//default function 
func getHrefTag(token html.Token) (result string, ok bool) { 
    for _, a := range token.Attr { 
     if a.Key == "href" { 
      result = a.Val 
      ok = true 
      break 
     } 
    } 
    return 
} 
+0

これは非常に興味深いようです。実際に私が書いたコードは別の人のブログ投稿の修正版でしたが、私はその主な構造を保っています。なぜあなたのやり方でやっている方が良いと思いますか?あなたはこのフラットモデルの背後に論理的根拠を与えることができますか? – newguy

+0

あなたのコード(更新版)を見ると、すべてのゴルーチンがその親をブロックしています。 1つの瞬間に1つのゴルーチンだけが動作します。 dephを999にする - 998のゴルーチンが最後のものを待つ瞬間があります。私はgoroutinesのためのusecaseではないと思う。実際に私はそれがかなり高価なアプローチになることができると感じています。 – bigless

+0

フラット=メインスポーンのgoroutinesのみ、再帰はありません。 – bigless

関連する問題