新手教程:Go 语言实现DoubanTop 250 数据爬取及保存到 CSV 和 Excel

在本教程中,我们将会详细讲解如何通过 Go 语言和 Colly 爬虫框架,爬取豆瓣电影 Top 250 的数据,并将结果分别保存到 CSV 和 Excel 文件中。通过这个项目,你可以学习如何使用 Go 进行网络爬虫,并把数据格式化保存下来。

技术栈

  • 编程语言:Go
  • 网络爬虫框架:Colly
  • 数据保存格式:CSV 和 Excel

第一步:项目初始化

首先,我们需要在项目根目录下初始化 Go 模块。你可以通过以下命令完成:

go mod init "<项目仓库名>"

这个命令会生成一个 go.mod 文件,用于管理项目的依赖。

第二步:创建项目结构

  1. 创建 main.go 文件

    在项目根目录下创建一个 main.go 文件,这是整个程序的入口文件,包含主程序逻辑。

  2. 创建 output 文件夹

    为了存储输出的文件,我们在项目根目录下创建一个 output 文件夹,并在这个目录内创建两个文件:CSV.goEXCEL.go。这两个文件分别用来保存爬取到的数据到 CSV 和 Excel 文件。

  3. 项目目录结构

    ├── main.go
    ├── output/
    │   ├── CSV.go
    │   └── EXCEL.go
    └── go.mod
    

第三步:安装依赖

  1. 安装 Colly 框架

    Colly 是一个强大的 Go 语言爬虫框架,可以轻松地从网页中提取数据。通过下面的命令安装 Colly:

    go get -u github.com/gocolly/colly/v2
    
  2. 安装进度条依赖

    为了让爬取过程更加直观,我们将使用 progressbar 包来显示爬取进度:

    go get -u github.com/schollz/progressbar/v3
    
  3. 安装 Excel 操作依赖

    为了将数据导出到 Excel 文件,我们需要使用 excelize 包:

    go get -u github.com/xuri/excelize/v2
    

第四步:编写 CSV.go 文件

output/CSV.go 中,我们编写保存 CSV 文件的逻辑。该文件的作用是接受一个数据数组,并将其保存为 CSV 格式。

package utils

import (
	"encoding/csv"
	"os"
)

// SaveToCSV 将数据保存到 CSV 文件
func SaveToCSV(filename string, data [][]string) error {
	file, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer file.Close()

	writer := csv.NewWriter(file)
	defer writer.Flush()

	// 写入表头
	header := []string{"标题", "类型", "评价"}
	if err := writer.Write(header); err != nil {
		return err
	}

	// 写入数据
	for _, record := range data {
		if err := writer.Write(record); err != nil {
			return err
		}
	}

	return nil
}

第五步:编写 EXCEL.go 文件

output/EXCEL.go 中,我们编写保存 Excel 文件的逻辑。这里使用 excelize 库来生成 Excel 文件。

package utils

import (
	excelize "github.com/xuri/excelize/v2"
)

// SaveToExcel 将数据保存到 Excel 文件
func SaveToExcel(filename string, data [][]string) error {
	f := excelize.NewFile()
	sheetName := "Sheet1"

	// 添加表头
	header := []string{"标题", "类型", "评价"}
	data = append([][]string{header}, data...)

	// 创建新的工作表
	index, err := f.NewSheet(sheetName)
	if err != nil {
		return err
	}

	// 写入数据
	for i, row := range data {
		for j, cell := range row {
			cellName, _ := excelize.CoordinatesToCellName(j+1, i+1)
			f.SetCellValue(sheetName, cellName, cell)
		}
	}

	f.SetActiveSheet(index)
	return f.SaveAs(filename)
}

第六步:编写 Main.go 文件

main.go 文件中编写爬虫的核心逻辑,并调用 CSV.goEXCEL.go 中的保存函数。

package main

import (
	"os"
	"fmt"
	"strings"
	"time"

	"douban_250/utils"

	"github.com/gocolly/colly/v2"
	"github.com/gocolly/colly/v2/extensions"
	"github.com/schollz/progressbar/v3"
)

func main() {
	var data [][]string
	startTime := time.Now() // 记录开始时间

	// 创建一个新的 Colly 爬虫实例
	c := colly.NewCollector(
		colly.IgnoreRobotsTxt(), // 忽略 robots.txt 文件
		colly.MaxDepth(2),       // 设置爬取的最大深度为 2
		colly.AllowedDomains("movie.douban.com", "sec.douban.com"), // 设置允许访问的域名
	)
	extensions.RandomUserAgent(c) // 设置随机 User-Agent

	c.OnHTML("#content > div > div.article > ol > li", func(e *colly.HTMLElement) {
		// 提取标题
		title := e.ChildText(".hd .title:first-child")

		// 提取完整文本
		fullText := e.ChildText(".bd p")

		// 从 `fullText` 中提取 `类型` 部分
		lines := strings.Split(fullText, "\n")
		var typeLine string
		if len(lines) > 1 {
			typeLine = strings.TrimSpace(lines[1])
			// 去掉多余的空白字符
			typeLine = strings.ReplaceAll(typeLine, " ", " ")
			typeLine = strings.TrimSpace(typeLine)
		}

		// 提取评价
		quote := e.ChildText(".quote .inq")

		// 输出结果
		// fmt.Printf("标题: %s\n", title)
		// fmt.Printf("类型: %s\n", typeLine)
		// fmt.Printf("评价: %s\n", quote)

		// 保存数据到切片
		data = append(data, []string{title, typeLine, quote})
	})

	// 请求之前的回调函数
	c.OnRequest(func(r *colly.Request) {
		fmt.Println("正在访问:", r.URL.String())
	})

	// 请求之后的回调函数
	c.OnResponse(func(r *colly.Response) {
		// 检查是否重定向
		if r.StatusCode == 302 {
			fmt.Println("重定向到:", r.Headers.Get("Location"))
		} else if r.StatusCode == 403 {
			// 检查响应内容和域名
			body := string(r.Body)
			if strings.Contains(body, "Forbidden") || strings.Contains(body, "403") {
				if strings.Contains(r.Request.URL.String(), "sec.douban.com") {
					fmt.Println("403 Forbidden: IP 被封禁或需要验证")
				} else {
					fmt.Println("403 Forbidden: 访问受限")
				}
			} else {
				fmt.Println("响应状态码:", r.StatusCode)
			}
		} else {
			fmt.Println("响应状态码:", r.StatusCode)
		}
	})

	// 设置进度条, 爬取前 10 页
	totalPages := 10 // 总共只有10页,即250条数据
	bar := progressbar.Default(int64(totalPages), "爬取进度条")

	// 爬取分页内容
	for i := 0; i <= totalPages; i++ {
		// 构造分页 URL
		pageURL := fmt.Sprintf("https://movie.douban.com/top250?start=%d&filter=", i*25)
		err := c.Visit(pageURL)
		if err != nil {
			fmt.Println("页面访问失败:", err)
		}
		fmt.Println("第", i+1, "页爬取完成")
		bar.Add(1) // 更新进度条
	}

	// 保存到 CSV 文件
	err := utils.SaveToCSV("movies.csv", data)
	if err != nil {
		fmt.Println("保存到 CSV 文件失败:", err)
	}

	// 保存到 Excel 文件
	err = utils.SaveToExcel("movies.xlsx", data)
	if err != nil {
		fmt.Println("保存到 Excel 文件失败:", err)
	}

	// 计算总耗时
	elapsedTime := time.Since(startTime)
	fmt.Printf("总耗时: %s\n", elapsedTime)

	// 提示用户按任意键退出
	fmt.Println("请按任意键退出...")
	_, _ = os.Stdin.Read(make([]byte, 1)) // 等待用户输入
}

第七步:运行程序

在项目根目录下运行以下命令来启动爬虫程序:

go run main.go

程序会自动开始爬取豆瓣电影 Top 250 的数据,并将结果分别保存到 movies.csvmovies.xlsx 文件中。

结语

通过本教程,你已经掌握了使用 Go 语言和 Colly 框架构建网络爬虫的基本方法,并了解了如何将数据保存到 CSV 和 Excel 文件。你可以根据自己的需求,进一步扩展此爬虫功能,比如抓取更多信息,处理验证码,或者实现更加复杂的爬取逻辑。