匠心精神 - 良心品质腾讯认可的专业机构-IT人的高薪实战学院

咨询电话:4000806560

使用Go语言构建Web爬虫

使用Go语言构建Web爬虫

在现今的互联网世界中,爬虫(Web Crawler)的应用越来越广泛。它可以用来抓取互联网上的数据,进行数据挖掘、信息分析等,是很多网站和应用程序的重要部分。而Go语言作为一个越来越流行的编程语言,在Web爬虫中也扮演着重要的角色。

这篇文章将介绍如何使用Go语言构建Web爬虫,包括如何解析HTML、如何使用正则表达式等。

1. 爬虫原理

爬虫是一个自动抓取网页内容的程序,它模拟浏览器行为,通过HTTP协议访问网页,并解析网页内容,从中抽取需要的信息。通常包括以下几个步骤:

1) 发送HTTP请求,获取网页内容。
2) 解析HTML,抽取需要的信息。HTML是一种标记语言,包含了文本、图片、链接等各种内容,我们需要解析它,并把需要的信息抽取出来。
3) 处理数据。我们可以把抽取出来的信息保存到数据库中,或者根据信息进行进一步的处理和分析。

2. Go语言与爬虫

Go语言是一种高效、类似于C语言的编程语言,它的特点是轻量级、并发支持、垃圾回收等。这些特点使得Go语言非常适合构建Web爬虫。

2.1 发送HTTP请求

Go标准库中提供了net/http包,它可以用来发送HTTP请求和接收HTTP响应。我们可以使用http.Get()方法来发送HTTP GET请求,该方法返回一个指向http.Response结构体的指针。

```go
resp, err := http.Get("http://www.example.com")
if err != nil {
    //处理错误
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
    //处理错误
}
fmt.Println(string(body))
```

2.2 解析HTML

HTML通常是网页的标记语言,用于定义网页的结构和内容。Go语言中有几个库可以用来解析HTML,如golang.org/x/net/html、golang.org/x/net/html/charset。其中golang.org/x/net/html是Go语言标准库中解析HTML的包。

```go
resp, err := http.Get("http://www.example.com")
if err != nil {
    //处理错误
}
defer resp.Body.Close()
doc, err := html.Parse(resp.Body)
if err != nil {
    //处理错误
}
```

解析完HTML之后,我们可以通过递归遍历文档树的方式获取节点信息。以下是一个简单的递归函数,用于遍历HTML文档树。

```go
func traverse(n *html.Node) {
    if n.Type == html.ElementNode && n.Data == "a" {
        for _, a := range n.Attr {
            if a.Key == "href" {
                fmt.Println(a.Val)
            }
        }
    }
    for c := n.FirstChild; c != nil; c = c.NextSibling {
        traverse(c)
    }
}
```

以上代码可以遍历HTML文档树中所有的a标签,并输出它们的href属性。

2.3 正则表达式

正则表达式是一种用于匹配字符串的表示方法。它可以用于解析HTML文本,提取需要的信息。Go语言中有内置的regexp包,用于支持正则表达式。

```go
resp, err := http.Get("http://www.example.com")
if err != nil {
    //处理错误
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
    //处理错误
}
re := regexp.MustCompile(`(?i)]+href="([^"]*)"`)
matches := re.FindAllStringSubmatch(string(body), -1)
for _, m := range matches {
    fmt.Println(m[1])
}
```

以上代码可以提取HTML文本中所有的a标签的href属性,并输出它们的值。

3. 实战演练

以下是一个简单的Web爬虫,它可以爬取豆瓣电影Top250的电影名称、导演、主演和评分等信息,并把这些信息保存到CSV文件中。

```go
package main

import (
    "encoding/csv"
    "fmt"
    "net/http"
    "regexp"
    "strconv"
    "strings"

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

type Movie struct {
    Rank       int
    Name       string
    Director   string
    Actor      string
    Score      float64
    Quote      string
}

func main() {
    file, err := createCsvFile("top250.csv")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer file.Close()
    writer := csv.NewWriter(file)
    defer writer.Flush()
    writer.Write([]string{"Rank", "Name", "Director", "Actor", "Score", "Quote"})
    pageUrl := "https://movie.douban.com/top250"
    for i := 0; i < 10; i++ {
        resp, err := http.Get(pageUrl + "?start=" + strconv.Itoa(i*25) + "&filter=")
        if err != nil {
            fmt.Println(err)
            continue
        }
        defer resp.Body.Close()
        doc, err := html.Parse(resp.Body)
        if err != nil {
            fmt.Println(err)
            continue
        }
        movies := parseMovies(doc)
        for _, m := range movies {
            writer.Write([]string{
                strconv.Itoa(m.Rank), m.Name, m.Director, m.Actor, strconv.FormatFloat(m.Score, 'f', 1, 64), m.Quote,
            })
        }
    }
}

func parseMovies(n *html.Node) []Movie {
    var movies []Movie
    if n.Type == html.ElementNode && n.Data == "ol" {
        rank := 1
        for c := n.FirstChild; c != nil; c = c.NextSibling {
            if c.Type == html.ElementNode && c.Data == "li" {
                movie := parseMovie(c, rank)
                movies = append(movies, movie)
                rank++
            }
        }
    }
    for c := n.FirstChild; c != nil; c = c.NextSibling {
        ms := parseMovies(c)
        movies = append(movies, ms...)
    }
    return movies
}

func parseMovie(n *html.Node, rank int) Movie {
    var movie Movie
    movie.Rank = rank
    for c := n.FirstChild; c != nil; c = c.NextSibling {
        if c.Type == html.ElementNode && c.Data == "div" && getAttrValue(c, "class") == "hd" {
            for c2 := c.FirstChild; c2 != nil; c2 = c2.NextSibling {
                if c2.Type == html.ElementNode && c2.Data == "a" {
                    movie.Name = strings.TrimSpace(c2.FirstChild.Data)
                }
            }
        }
        if c.Type == html.ElementNode && c.Data == "div" && getAttrValue(c, "class") == "bd" {
            for c2 := c.FirstChild; c2 != nil; c2 = c2.NextSibling {
                if c2.Type == html.ElementNode && c2.Data == "p" && getAttrValue(c2, "class") == "" {
                    movie.Director, movie.Actor = parseDirectorAndActor(c2.FirstChild.Data)
                }
                if c2.Type == html.ElementNode && c2.Data == "div" && getAttrValue(c2, "class") == "star" {
                    for c3 := c2.FirstChild; c3 != nil; c3 = c3.NextSibling {
                        if c3.Type == html.TextNode {
                            movie.Score = parseScore(c3.Data)
                        }
                    }
                }
                if c2.Type == html.ElementNode && c2.Data == "p" && getAttrValue(c2, "class") == "quote" {
                    quote := strings.TrimSpace(c2.FirstChild.Data)
                    quote = strings.TrimPrefix(quote, "“")
                    quote = strings.TrimSuffix(quote, "”")
                    movie.Quote = quote
                }
            }
        }
    }
    return movie
}

func getAttrValue(n *html.Node, name string) string {
    for _, a := range n.Attr {
        if a.Key == name {
            return a.Val
        }
    }
    return ""
}

func parseDirectorAndActor(s string) (string, string) {
    s = strings.TrimSpace(s)
    i := strings.Index(s, "导演:")
    if i != -1 {
        j := strings.Index(s[i+4:], "主演:")
        if j != -1 {
            director := strings.TrimSpace(s[i+4 : i+4+j])
            actor := strings.TrimSpace(s[i+4+j+4:])
            return director, actor
        }
    }
    return "", ""
}

func parseScore(s string) float64 {
    re := regexp.MustCompile(`\d+\.\d+`)
    matches := re.FindStringSubmatch(s)
    if len(matches) > 0 {
        score, err := strconv.ParseFloat(matches[0], 64)
        if err == nil {
            return score
        }
    }
    return 0
}

func createCsvFile(fileName string) (*os.File, error) {
    if _, err := os.Stat(fileName); err == nil {
        if err := os.Remove(fileName); err != nil {
            return nil, err
        }
    }
    return os.Create(fileName)
}
```

以上代码中,我们首先创建了一个CSV文件,并写入表头。接着,从第1页到第10页,逐页抓取数据。对于每一页,我们使用parseMovies()函数解析出其中的电影信息,并使用parseMovie()函数抽取其中的排名、电影名称、导演、主演、评分和引语等信息。最后,将所有电影信息写入CSV文件。

总结

在本文中,我们介绍了如何使用Go语言构建Web爬虫。我们学习了如何发送HTTP请求、解析HTML、使用正则表达式等技术。最后,我们通过实战演练构建了一个简单的Web爬虫,用于爬取豆瓣电影Top250的电影信息。希望本文能够对读者有所帮助,也希望读者能够掌握更多Web爬虫的技术知识,为数据挖掘和信息分析做出更多的贡献。