绵阳网赚论坛

golang练手小项目系列(2)-并发爬虫_绵阳网赚论坛

admin 2019-10-28 15:03 赚钱资源共享 0 评论

一个靠谱的赚钱小项目本系列整理了10个工作量和难度适中的Golang小项目,适合已经掌握Go语法的工程师进一步熟练语法和常用库的用法。

问题描述:

实现一个网络爬虫,以输入的URL为起点,使用广度优先顺序访问页面。

要点:

实现对多个页面的并发访问,同时访问的页面数由参数 -concurrency 指定,默认为 20。

使用 -depth     指定访问的页面深度,默认为 3。

注意已经访问过的页面不要重复访问。

扩展:

将访问到的页面写入到本地以创建目标网站的本地镜像,注意,只有指定域名下的页面需要采集,写入本地的页面里的元素的href的值需要被修改为指向镜像页面,而不是原始页面。

实现

import (

  "bytes"

  "flag"

  "fmt"

  "golang.org/x/net/html"

  "io"

  "log"

  "net/http"

  "net/url"

  "os"

  "path/filepath"

  "strings"

  "sync"

  "time"

)

type URLInfo struct {

  url string

  depth int

}

var base *url.URL

func forEachNode(n *html.Node, pre, post func(n *html.Node)){

  if pre != nil{

      pre(n)

  }

  for c := n.FirstChild; c != nil; c = c.NextSibling{

      forEachNode(c, pre, post)

  }

  if post != nil{

      post(n)

  }

}

func linkNodes(n *html.Node) []*html.Node {

  var links []*html.Node

  visitNode := func(n *html.Node) {

      if n.Type == html.ElementNode && n.Data == "a" {

        links = append(links, n)

      }

  }

  forEachNode(n, visitNode, nil)

  return links

}

func linkURLs(linkNodes []*html.Node, base *url.URL) []string {

  var urls []string

  for _, n := range linkNodes {

      for _, a := range n.Attr {

        if a.Key != "href" {

            continue

        }

        link, err := base.Parse(a.Val)

        // ignore bad and non-local URLs

        if err != nil {

            log.Printf("skipping %q: %s", a.Val, err)

            continue

        }

        if link.Host != base.Host {

            //log.Printf("skipping %q: non-local host", a.Val)

            continue

        }

        if strings.HasPrefix(link.String(), "javascript"){

            continue

        }

        urls = append(urls, link.String())

      }

  }

  return urls

}

func rewriteLocalLinks(linkNodes []*html.Node, base *url.URL) {

  for _, n := range linkNodes {

      for i, a := range n.Attr {

        if a.Key != "href" {

            continue

        }

        link, err := base.Parse(a.Val)

        if err != nil || link.Host != base.Host {

            continue // ignore bad and non-local URLs

        }

        link.Scheme = ""

        link.Host = ""

        link.User = nil

        a.Val = link.String()

        n.Attr[i] = a

      }

  }

}

func Extract(url string)(urls []string, err error){

  timeout := time.Duration(10 * time.Second)

  client := http.Client{

      Timeout: timeout,

  }

  resp, err := client.Get(url)

  if err != nil{

      fmt.Println(err)

      return nil, err

  }

  if resp.StatusCode != http.StatusOK{

      resp.Body.Close()

      return nil, fmt.Errorf("getting %s:%s", url, resp.StatusCode)

  }

  if err != nil{

      return nil, fmt.Errorf("parsing %s as HTML: %v", url, err)

  }

  u, err := base.Parse(url)

  if err != nil {

      return nil, err

  }

  if base.Host != u.Host {

      log.Printf("not saving %s: non-local", url)

      return nil, nil

  }

  var body io.Reader

  contentType := resp.Header["Content-Type"]

  if strings.Contains(strings.Join(contentType, ","), "text/html") {

      doc, err := html.Parse(resp.Body)

      resp.Body.Close()

      if err != nil {

        return nil, fmt.Errorf("parsing %s as HTML: %v", u, err)

      }

      nodes := linkNodes(doc)

      urls = linkURLs(nodes, u)

      rewriteLocalLinks(nodes, u)

      b := &bytes.Buffer{}

      err = html.Render(b, doc)

      if err != nil {

        log.Printf("render %s: %s", u, err)

      }

      body = b

  }

  err = save(resp, body)

  return urls, err

}

func crawl(url string) []string{

  list, err := Extract(url)

  if err != nil{

      log.Print(err)

  }

  return list

}

func save(resp *http.Response, body io.Reader) error {

  u := resp.Request.URL

  filename := filepath.Join(u.Host, u.Path)

  if filepath.Ext(u.Path) == "" {

      filename = filepath.Join(u.Host, u.Path, "index.html")

  }

  err := os.MkdirAll(filepath.Dir(filename), 0777)

  if err != nil {

      return err

  }

  fmt.Println("filename:", filename)

  file, err := os.Create(filename)

  if err != nil {

      return err

  }

  if body != nil {

      _, err = io.Copy(file, body)

  } else {

      _, err = io.Copy(file, resp.Body)

  }

  if err != nil {

      log.Print("save: ", err)

  }

  err = file.Close()

  if err != nil {

      log.Print("save: ", err)

  }

  return nil

}

func parallellyCrawl(initialLinks string, concurrency, depth int){

  worklist := make(chan []URLInfo, 1)

  unseenLinks := make(chan URLInfo, 1)

  //值为1时表示进入unseenLinks队列,值为2时表示crawl完成

  seen := make(map[string] int)

  seenLock := sync.Mutex{}

  var urlInfos []URLInfo

  for _, url := range strings.Split(initialLinks, " "){

      urlInfos = append(urlInfos, URLInfo{url, 1})

  }

  go func() {worklist

  go func() {

      for{

        time.Sleep(1 * time.Second)

        seenFlag := true

        seenLock.Lock()

        for k := range seen{

            if seen[k] == 1{

              seenFlag = false

            }

        }

        seenLock.Unlock()

        if seenFlag && len(worklist) == 0{

            close(unseenLinks)

            close(worklist)

            break

        }

      }

  }()

  for i := 0; i

      go func() {

        for link := range unseenLinks{

            foundLinks := crawl(link.url)

            var urlInfos []URLInfo

            for _, u := range foundLinks{

              urlInfos = append(urlInfos, URLInfo{u, link.depth + 1})

            }

            go func(finishedUrl string) {

              worklist

              seenLock.Lock()

              seen[finishedUrl] = 2

              seenLock.Unlock()

            }(link.url)

        }

      }()

  }

  for list := range worklist{

      for _, link := range list {

        if link.depth > depth{

            continue

        }

        seenLock.Lock()

        _, ok := seen[link.url]

        seenLock.Unlock()

        if !ok{

            seenLock.Lock()

            seen[link.url] = 1

            seenLock.Unlock()

            unseenLinks

        }

      }

  }

  fmt.Printf("共访问了%d个页面", len(seen))

}

func main() {

  var maxDepth int

  var concurrency int

  var initialLink string

  flag.IntVar(&maxDepth, "d", 3, "max crawl depth")

  flag.IntVar(&concurrency, "c", 20, "number of crawl goroutines")

  flag.StringVar(&initialLink, "u", "", "initial link")

  flag.Parse()

  u, err := url.Parse(initialLink)

  if err != nil {

      fmt.Fprintf(os.Stderr, "invalid url: %s\n", err)

      os.Exit(1)

  }

  base = u

  parallellyCrawl(initialLink, concurrency, maxDepth)

}

  原标题:朱婷回应了大使点赞(采访手记)

  走进土耳其驻华大使馆之前,中国女排运动员朱婷曾做客土耳其驻华使馆的信息引起了我们的格外关注,此次采访由此增加了“体育交流”这一主题。有意思的是,我们的报道在海外网发表后,朱婷本人特意回应了大使对她的高度评价。

  “我在中国当大使”栏目组迄今采访了近30位驻华大使,他们观察中国的角度不尽相同,但大多集中在经济、文化、科技等领域。这一次,对土耳其驻华大使的采访从体育切入,为描绘更加全面、立体的中国形象提供一个新维度。

  见到体型健硕的约南大使后,我们的采访从大使最爱的体育运动开始:排球、篮球、网球、乒乓球……说起喜爱的体育运动,初见时略显严肃的大使,面部线条逐渐柔和起来。当被问到对中国哪一项运动印象最为深刻时,大使一句“(中国的)广场舞很有趣”引来采访现场满堂大笑,他对中国人日常生活的体察之深由此可见一斑。

  体育不仅是约南大使个人生活与中国的连接点,也是两个文明古国人文交流的新亮点。在我们的追问下,约南大使回忆起怀抱小儿子与朱婷“拉家常”的愉快场景。朱婷对土耳其排球作出的重要贡献,约南大使不吝赞美之词,并通过我们的镜头郑重表达了推动土中加强体育交流合作的强烈愿望。

  约南大使的愿望在中国网友中引起强烈共鸣。关于约南大使专访的图文与短视频报道推出后,引发网友热烈讨论,24小时内阅读量超过80万,阅读量逾50亿的“朱婷超话”“女排朱婷吧”等微博话题相继转发,综合转发互动量超过2000条。海外网微博网友表示:“谢谢约南大使和土耳其人民对朱婷的认可与厚爱!”海外网微信网友说:“土耳其大使说的‘一带一路’运动会,很有创意!助力沿线国家民心相通。”

  我们的采访报道也引起了朱婷本人的关注。朱婷在回应约南大使的赞赏时表示,“听到他这么说我很开心”“如果今后还可以去(土耳其)打球,我希望能打得更好一些,做得更好一些”。

责任编辑:张玉