当前位置: 首页 > news >正文

Go语言爬虫系列教程(二) HTTP请求与响应处理详解

第2课:HTTP请求与响应处理详解

1. Go标准库net/http详解

在Go语言中,net/http包是处理HTTP请求的标准库,它提供了强大而简洁的API。下面我们来了解如何创建和配置一个HTTP客户端:

1.1 HTTP客户端

package mainimport ("fmt""net/http""time""io"
)func main() {// 创建自定义HTTP客户端client := &http.Client{// 设置整体请求超时时间为10秒Timeout: 10 * time.Second,// Transport用于配置HTTP传输的细节Transport: &http.Transport{// 最大空闲连接数MaxIdleConns: 10,// 每个主机最大连接数MaxConnsPerHost: 10,// 空闲连接在关闭前的最大存活时间IdleConnTimeout: 30 * time.Second,},}// 发送GET请求fmt.Println("发送请求中...")resp, err := client.Get("https://httpbin.org/get")if err != nil {fmt.Printf("请求发生错误: %v\n", err)return}// 重要:记得关闭响应体defer resp.Body.Close()// 读取响应体内容body, err := io.ReadAll(resp.Body)if err != nil {fmt.Printf("读取响应体失败: %v\n", err)return}// 打印响应详情fmt.Printf("状态码: %s\n", resp.Status)fmt.Printf("响应头: %v\n", resp.Header)fmt.Printf("响应体长度: %d 字节\n", len(body))fmt.Printf("响应内容: %s\n", body)
}

代码解释:

  • http.Client是Go中发送HTTP请求的主要结构体
  • Timeout参数控制整个请求的超时时间(包括连接、发送和接收数据)
  • Transport允许我们自定义HTTP传输层的行为
  • MaxIdleConnsMaxConnsPerHost帮助控制连接池的大小
  • defer resp.Body.Close()是必不可少的,防止资源泄漏

在爬虫开发中,设置请求超时非常重要,可以避免因单个请求卡住导致整个爬虫停止

1.2 创建自定义POST请求

有时候,我们需要更精细地控制HTTP请求的内容,例如指定请求方法、添加请求体或自定义请求头:

package mainimport ("fmt""net/http""strings""io"
)func main() {// 创建带有JSON请求体的POST请求jsonData := `{"username": "clown5", "password": "123456"}`body := strings.NewReader(jsonData)// 使用http.NewRequest创建自定义请求req, err := http.NewRequest("POST", "https://httpbin.org/post", body)if err != nil {fmt.Printf("创建请求失败: %v\n", err)return}// 设置Content-Type为JSONreq.Header.Set("Content-Type", "application/json")// 设置额外的请求头req.Header.Set("X-API-Key", "your_api_key")// 创建HTTP客户端并发送请求client := &http.Client{}fmt.Println("发送POST请求...")resp, err := client.Do(req)if err != nil {fmt.Printf("发送请求失败: %v\n", err)return}defer resp.Body.Close()// 读取并显示响应respBody, err := io.ReadAll(resp.Body)if err != nil {fmt.Printf("读取响应失败: %v\n", err)return}fmt.Printf("状态码: %s\n", resp.Status)fmt.Printf("响应体: %s\n", respBody)
}

1.3 创建自定义GET请求

当然我们也可以通过这个方法,创建GET请求 :

// 创建请求req, err := http.NewRequest("GET", "https://httpbin.org/get", nil)//请求载荷设置为nil即可if err != nil {log.Fatalf("创建请求失败: %v", err)}

1.4 创建HTTP客户端(带代理)

为了针对访问频繁,我们可以通过不断的切换代理IP来访问请求, 或者像Google、Ins这里网站需要VPN才能访问,我们也可以通过添加代理地址来访问。

	//只需要增加http.TransportproxyURL, err := url.Parse(ProxyURL)if err != nil {return nil, fmt.Errorf("解析代理URL失败: %v", err)}transport := &http.Transport{Proxy: http.ProxyURL(proxyURL),TLSClientConfig: &tls.Config{InsecureSkipVerify: true,},}client := &http.Client{Timeout:   30 * time.Second,Transport: transport,}

2. 设置请求头和Cookie

在实际爬虫开发中,我们常常需要设置各种HTTP头部和请求参数来模拟浏览器行为或满足网站要求。

2.1 管理请求头

请求头对于与Web服务器通信至关重要,合适的请求头可以帮助我们:

  • 表明客户端身份(User-Agent)
  • 指定接受的内容类型(Accept)
  • 控制缓存行为(Cache-Control)
  • 传递认证信息(Authorization)
package mainimport ("fmt""net/http""io"
)func main() {// 创建新的GET请求req, err := http.NewRequest("GET", "https://httpbin.org/headers", nil)if err != nil {fmt.Printf("创建请求失败: %v\n", err)return}// 设置模拟真实浏览器的请求头req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")req.Header.Set("Accept-Encoding", "gzip, deflate, br")req.Header.Set("Connection", "keep-alive")req.Header.Set("Cache-Control", "max-age=0")req.Header.Set("Sec-Ch-Ua", "\"Not A(Brand\";v=\"99\", \"Google Chrome\";v=\"120\", \"Chromium\";v=\"120\"")req.Header.Set("Sec-Ch-Ua-Mobile", "?0")req.Header.Set("Sec-Ch-Ua-Platform", "\"Windows\"")// 添加自定义头部req.Header.Add("X-Requested-With", "XMLHttpRequest")// 发送请求client := &http.Client{}resp, err := client.Do(req)if err != nil {fmt.Printf("请求失败: %v\n", err)return}defer resp.Body.Close()// 读取响应body, err := io.ReadAll(resp.Body)if err != nil {fmt.Printf("读取响应失败: %v\n", err)return}// 打印状态和响应内容fmt.Printf("状态码: %s\n", resp.Status)fmt.Printf("响应体: %s\n", body)// 打印所有响应头fmt.Println("\n所有响应头:")for key, values := range resp.Header {for _, value := range values {fmt.Printf("%s: %s\n", key, value)}}
}
  • User-Agent告诉服务器关于客户端浏览器和操作系统的信息,现代网站通常使用请求头来防止爬虫,所以合理设置这些头部很重要
  • Set()方法会覆盖已有的头部值,而Add()方法会添加新值(不替换已有值),请求头的名称是大小写不敏感的(Header会自动规范化名称)

2.2 使用Cookie获取登录状态

有很多数据需要我们登录才能访问,那么怎么获取登录状态呢?大部分网站登录后,都会生成一个cookie,这个cookie就保存了我们账号的登录状态(有的网站可能用的 Authorization)。

当账号登录后,我们只需要使用浏览器的开发者工具, 从任意一个请求获取到cookie,然后添加到请求头即可。

实际上很多网站想要获取数据,还有一些其他的自定义请求,只有cookie是不够的

下面我们通过一个代码来简单的演示下,我们使用这个地址作为测试https://www.ghxi.com/userset ,访问账号设置, 如果账号登录应该是下面的界面

在这里插入图片描述

但是如果没登录直接访问,会跳转到登录界面

package mainimport ("fmt""bytes""mime/multipart""net/http""io"
)func main() {cookie :=`设置为你自己的cookie`url := "https://www.ghxi.com/wp-admin/admin-ajax.php"method := "POST"payload := &bytes.Buffer{}writer := multipart.NewWriter(payload)_ = writer.WriteField("action", "wpcom_is_login")err := writer.Close()if err != nil {fmt.Println(err)return}client := &http.Client {}req, err := http.NewRequest(method, url, payload)if err != nil {fmt.Println(err)return}req.Header.Add("cookie", cookie)req.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36")req.Header.Set("Content-Type", writer.FormDataContentType())res, err := client.Do(req)if err != nil {fmt.Println(err)return}defer res.Body.Close()body, err := io.ReadAll(res.Body)if err != nil {fmt.Println(err)return}fmt.Println(string(body))
}

运行测试,如果没有添加cookie,输出的内容会包含登录账号的字段,如果正确的添加cookie,输出的内容会包含账号信息

3. 请求重试机制

我们在爬虫的时候,可能会因为网络波动等原因 ,导致访问失败,这时候就需要引入重试机制,避免数据遗漏。

package mainimport ("fmt""net/http""time"
)func retryRequest(url string, maxRetries int) (*http.Response, error) {var lastErr errorfor attempt := 1; attempt <= maxRetries; attempt++ {client := &http.Client{Timeout: 5 * time.Second,}resp, err := client.Get(url)if err == nil && resp.StatusCode < 500 {return resp, nil}if resp != nil {resp.Body.Close()}lastErr = errif err == nil {lastErr = fmt.Errorf("server error: %d", resp.StatusCode)}fmt.Printf("Attempt %d failed: %v\n", attempt, lastErr)if attempt < maxRetries {// 指数退避waitTime := time.Duration(attempt) * time.Secondfmt.Printf("Waiting %v before retry...\n", waitTime)time.Sleep(waitTime)}}return nil, fmt.Errorf("all %d attempts failed: %v", maxRetries, lastErr)
}func main() {url := "https://httpbin.org/status/500"resp, err := retryRequest(url, 3)if err != nil {fmt.Printf("Final error: %v\n", err)return}defer resp.Body.Close()fmt.Printf("Success! Status: %s\n", resp.Status)
}

4. 实战:爬取静态网页

让我们创建一个实际的爬虫示例,爬取一个引用网站(quotes.toscrape.com)并提取所有引言

4.1 定义爬虫结构

// Crawler 表示一个爬虫
type Crawler struct {client    *http.Client  // HTTP客户端userAgent string        // 浏览器标识delay     time.Duration // 请求间隔时间headers   map[string]string
}// NewCrawler 创建一个新的爬虫实例
func NewCrawler(timeout time.Duration, delay time.Duration) *Crawler {return &Crawler{client: &http.Client{Timeout: timeout,},userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",delay:     delay,}
}

4.2 定义错误结构


// 定义错误类型
type ErrorType stringconst (ErrorRequest  ErrorType = "请求错误"ErrorResponse ErrorType = "响应错误"ErrorStatus   ErrorType = "状态码错误"
)// CrawlerError 代表爬虫错误
type CrawlerError struct {Type    ErrorTypeMessage stringErr     error
}func (e *CrawlerError) Error() string {if e.Err != nil {return e.Message + ": " + e.Err.Error()}return e.Message
}

4.3 实现基本的获取页面功能


// SetHeaders 设置自定义HTTP头部
func (c *Crawler) SetHeaders(headers map[string]string) {// 如果设置了User-Agent,则更新crawler的userAgent属性if ua, ok := headers["User-Agent"]; ok {c.userAgent = uadelete(headers, "User-Agent") // 从map中删除,避免重复设置}c.headers = headers
}// Fetch 获取指定URL的网页内容
func (c *Crawler) Fetch(url string) ([]byte, error) {// 创建请求req, err := http.NewRequest("GET", url, nil)if err != nil {return nil, &CrawlerError{Type:    ErrorRequest,Message: "创建请求失败",Err:     err,}}// 设置头部req.Header.Set("User-Agent", c.userAgent)// 发送请求resp, err := c.client.Do(req)if err != nil {return nil, &CrawlerError{Type:    ErrorRequest,Message: "发送请求失败",Err:     err,}}defer resp.Body.Close()// 检查状态码if resp.StatusCode != http.StatusOK {return nil, &CrawlerError{Type:    ErrorStatus,Message: "非正常状态码: " + resp.Status,}}// 读取响应体body, err := ioutil.ReadAll(resp.Body)if err != nil {return nil, &CrawlerError{Type:    ErrorResponse,Message: "读取响应失败",Err:     err,}}// 返回页面内容return body, nil
}

4.4 爬取静态网页

下面是,我们希望获取到的内容,为了获取到内容,我使用正则表达式来匹配

<span class="text" itemprop="text">“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”</span
<small class="author" itemprop="author">Albert Einstein</small>
func main() {// 创建爬虫实例crawler := NewCrawler(10*time.Second, 1*time.Second)// 设置自定义头部crawler.SetHeaders(map[string]string{"Accept":          "text/html,application/xhtml+xml,application/xml","Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",})// 定义目标URLtargetURL := "https://quotes.toscrape.com/"fmt.Printf("开始爬取网站: %s\n", targetURL)// 获取页面内容content, err := crawler.Fetch(targetURL)if err != nil {log.Fatalf("爬取失败: %v", err)}// 提取引言(这里使用正则表达式作为示例,后面章节会介绍更好的HTML解析方法)quoteRegex := regexp.MustCompile(`<span class="text" itemprop="text">(.*?)</span>`)authorRegex := regexp.MustCompile(`<small class="author" itemprop="author">(.*?)</small>`)quoteMatches := quoteRegex.FindAllStringSubmatch(string(content), -1)authorMatches := authorRegex.FindAllStringSubmatch(string(content), -1)if len(quoteMatches) == 0 {log.Fatal("未找到任何引言")}// 创建结果文件file, err := os.Create("quotes.txt")if err != nil {log.Fatalf("创建文件失败: %v", err)}defer file.Close()// 写入引言for i, quote := range quoteMatches {if i < len(authorMatches) {// 清理HTML实体cleanQuote := strings.ReplaceAll(quote[1], "&quot;", "\"")cleanQuote = strings.ReplaceAll(cleanQuote, "&#39;", "'")// 写入文件line := fmt.Sprintf("%s - %s\n\n", cleanQuote, authorMatches[i][1])file.WriteString(line)// 打印到控制台fmt.Println(line)}}fmt.Printf("爬取完成,共获取 %d 条引言,已保存到 quotes.txt\n", len(quoteMatches))
}

在这个示例中,我们使用了正则表达式来提取网页中的引言和作者信息。这种方法适用于简单的提取任务,但对于复杂的HTML解析,我们需要使用专门的HTML解析库,这将在下一章介绍

相关文章:

  • 鸿蒙ArkTS-List列表下拉刷新案例
  • DEBUG设置为False 时,django默认的后台样式等静态文件丢失的问题
  • OpenHarmony 5.0中状态栏添加以太网状态栏图标以及功能实现
  • Vue3 + ThinkPHP8 + PHP8.x 生态与 Swoole 增强方案对比分析
  • Linux之Ext系列文件系统(含动静态库)
  • ansible剧本和角色的使用,部署lnmp
  • Laravel 连接 SQL Server 之 Linux 系统安装 unixODBC 和 Microsoft ODBC 驱动
  • 【工具类】常用的工具类——CollectionUtil
  • 红帽企业 Linux 10:探索全新生成式 AI 助手!
  • JDK21全景图:关键特性与升级价值
  • Python爬虫(34)Python爬虫高阶:动态页面处理与Playwright增强控制深度解析
  • MCP如何助力智能交通系统?从数据融合到精准决策
  • 图纸加密软件的核心优势解析
  • C++11-(3)
  • 文章记单词 | 第104篇(六级)
  • PostgreSQL 用户权限与安全管理
  • 610Hz!无惧环境光新薄膜!ROG全新电竞显示器亮相2025台北电脑展
  • 阿里云服务器 篇十四:图片库网站
  • 应届本科生简历制作指南
  • 嵌入式学习笔记 - Void类型的指针
  • 做网站有哪些软件/网络营销企业是什么
  • 网站设计制作策划书/英语培训
  • 太原网站科技公司/网站设计制作哪家好
  • 泰安网站建设优化技术/安徽seo优化规则
  • 做网站上传图片/自媒体平台
  • 织梦做的网站首页打不开/广州seo