我是Go的新手。我正在尝试对我网站中的数据进行抓取,以便能够在Go应用中使用它。

我使用goroutines和sync.WaitGroup等待结果,但是我遇到了问题。如果我使用goroutines并尝试对数据集使用json.Marshal,则结构内部有空数组,即填充了goroutines内部。

如果我不使用常规来填充我的结构,那么一切都会很好。

这是我的结构:

type CategoryScrapper struct {
    Name        string                `json:"name"`
    Link        string                `json:"link"`
    Products []Product.ProductData `json:"products"`
}

type ProductData struct {
    Name        string `json:"name"`
    Link        string `json:"link"`
    Thumbnail   string `json:"thumbnail"`
    OriginPrice string `json:"OriginPrice"`
    Excerpt     string `json:"Excerpt"`
}

这是我的应用程序的一部分
func main() {
    cs := Category.CategoryScrapper{
        Name: "Name",
        Link: "/link",
    }

    wg := new(sync.WaitGroup)
    go cs.GetProducts(wg)
    wg.Wait()

    res, _ := json.Marshal(cs)

    fmt.Println(string(res))
}

func (s *CategoryScrapper) GetProducts(pool *sync.WaitGroup) {
    pool.Add(1)
    defer pool.Done()

    maxPageNum := s.getMaxPageNum()
    localPool := new(sync.WaitGroup)

    s.Products = make([]Product.ProductData, 0)

    for i := 1; i <= maxPageNum; i++ {
        go s.getPage(i, localPool)
    }

    localPool.Wait()
}

func (s *CategoryScrapper) getPage(page int, waitingPool *sync.WaitGroup) {
    product := Product.ProductData{
        Name: "Name",
        Link: "Link",
        Thumbnail: "Thumb",
        OriginPrice: "1111",
        Excerpt: "Excerpt",
    }

    s.Products = append(s.Products, product)
}

最佳答案

这里使用的抽象对我而言似乎不一致。我只是将对GetProducts的基本调用设为阻塞调用,然后在需要时使用等待组。简化之后,您可以看到在写入数据的地方没有发生同步操作。 (在getPage中),因此您最终会得到nil

比赛条件固定

package main

import (
    "encoding/json"
    "fmt"
    "sync"
)

type CategoryScrapper struct {
    Name     string        `json:"name"`
    Link     string        `json:"link"`
    Products []ProductData `json:"products"`
}

type ProductData struct {
    Name        string `json:"name"`
    Link        string `json:"link"`
    Thumbnail   string `json:"thumbnail"`
    OriginPrice string `json:"OriginPrice"`
    Excerpt     string `json:"Excerpt"`
}

func (*CategoryScrapper) getMaxPageNum() int {
    return 1
}
func main() {
    cs := CategoryScrapper{
        Name: "Name",
        Link: "/link",
    }

    cs.GetProducts()

    res, err := json.Marshal(cs)
    if err != nil {
        fmt.Printf("ERROR: %v", err)
    }

    fmt.Println(string(res))
}

func (s *CategoryScrapper) GetProducts() {
    maxPageNum := s.getMaxPageNum()
    var wg sync.WaitGroup
    ch := make(chan ProductData)
    go func() {
        for p := range ch {
            s.Products = append(s.Products, p)
        }
        wg.Done()
    }()

    for i := 1; i <= maxPageNum; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            ch <- s.getPage(i)
        }(i)
    }

    wg.Wait()
    // make sure we close the chan reader go routine
    wg.Add(1)
    close(ch)
    wg.Wait()
}

func (s *CategoryScrapper) getPage(page int) ProductData {
    return ProductData{
        Name:        "Name",
        Link:        "Link",
        Thumbnail:   "Thumb",
        OriginPrice: "1111",
        Excerpt:     "Excerpt",
    }
}

一些风格点:
对于您的Products slice ,您无需将其初始化为零长度大小,可以找到nil。
我删除了像Product这样的数字包前缀,它可以帮助人们回答是否可以轻松地在play.golang.org中运行示例

希望这可以帮助。仅当您完成几次后才很明显,即使这样,也并非总是如此:-)

10-04 10:21