如何使用Golang开发爬虫数据存储
在Golang爬虫开发中,爬取到的数据需要合理存储才能发挥价值。不同的业务场景对存储的需求不同,常见的存储方式包括内存存储、文件存储、关系型数据库存储和非关系型数据库存储。本文将结合实际场景,介绍几种主流的爬虫数据存储方案,并给出对应的Golang实现示例。
一、内存临时存储
如果爬虫数据只需要短时间使用,或者后续会批量写入持久化存储,可以先使用内存中的数据结构临时存储。Golang中常用的临时存储结构有切片、映射等,适合存储小规模、临时的爬取结果。
以下是一个使用切片临时存储爬虫数据的示例,假设我们爬取的是简单的文章信息:
package main
import (
"fmt"
"time"
)
// 定义文章数据结构
type Article struct {
Title string
Author string
Content string
URL string
CrawlAt time.Time
}
func main() {
// 临时存储文章的切片
var articleCache []Article
// 模拟爬取到的3篇文章数据
crawledData := []Article{
{
Title: "Golang爬虫入门教程",
Author: "张三",
Content: "本文介绍Golang爬虫的基本用法...",
URL: "https://ipipp.com/golang-spider-basic",
CrawlAt: time.Now(),
},
{
Title: "Go语言并发编程实践",
Author: "李四",
Content: "并发是Go语言的核心特性...",
URL: "https://ipipp.com/go-concurrency",
CrawlAt: time.Now(),
},
{
Title: "爬虫数据存储方案对比",
Author: "王五",
Content: "不同存储方式的优缺点分析...",
URL: "https://ipipp.com/spider-storage-compare",
CrawlAt: time.Now(),
},
}
// 将爬取到的数据存入临时切片
articleCache = append(articleCache, crawledData...)
// 打印临时存储的数据
fmt.Println("临时存储的文章数量:", len(articleCache))
for _, article := range articleCache {
fmt.Printf("标题:%s,作者:%s,爬取时间:%s\n", article.Title, article.Author, article.CrawlAt.Format("2006-01-02 15:04:05"))
}
}这种方式适合数据量小、不需要持久化的场景,程序退出后数据会丢失,实际爬虫项目中通常会结合持久化存储一起使用。
二、文件存储
文件存储适合存储非结构化或半结构化数据,常用的格式有JSON、CSV等。Golang标准库提供了encoding/json、encoding/csv等包,可以方便地实现文件写入。
2.1 JSON文件存储
JSON格式通用性强,适合存储结构化的爬虫数据,以下是把爬取的文章数据写入JSON文件的示例:
package main
import (
"encoding/json"
"fmt"
"os"
"time"
)
type Article struct {
Title string `json:"title"`
Author string `json:"author"`
Content string `json:"content"`
URL string `json:"url"`
CrawlAt time.Time `json:"crawl_at"`
}
func main() {
// 模拟爬取的文章数据
articles := []Article{
{
Title: "Golang爬虫入门教程",
Author: "张三",
Content: "本文介绍Golang爬虫的基本用法...",
URL: "https://ipipp.com/golang-spider-basic",
CrawlAt: time.Now(),
},
{
Title: "Go语言并发编程实践",
Author: "李四",
Content: "并发是Go语言的核心特性...",
URL: "https://ipipp.com/go-concurrency",
CrawlAt: time.Now(),
},
}
// 将数据编码为JSON格式
jsonData, err := json.MarshalIndent(articles, "", " ")
if err != nil {
fmt.Println("JSON编码失败:", err)
return
}
// 写入JSON文件
err = os.WriteFile("articles.json", jsonData, 0644)
if err != nil {
fmt.Println("写入文件失败:", err)
return
}
fmt.Println("数据已成功写入articles.json文件")
}2.2 CSV文件存储
如果数据是表格形式,CSV格式更方便后续用Excel等工具处理,以下是写入CSV文件的示例:
package main
import (
"encoding/csv"
"fmt"
"os"
"time"
)
type Article struct {
Title string
Author string
Content string
URL string
CrawlAt time.Time
}
func main() {
// 模拟爬取的文章数据
articles := []Article{
{
Title: "Golang爬虫入门教程",
Author: "张三",
Content: "本文介绍Golang爬虫的基本用法...",
URL: "https://ipipp.com/golang-spider-basic",
CrawlAt: time.Now(),
},
{
Title: "Go语言并发编程实践",
Author: "李四",
Content: "并发是Go语言的核心特性...",
URL: "https://ipipp.com/go-concurrency",
CrawlAt: time.Now(),
},
}
// 创建CSV文件
file, err := os.Create("articles.csv")
if err != nil {
fmt.Println("创建文件失败:", err)
return
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
// 写入表头
header := []string{"标题", "作者", "内容", "链接", "爬取时间"}
if err := writer.Write(header); err != nil {
fmt.Println("写入表头失败:", err)
return
}
// 写入数据行
for _, article := range articles {
row := []string{
article.Title,
article.Author,
article.Content,
article.URL,
article.CrawlAt.Format("2006-01-02 15:04:05"),
}
if err := writer.Write(row); err != nil {
fmt.Println("写入数据行失败:", err)
return
}
}
fmt.Println("数据已成功写入articles.csv文件")
}三、关系型数据库存储(MySQL为例)
如果爬虫数据需要频繁查询、更新,或者数据之间有关联关系,使用关系型数据库是更好的选择。下面以MySQL为例,介绍如何使用Golang操作数据库存储爬虫数据,需要用到的驱动是github.com/go-sql-driver/mysql。
首先初始化数据库表结构,执行以下SQL创建文章表:
CREATE TABLE `articles` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(255) NOT NULL COMMENT '文章标题', `author` varchar(100) DEFAULT NULL COMMENT '作者', `content` text COMMENT '文章内容', `url` varchar(500) NOT NULL COMMENT '文章链接', `crawl_at` datetime NOT NULL COMMENT '爬取时间', PRIMARY KEY (`id`), UNIQUE KEY `idx_url` (`url`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
以下是Golang写入MySQL的示例代码:
package main
import (
"database/sql"
"fmt"
"time"
_ "github.com/go-sql-driver/mysql"
)
type Article struct {
Title string
Author string
Content string
URL string
CrawlAt time.Time
}
func main() {
// 数据库连接配置,替换为实际的数据库地址、用户名、密码、库名
dsn := "root:password@tcp(127.0.0.1:3306)/spider_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := sql.Open("mysql", dsn)
if err != nil {
fmt.Println("数据库连接失败:", err)
return
}
defer db.Close()
// 验证数据库连接
if err := db.Ping(); err != nil {
fmt.Println("数据库连接验证失败:", err)
return
}
fmt.Println("数据库连接成功")
// 模拟爬取的文章数据
article := Article{
Title: "Golang爬虫数据存储实战",
Author: "赵六",
Content: "本文详细介绍爬虫数据存储的多种方案...",
URL: "https://ipipp.com/spider-storage-practice",
CrawlAt: time.Now(),
}
// 插入数据,忽略重复链接的记录
insertSQL := "INSERT IGNORE INTO articles (title, author, content, url, crawl_at) VALUES (?, ?, ?, ?, ?)"
result, err := db.Exec(insertSQL, article.Title, article.Author, article.Content, article.URL, article.CrawlAt)
if err != nil {
fmt.Println("插入数据失败:", err)
return
}
// 获取插入影响的行数
rowsAffected, _ := result.RowsAffected()
if rowsAffected > 0 {
fmt.Println("数据插入成功")
} else {
fmt.Println("数据已存在,未重复插入")
}
}四、非关系型数据库存储(Redis为例)
如果爬虫需要高速读写、缓存热点数据,或者存储结构灵活的数据,Redis是非关系型数据库的不错选择。Golang操作Redis常用的驱动是github.com/go-redis/redis/v8。
以下是使用Redis存储爬虫数据的示例,这里把文章数据以哈希结构存储,同时用集合记录所有已爬取的URL避免重复爬取:
package main
import (
"context"
"fmt"
"time"
"github.com/go-redis/redis/v8"
)
type Article struct {
Title string
Author string
Content string
URL string
CrawlAt time.Time
}
func main() {
ctx := context.Background()
// 连接Redis,替换为实际的Redis地址和密码
rdb := redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "", // 无密码则留空
DB: 0,
})
// 测试Redis连接
_, err := rdb.Ping(ctx).Result()
if err != nil {
fmt.Println("Redis连接失败:", err)
return
}
fmt.Println("Redis连接成功")
// 模拟爬取的文章数据
article := Article{
Title: "Redis在爬虫中的应用",
Author: "孙七",
Content: "Redis可以作为爬虫的去重缓存和高速存储...",
URL: "https://ipipp.com/redis-in-spider",
CrawlAt: time.Now(),
}
// 先检查URL是否已爬取过,避免重复存储
crawled, err := rdb.SIsMember(ctx, "spider:crawled_urls", article.URL).Result()
if err != nil {
fmt.Println("检查URL失败:", err)
return
}
if crawled {
fmt.Println("该URL已爬取,跳过存储")
return
}
// 将文章数据存入Redis哈希
hashKey := "article:" + article.URL
err = rdb.HSet(ctx, hashKey, map[string]interface{}{
"title": article.Title,
"author": article.Author,
"content": article.Content,
"url": article.URL,
"crawl_at": article.CrawlAt.Format("2006-01-02 15:04:05"),
}).Err()
if err != nil {
fmt.Println("存储文章数据失败:", err)
return
}
// 把URL加入已爬取集合
err = rdb.SAdd(ctx, "spider:crawled_urls", article.URL).Err()
if err != nil {
fmt.Println("记录URL失败:", err)
return
}
fmt.Println("数据已成功存入Redis")
}五、存储方案选择建议
不同的存储方案适用场景不同,可以根据实际需求选择:
- 临时缓存、小批量数据:优先选择内存存储,读写速度快
- 离线分析、数据导出、小量持久化:选择JSON或CSV文件存储,实现简单
- 结构化数据、需要复杂查询、数据关联:选择MySQL等关系型数据库
- 高速读写、去重、缓存、灵活结构数据:选择Redis等非关系型数据库
实际项目中也可以组合使用多种存储方案,比如用Redis做去重和临时缓存,用MySQL做持久化存储,用文件做数据备份,充分发挥不同存储方式的优势。