Golang简单爬虫实现教程
本文详细介绍了如何使用Golang实现一个简单的网络爬虫。首先,文章阐述了使用`net/http`发送HTTP请求并处理潜在错误(如超时和重定向)的重要性。随后,利用`goquery`库结合CSS选择器,高效地解析HTML文档并提取所需数据,例如网页标题和链接。此外,文章还深入探讨了如何通过`goroutine`和`channel`实现并发抓取,配合`sync.WaitGroup`进行同步,从而显著提升爬虫效率。最后,根据数据量和需求,介绍了将抓取到的数据存储到文件或数据库(如SQLite、PostgreSQL、MongoDB)的策略,为读者提供了一套完整的Golang爬虫解决方案。
答案:使用Golang实现爬虫需先用net/http发送请求并处理错误、超时和重定向,再通过goquery结合CSS选择器解析HTML提取数据,最后利用goroutine和channel实现并发抓取,配合WaitGroup同步,数据可存为文件或数据库。
用Golang实现一个简单的爬虫,核心思路其实就是两步:先用标准库net/http
发出HTTP请求,获取网页的HTML内容;接着,利用goquery
这个库来解析HTML,像jQuery一样方便地定位和提取你想要的数据。这套组合拳下来,处理大部分静态网页数据抓取的需求,基本就够用了。
解决方案
说实话,第一次用Go写爬虫,net/http
的简洁性让我有点惊喜。它把HTTP请求的复杂性封装得很好,你只需要关心你想请求什么URL,以及如何处理返回的数据。而当HTML内容到手后,传统的字符串匹配或者正则表达,面对复杂的网页结构简直是噩梦。这时候,goquery
就成了救星,它把前端开发中熟悉的CSS选择器带到了后端,大大提升了开发效率和代码的可读性。
下面是一个基础的实现,展示了如何抓取一个网页的标题和所有链接:
package main import ( "fmt" "log" "net/http" "strings" "github.com/PuerkitoBio/goquery" ) func main() { // 目标URL,这里以一个示例网站为例,实际使用时请替换 url := "http://example.com" // 请替换成实际可访问的URL // 发送HTTP GET请求 resp, err := http.Get(url) if err != nil { log.Fatalf("请求URL失败: %v", err) return } defer resp.Body.Close() // 确保在函数结束时关闭响应体 // 检查HTTP状态码 if resp.StatusCode != http.StatusOK { log.Fatalf("HTTP请求失败,状态码: %d %s", resp.StatusCode, resp.Status) return } // 使用goquery解析HTML文档 doc, err := goquery.NewDocumentFromReader(resp.Body) if err != nil { log.Fatalf("解析HTML文档失败: %v", err) return } // 提取网页标题 title := doc.Find("title").Text() fmt.Printf("网页标题: %s\n", title) fmt.Println("\n所有链接:") // 遍历所有a标签,提取href属性和链接文本 doc.Find("a").Each(func(i int, s *goquery.Selection) { href, exists := s.Attr("href") if exists { linkText := strings.TrimSpace(s.Text()) // 简单过滤空链接文本,或只显示非锚点链接 if linkText != "" && !strings.HasPrefix(href, "#") { fmt.Printf("- 链接 %d: %s (%s)\n", i+1, linkText, href) } } }) // 尝试提取某个特定元素,比如第一个段落 firstParagraph := doc.Find("p").First().Text() if firstParagraph != "" { fmt.Printf("\n第一个段落内容: %s\n", strings.TrimSpace(firstParagraph)) } else { fmt.Println("\n未找到任何段落。") } }
这段代码展示了最基本的爬取和解析流程。从请求到错误处理,再到用goquery
定位元素,一切都显得相当直观。
Golang爬虫如何处理HTTP请求错误与重定向?
在实际的爬虫开发中,网络波动或者目标网站的反爬机制,常常让请求变得不可靠。光是简单的http.Get()
可能不够用,我们需要更精细的控制。
首先是错误处理。除了检查http.Get()
返回的错误,我们还需要关注resp.StatusCode
。一个非200的状态码(比如404 Not Found,403 Forbidden,500 Internal Server Error)意味着请求没有成功,这时候应该根据具体情况决定是重试、记录日志还是直接跳过。
超时设置也是个关键点。默认的HTTP请求可能不会设置超时,导致程序长时间阻塞。我们可以创建一个自定义的http.Client
来配置超时:
client := &http.Client{ Timeout: 10 * time.Second, // 设置10秒的请求超时 } resp, err := client.Get(url) // ... 后续处理
User-Agent的设置也挺重要。很多网站会根据User-Agent来判断请求来源,如果发现是爬虫,可能会直接拒绝。模拟浏览器行为,设置一个常见的User-Agent头是个好习惯:
req, err := http.NewRequest("GET", url, nil) if err != nil { log.Fatalf("创建请求失败: %v", err) } req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") client := &http.Client{} resp, err := client.Do(req) // ... 后续处理
至于重定向,net/http
的http.Client
默认是会自动处理3xx重定向的。但有时候,我们可能需要禁用重定向,或者在重定向发生时执行一些自定义逻辑。比如,你想知道最终重定向到的URL,或者想限制重定向的次数。这可以通过设置http.Client
的CheckRedirect
字段来实现:
client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { if len(via) >= 10 { // 限制重定向次数,防止无限循环 return errors.New("stopped after 10 redirects") } fmt.Printf("重定向到: %s (原URL: %s)\n", req.URL.String(), via[0].URL.String()) return nil // 返回nil表示允许重定向 }, } // 如果想禁用重定向,直接返回http.ErrUseLastResponse即可 // CheckRedirect: func(req *http.Request, via []*http.Request) error { // return http.ErrUseLastResponse // }
这些细节的考量,能让你的爬虫在面对复杂网络环境时,显得更加健壮和可靠。
goquery在复杂HTML结构中如何精准定位元素?
说完了请求,接下来的重头戏自然是数据的提取。goquery
之所以好用,很大程度上是因为它对CSS选择器的支持。如果你熟悉前端开发,那么goquery
的API几乎是无缝衔接。
要精准定位元素,关键在于灵活运用CSS选择器。
- 基本选择器:
tagName
:选择所有指定标签的元素,比如"div"
,"p"
。.className
:选择所有带有指定class的元素,比如".product-title"
。#idName
:选择带有指定ID的元素,比如"#main-content"
。
- 组合选择器:
parent child
:后代选择器,选择parent
元素下的所有child
元素。例如"div p"
选择所有在div
标签内的p
标签。parent > child
:子元素选择器,选择parent
元素的直接child
元素。例如"ul > li"
。tag.className
:同时匹配标签和class,例如"span.price"
。[attribute=value]
:属性选择器,选择带有特定属性和值的元素。例如"a[target=_blank]"
。
- 伪类选择器:
:nth-child(n)
:选择父元素下的第n个子元素。:first-child
,:last-child
:选择第一个/最后一个子元素。
goquery
的Find()
方法就是用来接收这些CSS选择器的。它会返回一个*goquery.Selection
对象,这个对象代表了所有匹配到的元素集合。你可以继续在这个Selection
对象上调用Find()
进行链式操作,从而深入到更复杂的嵌套结构中。
举个例子,假设你有一个产品列表,每个产品在一个div
中,div
有一个class="product-item"
,产品标题在内部的一个h3
标签里,链接在h3
里的a
标签里:
<div class="product-item"> <h3 class="product-title"> <a href="/product/123">Awesome Product</a> </h3> <span class="price">$19.99</span> </div>
要提取产品标题和价格,你可以这么做:
doc.Find(".product-item").Each(func(i int, s *goquery.Selection) { // 在当前产品项的Selection中查找标题和价格 title := s.Find(".product-title a").Text() href, _ := s.Find(".product-title a").Attr("href") price := s.Find(".price").Text() fmt.Printf("产品 %d: 标题=%s, 链接=%s, 价格=%s\n", i+1, title, href, price) })
这里的关键是s.Find()
,它是在当前迭代的product-item
元素内部进行查找,而不是从整个文档的根部开始,这大大提高了定位的准确性。
此外,goquery
还提供了First()
, Last()
, Eq(index)
等方法来获取Selection
集合中的特定元素,以及Text()
用于获取元素的文本内容,Attr(name)
用于获取元素的属性值。这些方法组合起来,足以应对绝大多数的HTML解析场景。
如何让Golang爬虫更健壮:并发与数据存储策略?
一个简单的爬虫可能只抓取一两个页面,但如果面对成千上万的页面,甚至需要持续抓取,那么并发和数据存储就成了绕不开的话题。
Go语言天生就是为并发而设计的,goroutine
和channel
是其并发模型的核心。利用它们,我们可以轻松地实现并发抓取,显著提高爬取效率。
基本的并发抓取思路是:
- 生产者-消费者模型:一个或多个
goroutine
负责生成待抓取的URL(生产者),将URL发送到一个channel
中。 - 工作池模型:多个
goroutine
作为消费者,从channel
中接收URL,然后并发地执行抓取和解析任务。 - 结果收集:抓取到的数据也可以通过另一个
channel
发送给一个专门的goroutine
进行统一处理或存储。 - 同步等待:使用
sync.WaitGroup
来等待所有抓取goroutine
完成任务。
// 这是一个简化的并发抓取框架示例 func worker(id int, urls <-chan string, results chan<- string, wg *sync.WaitGroup) { defer wg.Done() for url := range urls { fmt.Printf("工作者 %d 正在抓取: %s\n", id, url) // 模拟抓取和解析 // resp, err := http.Get(url) // doc, err := goquery.NewDocumentFromReader(resp.Body) // ... 实际的抓取解析逻辑 time.Sleep(time.Millisecond * 500) // 模拟网络延迟和处理时间 results <- fmt.Sprintf("抓取完成: %s", url) } } func main() { // ... 前面省略的导入和主函数开头 urlsToCrawl := []string{ "http://example.com/page1", "http://example.com/page2", "http://example.com/page3", // ... 更多URL } numWorkers := 5 // 设定并发工作者数量 urls := make(chan string, len(urlsToCrawl)) results := make(chan string, len(urlsToCrawl)) var wg sync.WaitGroup // 启动工作者goroutine for i := 1; i <= numWorkers; i++ { wg.Add(1) go worker(i, urls, results, &wg) } // 将URL发送到urls channel for _, url := range urlsToCrawl { urls <- url } close(urls) // 关闭urls channel,通知worker没有更多URL了 // 等待所有worker完成 wg.Wait() close(results) // 关闭results channel // 收集并处理结果 for res := range results { fmt.Println(res) } fmt.Println("所有抓取任务完成。") }
当然,并发抓取还需要考虑速率限制(避免对目标网站造成过大压力,甚至被封禁IP)、错误重试机制、IP代理池等,这些都是让爬虫更健壮的进阶话题。
数据存储方面,取决于你的需求和数据量:
- 简单场景:如果数据量不大,直接打印到控制台,或者保存到TXT文件、CSV文件(使用
encoding/csv
包)或JSON文件(使用encoding/json
包)都是不错的选择。CSV适合结构化表格数据,JSON适合半结构化数据。 - 中等规模:可以考虑使用SQLite。它是一个轻量级的嵌入式数据库,不需要独立的服务器进程,直接以文件形式存在,非常适合本地开发和中小型爬虫项目。Go有成熟的SQLite驱动。
- 大规模和持久化:对于需要长期存储、支持复杂查询和高并发读写的数据,关系型数据库(如PostgreSQL、MySQL)或NoSQL数据库(如MongoDB、Redis)是更好的选择。Go的标准库
database/sql
配合相应的驱动可以很方便地操作这些数据库。
选择哪种存储方式,最终还是取决于你爬取的数据量、数据结构以及后续如何使用这些数据。
好了,本文到此结束,带大家了解了《Golang简单爬虫实现教程》,希望本文对你有所帮助!关注golang学习网公众号,给大家分享更多Golang知识!

- 上一篇
- Golang反射处理类型别名,Unwrap使用时机解析

- 下一篇
- Golang中SAGA事务与补偿机制详解
-
- Golang · Go教程 | 7分钟前 |
- Go语言括号换行错误怎么解决
- 130浏览 收藏
-
- Golang · Go教程 | 10分钟前 |
- Go中如何安全读取N字节数据
- 314浏览 收藏
-
- Golang · Go教程 | 12分钟前 |
- Golang通道详解:通信与select多路复用技巧
- 190浏览 收藏
-
- Golang · Go教程 | 22分钟前 |
- OpenBSD部署Golang及libc解决方法
- 353浏览 收藏
-
- Golang · Go教程 | 30分钟前 |
- Go中template_test包使用教程
- 199浏览 收藏
-
- Golang · Go教程 | 37分钟前 |
- Go语言time包定时任务常见问题解析
- 265浏览 收藏
-
- Golang · Go教程 | 39分钟前 |
- Golang边缘计算优化技巧分享
- 291浏览 收藏
-
- Golang · Go教程 | 40分钟前 |
- Golang构建Serverless微服务:集成AWSLambda方法
- 204浏览 收藏
-
- Golang · Go教程 | 40分钟前 |
- Go语言多行字符串使用技巧
- 310浏览 收藏
-
- Golang · Go教程 | 40分钟前 |
- Go通道异步注册表深入解析
- 301浏览 收藏
-
- Golang · Go教程 | 49分钟前 |
- Go语言图像处理技巧:缩放与锐化方法
- 121浏览 收藏
-
- Golang · Go教程 | 56分钟前 |
- Golang并发限制与channel/mutex选择指南
- 374浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 511次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 498次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- 千音漫语
- 千音漫语,北京熠声科技倾力打造的智能声音创作助手,提供AI配音、音视频翻译、语音识别、声音克隆等强大功能,助力有声书制作、视频创作、教育培训等领域,官网:https://qianyin123.com
- 170次使用
-
- MiniWork
- MiniWork是一款智能高效的AI工具平台,专为提升工作与学习效率而设计。整合文本处理、图像生成、营销策划及运营管理等多元AI工具,提供精准智能解决方案,让复杂工作简单高效。
- 169次使用
-
- NoCode
- NoCode (nocode.cn)是领先的无代码开发平台,通过拖放、AI对话等简单操作,助您快速创建各类应用、网站与管理系统。无需编程知识,轻松实现个人生活、商业经营、企业管理多场景需求,大幅降低开发门槛,高效低成本。
- 172次使用
-
- 达医智影
- 达医智影,阿里巴巴达摩院医疗AI创新力作。全球率先利用平扫CT实现“一扫多筛”,仅一次CT扫描即可高效识别多种癌症、急症及慢病,为疾病早期发现提供智能、精准的AI影像早筛解决方案。
- 179次使用
-
- 智慧芽Eureka
- 智慧芽Eureka,专为技术创新打造的AI Agent平台。深度理解专利、研发、生物医药、材料、科创等复杂场景,通过专家级AI Agent精准执行任务,智能化工作流解放70%生产力,让您专注核心创新。
- 191次使用
-
- Golangmap实践及实现原理解析
- 2022-12-28 505浏览
-
- 试了下Golang实现try catch的方法
- 2022-12-27 502浏览
-
- Go语言中Slice常见陷阱与避免方法详解
- 2023-02-25 501浏览
-
- Golang中for循环遍历避坑指南
- 2023-05-12 501浏览
-
- Go语言中的RPC框架原理与应用
- 2023-06-01 501浏览