我正在尝试在 Go 中创建一个爬虫来生成 X 个 goroutine。目前,我只生成一个 goroutine (workers=1),并且我正在使用一个通道来发送/读取它。期望的结果是产生一定数量的工作人员,这些工作人员从 chan 读取作业,并且每个作业可以在不阻塞 goroutine 的情况下将更多作业推送到 chan。
但是由于某种原因,现在 goroutine 被阻塞并且不处理递归链接。可能是什么原因?我该如何解决这个问题?
const maxDepth = 1
type job struct {
URL string
Depth int
}
type Crawler struct {
jobs chan job
workers int
db db.DB
timeout int
imagesFolderName string
visited *sync.Map
wg *sync.WaitGroup
}
func NewCrawler(db db.DB, workers int, timeout int, imagesFolderName string) *Crawler {
return &Crawler{
jobs: make(chan job),
workers: workers,
db: db,
timeout: timeout,
imagesFolderName: imagesFolderName,
visited: &sync.Map{},
wg: &sync.WaitGroup{},
}
}
func (c *Crawler) enqueue(j job) {
c.wg.Add(1)
c.jobs <- j
}
func (c *Crawler) Start(startingLinks []string) {
if err := os.MkdirAll("apps/imagecrawler/"+c.imagesFolderName, os.ModePerm); err != nil {
fmt.Printf("Error creating image directory: %v\n", err)
os.Exit(1)
}
for i := 0; i < c.workers; i++ {
go func() {
for j := range c.jobs {
c.runJob(j)
c.wg.Done()
}
}()
}
for _, link := range startingLinks {
c.enqueue(job{URL: link, Depth: 0})
}
c.wg.Wait()
close(c.jobs)
}
func (c *Crawler) runJob(j job) {
_, alreadyVisited := c.visited.LoadOrStore(j.URL, true)
if alreadyVisited {
return
}
pageCtx, pageCancel := context.WithTimeout(context.Background(), time.Minute*time.Duration(c.timeout))
fmt.Println("Crawling URL", j.URL, "Depth:", j.Depth)
resp, err := http.Get(j.URL)
if err != nil {
fmt.Println("Fetching error:", err)
resp.Body.Close()
pageCancel()
return
}
b, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Reading body: ", err)
resp.Body.Close()
pageCancel()
return
}
resp.Body.Close()
if j.Depth >= maxDepth {
pageCancel()
return
}
links, err := extracter.ExtractLinks(pageCtx, b, j.URL)
if err != nil {
fmt.Println("ExtractLinks Err", err)
pageCancel()
return
}
for _, link := range links {
c.enqueue(job{URL: link, Depth: j.Depth + 1})
}
pageCancel()
}
解决方案实际上是将递归链接放入一个单独的 goroutine 中,这部分解决了问题。尽管如此,这还是创建/运行了更多 goroutine。我希望在整个爬行过程中只有 X 个创建并运行 goroutine。有替代解决方案吗?
func (c *Crawler) runJob(j job) {
// ...Same stuff
for _, link := range links {
c.wg.Add(1)
--> go func(link string) {
c.jobs <- job{URL: link, Depth: j.Depth + 1}
}(link)
}
pageCancel()
}