Go语言爬虫常用错误处理策略包括:网络错误重试并配合指数退避,根据HTTP状态码区分客户端与服务器错误以决定重试逻辑,解析失败时校验HTML格式与编码,数据提取时判断空值;通过context控制超时,用fmt.Errorf包装错误保留上下文,确保爬虫健壮性。

在Go语言里,想写个小爬虫,
net/http
goquery
一个简单的Go爬虫,利用
net/http
goquery
package main
import (
"fmt"
"log"
"net/http"
"net/url" // 用于处理相对路径
"strings"
"time"
"github.com/PuerkitoBio/goquery"
)
// SimpleCrawlerConfig 定义爬虫配置
type SimpleCrawlerConfig struct {
TargetURL string
Timeout time.Duration
}
// CrawlResult 定义爬取结果
type CrawlResult struct {
Title string
Links []string
}
// crawlPage 抓取并解析单个页面
func crawlPage(config SimpleCrawlerConfig) (*CrawlResult, error) {
fmt.Printf("正在尝试抓取: %s\n", config.TargetURL)
client := &http.Client{
Timeout: config.Timeout, // 设置请求超时
}
req, err := http.NewRequest("GET", config.TargetURL, nil)
if err != nil {
return nil, fmt.Errorf("创建请求失败: %w", err)
}
// 可以设置User-Agent等请求头,模拟浏览器
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")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("HTTP请求失败: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP状态码异常: %d %s", resp.StatusCode, resp.Status)
}
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return nil, fmt.Errorf("解析HTML失败: %w", err)
}
result := &CrawlResult{}
result.Title = doc.Find("title").Text()
baseURL, err := url.Parse(config.TargetURL)
if err != nil {
log.Printf("解析基础URL失败: %v", err)
baseURL = nil // 继续执行,但相对路径可能不准确
}
doc.Find("a").Each(func(i int, s *goquery.Selection) {
href, exists := s.Attr("href")
if exists && href != "" {
// 处理相对路径和绝对路径
resolvedURL := href
if baseURL != nil {
parsedHref, parseErr := url.Parse(href)
if parseErr == nil {
resolvedURL = baseURL.ResolveReference(parsedHref).String()
}
}
// 简单过滤掉一些非HTTP(S)链接
if strings.HasPrefix(resolvedURL, "http://") || strings.HasPrefix(resolvedURL, "https://") {
result.Links = append(result.Links, resolvedURL)
}
}
})
return result, nil
}
func main() {
config := SimpleCrawlerConfig{
TargetURL: "http://example.com", // 替换成你想抓取的实际URL
Timeout: 10 * time.Second,
}
crawlResult, err := crawlPage(config)
if err != nil {
log.Fatalf("爬取失败: %v", err)
}
fmt.Printf("\n页面标题: %s\n", crawlResult.Title)
fmt.Println("\n提取到的链接:")
for _, link := range crawlResult.Links {
fmt.Println(link)
}
}
说实话,写爬虫,最让人头疼的不是怎么抓取,而是怎么处理那些千奇百怪的错误。网络环境复杂,目标网站也可能随时变动,所以错误处理是构建健壮爬虫的关键。
我们通常会遇到几种错误:
立即学习“go语言免费学习笔记(深入)”;
net/http
error
error
nil
404 Not Found
500 Internal Server Error
403 Forbidden
404
403
User-Agent
Cookie
4xx
5xx
goquery.NewDocumentFromReader
resp.Body
goquery
Find
Selection
.Text()
.Attr()
false
在Go里面,错误处理的哲学就是显式地返回
error
fmt.Errorf
%w
context.Context
Go语言的并发模型,说实话,是它在爬虫领域大放异彩的主要原因。
goroutine
channel
你想啊,一个普通的爬虫,顺序地一个接一个地抓取网页,当它在等待一个网页响应时,CPU其实是闲置的。但如果用
goroutine
goroutine
基本思路是:
goroutine
channel
channel
channel
goroutine
goroutine
URL
channel
channel
一个简单的并发抓取骨架大概是这样:
package main
import (
"fmt"
"log"
"sync"
"time"
)
// 假设 crawlPage 函数如上文定义
func main() {
urlsToCrawl := []string{
"http://example.com",
"http://www.google.com", // 替换为其他可访问的URL
"http://www.baidu.com", // 替换为其他可访问的URL
// 更多URL...
}
var wg sync.WaitGroup
resultsChan := make(chan *CrawlResult, len(urlsToCrawl)) // 缓冲通道,防止阻塞
for _, u := range urlsToCrawl {
wg.Add(1)
go func(url string) {
defer wg.Done()
config := SimpleCrawlerConfig{
TargetURL: url,
Timeout: 5 * time.Second,
}
result, err := crawlPage(config)
if err != nil {
log.Printf("爬取 %s 失败: %v", url, err)
return
}
resultsChan <- result // 将结果发送到通道
}(u)
}
// 等待所有goroutine完成
wg.Wait()
close(resultsChan) // 关闭通道,表示所有结果都已发送
// 从通道接收并处理结果
fmt.Println("\n--- 所有并发爬取结果 ---")
for result := range resultsChan {
fmt.Printf("URL: %s\n", result.Title) // 这里需要知道是哪个URL的标题,CrawlResult需要增加URL字段
fmt.Printf("标题: %s\n", result.Title)
fmt.Printf("链接数量: %d\n", len(result.Links))
fmt.Println("---")
}
}
// 注意:CrawlResult 结构体需要添加一个 OriginalURL 字段
// type CrawlResult struct {
// OriginalURL string
// Title string
// Links []string
// }
// 并在 crawlPage 中设置 OriginalURL = config.TargetURL通过这种方式,你可以把抓取、解析、存储等不同阶段的任务拆分到不同的
goroutine
channel
goquery
goquery
我经常用它来抓取文章标题、图片URL,甚至是一些表格数据,只要有CSS选择器能定位到,基本就没问题。
这里是一些常用的提取方式:
div
p
.Text()
// 假设要提取 class 为 "article-title" 的 h1 标签文本
title := doc.Find("h1.article-title").Text()
fmt.Printf("文章标题: %s\n", title)src
href
data-id
.Attr("属性名")// 提取页面中第一张图片的 src 属性
imgSrc, exists := doc.Find("img").First().Attr("src")
if exists {
fmt.Printf("第一张图片URL: %s\n", imgSrc)
}.Each()
// 提取所有 class 为 "product-item" 的 div 里的商品名称和价格
doc.Find("div.product-item").Each(func(i int, s *goquery.Selection) {
productName := s.Find("h2.product-name").Text()
productPrice := s.Find("span.price").Text()
fmt.Printf("商品 %d: %s - %s\n", i+1, productName, productPrice)
})goquery
>
[attr=value]
:nth-child
// 提取 ID 为 "main-content" 下的第一个段落文本
firstParagraph := doc.Find("#main-content p:first-of-type").Text()
fmt.Printf("主内容区第一段: %s\n", firstParagraph)掌握了这些基本的
goquery
以上就是Golang实现简单爬虫程序 net/http与goquery结合的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号