GO语言基础教程(255)Go语言在爬虫中的应用之将抓取的网页内容存储在文件中:Go语言爬虫实战:让你的数据抓取效率翻倍还不炸电脑
来源:     阅读:4
易浩激活码
发布于 2025-11-06 18:47
查看主页

让数据收集像吃饼干一样简单,还能保证电脑不卡顿

01 为什么Go语言是爬虫界的隐形冠军?

当我们谈论爬虫开发时,很多人会立刻想到Python。但实际上,Go语言正在成为爬虫开发的隐形冠军

这就像从单线程的蜗牛变成了多线程的猎豹,爬取速度简直不可同日而语。

Go语言内置的net/http包让我们只需几行代码就能发送HTTP请求,而其独特的goroutine和channel机制使得并发爬取变得异常简单。

想象一下,你一个人同时操作多台电脑收集资料,而你的对手只能一台一台地操作——这就是使用Go语言开发爬虫的优势!

更重要的是,Go语言编译出的可执行文件是静态链接的,可以直接运行在任何主流操作系统上,无需环境依赖,部署异常简便

无论你是Linux、Windows还是macOS用户,都可以轻松运行相同的爬虫程序,这种跨平台一致性大大减少了部署时的麻烦。

02 搭建开发环境:打好地基

任何伟大的工程都需要从打好地基开始,搭建Go爬虫开发环境其实非常简单。

首先,你需要安装Go语言环境。可以从Go官方网站下载并安装最新版本的Go编程语言。安装完成后,设置GOPATH环境变量,并创建一个新的工作目录用于存放你的Go项目。

在Linux系统上,如果你使用的是Debian系列,可以通过以下命令安装:



sudo apt update
sudo apt install golang-go

安装完成后,可以通过以下命令检查Go版本:


go version

接下来,为爬虫项目创建一个专门的工作目录:



mkdir my-crawler
cd my-crawler
go mod init my-crawler

这些命令会创建一个新的项目目录并初始化Go模块,为后续开发做好准备。

Go语言强大的标准库是它的另一大优势。除了标准库,你可能需要安装一些第三方库,例如goquery用于解析HTML内容。只需在终端中运行以下命令:


go get -u github.com/PuerkitoBio/goquery

这个著名的goquery库能让HTML解析变得像jQuery一样简单直观。

03 爬虫基础:从简单请求开始

让我们先从一个简单的爬虫程序开始,它会把整个网页内容抓取下来。这就好比我们第一次学钓鱼——先不管能不能钓到特定的鱼,确保能把钓竿甩出去再说。



package main
 
import (
 "fmt"
 "io/ioutil"
 "log"
 "net/http"
)
 
func main() {
 resp, err := http.Get("http://example.com")
 if err != nil {
  log.Fatal(err)
 }
 defer resp.Body.Close()
 
 body, err := ioutil.ReadAll(resp.Body)
 if err != nil {
  log.Fatal(err)
 }
 
 fmt.Println(string(body))
}

这个简单的程序使用了Go语言内置的net/http包。http.Get函数会返回一个http.Response对象,通过读取resp.Body可以获取网页内容。

但这里有个小问题——如果网站要求URL必须包含http://或https://前缀,我们的代码可能会出错。优化方法很简单,添加一个URL检查函数即可:



func checkUrl(s string) string {
 if strings.HasPrefix(s, "http") {
  return s
 }
 return fmt.Sprint("http://", s)
}

这个函数可以确保我们请求的URL始终具有正确的前缀,避免因URL格式错误导致的请求失败。

04 解析HTML:从杂乱中提取精华

获取网页内容只是第一步,真正有价值的是从HTML中提取我们需要的信息。这时候,goquery库就派上用场了。

如果说原始HTML是一团杂乱无章的毛线,那么goquery就是帮我们理清这团毛线的神奇钩针。



package main
 
import (
 "fmt"
 "log"
 "net/http"
 
 "github.com/PuerkitoBio/goquery"
)
 
func main() {
 resp, err := http.Get("http://example.com")
 if err != nil {
  fmt.Println("Error:", err)
  return
 }
 defer resp.Body.Close()
 
 doc, err := goquery.NewDocumentFromReader(resp.Body)
 if err != nil {
  fmt.Println("Error:", err)
  return
 }
 
 doc.Find("h1").Each(func(i int, s *goquery.Selection) {
  fmt.Println(s.Text())
 })
}

以上代码示例展示了如何使用goquery库解析HTML文档,并提取所有h1标签的文本内容。

如果你需要提取更复杂的数据,比如网页中的所有链接,goquery也能让这项工作变得简单:



func main() {
 url := "https://example.com"
 resp, err := http.Get(url)
 if err != nil {
  log.Fatal(err)
 }
 defer resp.Body.Close()
 
 doc, err := goquery.NewDocumentFromReader(resp.Body)
 if err != nil {
  log.Fatal(err)
 }
 
 doc.Find("a").Each(func(i int, s *goquery.Selection) {
  href, exists := s.Attr("href")
  if exists {
   fmt.Println(href)
  }
 })
}

这段代码会找出页面中的所有链接并打印出来,演示了如何使用goquery选择和提取HTML元素的属性。

05 进阶技巧:让爬虫“并发”起来

现在让我们进入Go语言爬虫最精彩的部分——并发处理。通过goroutine和channel,我们可以同时处理多个请求,效率呈指数级增长。



package main
 
import (
 "fmt"
 "net/http"
 "sync"
)
 
func fetch(url string, wg *sync.WaitGroup) {
 defer wg.Done()
 resp, err := http.Get(url)
 if err != nil {
  fmt.Println("Error:", err)
  return
 }
 defer resp.Body.Close()
 fmt.Println("Fetched:", url)
}
 
func main() {
 var wg sync.WaitGroup
 urls := []string{
  "http://example.com",
  "http://example.org",
  "http://example.net",
 }
 
 for _, url := range urls {
  wg.Add(1)
  go fetch(url, &wg)
 }
 wg.Wait()
}

在这个例子中,fetch函数用于发送HTTP请求,并在完成后通过调用wg.Done()通知WaitGroup。在主函数中,我们创建一个WaitGroup,并为每个URL启动一个goroutine来并发处理请求。

如果要更精细地控制并发并收集处理结果,可以使用channel:



package main
 
import (
 "fmt"
 "io"
 "io/ioutil"
 "net/http"
 "os"
 "strings"
 "time"
)
 
func main() {
 start := time.Now()
 ch := make(chan string)
 for _, url := range os.Args[1:] {
  url = checkUrl(url)
  go fetch(url, ch)
 }
 for range os.Args[1:] {
  fmt.Println(<-ch)
 }
 fmt.Printf("总耗时: %.2fs
", time.Since(start).Seconds())
}
 
func fetch(url string, ch chan<- string) {
 start := time.Now()
 resp, err := http.Get(url)
 if err != nil {
  ch <- fmt.Sprintf("获取 %s 出错: %v", url, err)
  return
 }
 nbytes, err := io.Copy(ioutil.Discard, resp.Body)
 resp.Body.Close()
 if err != nil {
  ch <- fmt.Sprintf("读取 %s 出错: %v", url, err)
  return
 }
 secs := time.Since(start).Seconds()
 ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url)
}
 
func checkUrl(s string) string {
 if strings.HasPrefix(s, "http") {
  return s
 }
 return fmt.Sprint("http://", s)
}

这段代码的精妙之处在于,它使用goroutine同时发起多个请求,再通过channel收集结果。就像同时派出多个侦察兵,然后等待他们一个个回来报告那样高效!

06 数据存储:把收获整理好,给数据一个安全的家

爬取到的数据如果不保存,就像沙滩上的画作,潮水一来就消失了。常见的做法是将数据存储到文件或数据库中。

好的存储方案就像是给收集来的数据一个安全的家,随时可以找到并使用。

存储到CSV文件

CSV是一种通用且简单的格式,适合存储表格型数据。以下示例展示了如何将数据保存到CSV文件:



package main
 
import (
 "encoding/csv"
 "os"
)
 
func saveToCSV(data [][]string, filename string) error {
 file, err := os.Create(filename)
 if err != nil {
  return err
 }
 defer file.Close()
 
 writer := csv.NewWriter(file)
 defer writer.Flush()
 
 for _, record := range data {
  if err := writer.Write(record); err != nil {
   return err
  }
 }
 return nil
}
 
func main() {
 data := [][]string{
  {"Name", "Age"},
  {"Alice", "30"},
  {"Bob", "25"},
 }
 
 if err := saveToCSV(data, "output.csv"); err != nil {
  fmt.Println("Error:", err)
 } else {
  fmt.Println("Data saved to output.csv")
 }
}

这个示例展示了如何使用Go语言的标准库encoding/csv将数据保存到CSV文件中。

存储为纯文本

如果只需要简单保存网页内容,纯文本格式是最直接的选择:



package main
 
import (
 "fmt"
 "io/ioutil"
 "net/http"
 "os"
)
 
func main() {
 resp, err := http.Get("http://example.com")
 if err != nil {
  fmt.Println("Error:", err)
  return
 }
 defer resp.Body.Close()
 
 body, err := ioutil.ReadAll(resp.Body)
 if err != nil {
  fmt.Println("Error:", err)
  return
 }
 
 err = os.WriteFile("webpage.html", body, 0644)
 if err != nil {
  fmt.Println("Error writing file:", err)
  return
 }
 
 fmt.Println("Web page saved to webpage.html")
}

这种方法适合保存完整的HTML内容,便于后续处理或分析。

存储为JSON格式

对于结构化数据,JSON格式是理想的选择:



package main
 
import (
 "encoding/json"
 "fmt"
 "net/http"
 "os"
 
 "github.com/PuerkitoBio/goquery"
)
 
type PageData struct {
 URL   string   `json:"url"`
 Title string   `json:"title"`
 Links []string `json:"links"`
}
 
func main() {
 var pageData PageData
 pageData.URL = "http://example.com"
 
 resp, err := http.Get(pageData.URL)
 if err != nil {
  fmt.Println("Error:", err)
  return
 }
 defer resp.Body.Close()
 
 doc, err := goquery.NewDocumentFromReader(resp.Body)
 if err != nil {
  fmt.Println("Error:", err)
  return
 }
 
 pageData.Title = doc.Find("title").Text()
 
 doc.Find("a").Each(func(i int, s *goquery.Selection) {
  href, exists := s.Attr("href")
  if exists {
   pageData.Links = append(pageData.Links, href)
  }
 })
 
 jsonData, err := json.MarshalIndent(pageData, "", "  ")
 if err != nil {
  fmt.Println("Error encoding JSON:", err)
  return
 }
 
 err = os.WriteFile("page_data.json", jsonData, 0644)
 if err != nil {
  fmt.Println("Error writing file:", err)
  return
 }
 
 fmt.Println("Data saved to page_data.json")
}

这个示例演示了如何提取网页的标题和所有链接,并将这些结构化数据保存为JSON格式。

07 完整示例:构建一个新闻标题爬虫

现在,让我们把前面学到的所有知识整合起来,创建一个完整的、可以并发爬取多个新闻网站标题并保存结果的爬虫程序。

这就像是从学做一道菜升级到了准备一整桌宴席,虽然挑战更大,但成就感也更强。



package main
 
import (
 "encoding/csv"
 "fmt"
 "log"
 "net/http"
 "os"
 "sync"
 
 "github.com/PuerkitoBio/goquery"
)
 
type NewsItem struct {
 Source string
 Title  string
 URL    string
}
 
func fetchNews(url string, source string, ch chan<- NewsItem, wg *sync.WaitGroup) {
 defer wg.Done()
 
 resp, err := http.Get(url)
 if err != nil {
  log.Printf("Error fetching %s: %v", url, err)
  return
 }
 defer resp.Body.Close()
 
 doc, err := goquery.NewDocumentFromReader(resp.Body)
 if err != nil {
  log.Printf("Error parsing document from %s: %v", url, err)
  return
 }
 
 doc.Find("h1, h2, h3").Each(func(i int, s *goquery.Selection) {
  title := s.Text()
  if len(title) > 10 { // 过滤过短的标题
   ch <- NewsItem{
    Source: source,
    Title:  title,
    URL:    url,
   }
  }
 })
}
 
func main() {
 sources := map[string]string{
  "Example News": "http://example.com",
  "Example Blog": "http://example.org",
 }
 
 var wg sync.WaitGroup
 newsCh := make(chan NewsItem, 100)
 
 // 启动爬取goroutines
 for source, url := range sources {
  wg.Add(1)
  go fetchNews(url, source, newsCh, &wg)
 }
 
 // 关闭channel的goroutine
 go func() {
  wg.Wait()
  close(newsCh)
 }()
 
 // 收集结果
 var newsItems []NewsItem
 for item := range newsCh {
  newsItems = append(newsItems, item)
 }
 
 // 保存到CSV文件
 file, err := os.Create("news.csv")
 if err != nil {
  log.Fatal("Error creating CSV file:", err)
 }
 defer file.Close()
 
 writer := csv.NewWriter(file)
 defer writer.Flush()
 
 // 写入CSV头部
 writer.Write([]string{"Source", "Title", "URL"})
 
 // 写入数据
 for _, item := range newsItems {
  err := writer.Write([]string{item.Source, item.Title, item.URL})
  if err != nil {
   log.Printf("Error writing record to CSV: %v", err)
  }
 }
 
 fmt.Printf("Saved %d news items to news.csv
", len(newsItems))
}

这个完整的爬虫示例演示了如何:

使用goroutine并发爬取多个网站使用goquery解析HTML并提取标题使用channel收集爬取结果将最终结果保存到CSV文件

08 爬虫礼仪与最佳实践

在编写爬虫时,请确保遵守目标网站的robots.txt文件规定,并且不要发送过多的请求以免对服务器造成负担。

遵守robots.txt

在爬取任何网站前,务必检查其robots.txt文件,尊重网站的爬虫规则。有些网站可能明确禁止爬虫访问某些敏感区域。

添加延迟

避免在短时间内发送大量请求,可以通过添加适当的延迟来减轻对目标网站的压力:



import "time"
 
// 在请求之间添加延迟
func politeFetch(url string) {
 // 爬取代码...
 time.Sleep(1 * time.Second) // 延迟1秒
}

错误处理

健全的错误处理机制是稳定爬虫的必备特性:



func safeFetch(url string) (*http.Response, error) {
 resp, err := http.Get(url)
 if err != nil {
  return nil, err
 }
 
 if resp.StatusCode != 200 {
  return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, resp.Status)
 }
 
 return resp, nil
}

并发控制

虽然Go的并发能力强大,但过高的并发数可能导致本地资源耗尽或被目标网站封禁。可以通过带缓冲的channel来实现并发控制:



func controlledCrawl(urls []string, maxConcurrency int) {
 sem := make(chan struct{}, maxConcurrency)
 var wg sync.WaitGroup
 
 for _, url := range urls {
  sem <- struct{}{} // 获取信号量
  wg.Add(1)
  
  go func(u string) {
   defer wg.Done()
   defer func() { <-sem }() // 释放信号量
   
   fetch(u)
  }(url)
 }
 
 wg.Wait()
}

09 结语

Go语言为爬虫开发带来了独特的优势,其简洁的语法、强大的并发模型和出色的性能,使得构建高效稳定的爬虫系统变得轻而易举。

通过本文的介绍,相信你已经掌握了使用Go语言开发爬虫的基础知识,特别是如何将抓取的数据存储到不同类型的文件中。

现在,是时候动手尝试创建你自己的Go语言爬虫了!从简单的示例开始,逐步增加复杂度,你会发现Go语言能让数据抓取变得既高效又有趣。

记住,一个好的爬虫不仅要高效抓取数据,还要尊重目标网站的资源,合理控制请求频率,遵守网络礼仪。

免责声明:本文为用户发表,不代表网站立场,仅供参考,不构成引导等用途。 系统环境
相关推荐
首页
搜索
订单
购物车
我的