LOADING

正在加载

爬虫库——net_http库

壹 简介

随着万维网的迅速发展,加上大数据的出现,快速提取并利用大量的有效数据信息成了焦点,网络爬虫也应运而生。网络爬虫是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本(这是百度百科对爬虫的解释)。

在Golang的语言中,我们使用net/httpCollyGoquery等,其中第一个是内置库,可以进行简单的爬虫,而后面两个是爬虫框架,这篇文章主要讲解net/http

贰 安装以及引用

内置库,不需要安装。

引用:

import "net/http"

叁 方法说明

3.1 获取响应

3.1.1 构造请求——http.NewRequest

格式:

func NewRequest(method, url string, body io.Reader) (*Request, error)
// method:请求模式,值一般为:http.Method+请求模式,如http.MethodGet等,下面有请求模式常量
// url:就是请求的URL地址
// body:请求的数据
// 返回请求结构体和错误类型

3.1.2 发起请求——http.DefaultClient.Do

格式:

func (c *Client) Do(req *Request) (*Response, error)
// req:构造的请求体,http.NewRequest返回的值
// 返回响应和错误类型

http.DefaultClient的底层实际是一个&Client{}类型,使用使用http.DefaultClient.Do,就是使用&Client{}类型的Do方法。

3.1.3 请求模式常量

const (
    MethodGet     = "GET"
    MethodHead    = "HEAD"
    MethodPost    = "POST"
    MethodPut     = "PUT"
    MethodPatch   = "PATCH" // RFC 5789
    MethodDelete  = "DELETE"
    MethodConnect = "CONNECT"
    MethodOptions = "OPTIONS"
    MethodTrace   = "TRACE"
)

3.1.4 例子

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    // 构造请求
    // http://httpbin.org/网站是用来测试http的访问
    request, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil)
    if err != nil {
        fmt.Println("[-] http.NewRequest Error:", err)
        return
    }
    // 发起请求
    resp, err := http.DefaultClient.Do(request)
    if err != nil {
        fmt.Println("[-] http.DefaultClient.Do Errot:", err)
        return
    }
    // 使用defer在最后关闭连接
    defer resp.Body.Close()
    // 显示响应信息
    // resp.Body就是响应信息,但是要输出到终端,自己实现比较麻烦,所以需要使用ioutil库进行终端输出
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("[-] ioutil.ReadAll Error:", err)
        return
    }
    // 这里的body是[]uint8类型需要转换成字符串才会打印出来
    fmt.Printf("%s", body)
}

这里需要说明对应Get和Post请求,有现成的http.Gethttp.Post,上面方法是这两种方法的底层,只是官方给出来比较便捷的请求方式,建议使用上面的方法,有利于理解。

3.2 设置请求参数信息(在构造请求时设置)

3.2.1 请求体的常量信息

要想设置请求参数,我们先来了解请求体的常量信息。

type URL struct {
    Scheme      string			// 协议,http或者https等
    Opaque      string    // 编码不透明数据
    User        *Userinfo // 请求的用户名和密码信息
    Host        string    // 请求的IP或者IP:端口
    Path        string    // 请求路径
    RawPath     string    // 编码路径提示
    ForceQuery  bool      // 表示RawQuery带不带?,默认false,带?
    RawQuery    string    // 参数, 不带?
    Fragment    string    // 用于引用的片段,不带#号
    RawFragment string    // 编码片段提示
}

从上面我们可以知道要加入参数我们可以设置RawQuery

3.2.2 追加参数——Add

当然可以直接使用RawQuery设置参数,但是为了方便参数设置,在net.url中存在一个url.Values类型,其底层是map类型,可以通过Add(这个是追加设置)和Set(这个是清空然后再设置)进行设置。

格式:

func (v Values) Set(key, value string)
// key:键
// value:值
// 底层就是追加一个参数

3.2.3 设置参数——Set

格式:

func (v Values) Add(key, value string)
// key:键
// value:值
// 底层就是用append追加一个参数

3.2.4 例子

package main

import (
    "fmt"
    "net/http"
    "net/url"
)

func main() {
    // 构造请求
    request, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil)
    if err != nil {
        fmt.Println("[-] http.NewRequest Error:", err)
        return
    }
    // 设置参数
    // 初始化url.Values类型
    val_url_head := url.Values{}
    // 设置name、age参数,键值都要字符串
    val_url_head.Set("name", "text")
    val_url_head.Set("age", "1")
    // 将参数追加到请求体中
    // 如果直接赋值,是将map赋值给request.URL.RawQuery,而request.URL.RawQuery接收的是字符串,所以需要进行Encode()编码
    request.URL.RawQuery = val_url_head.Encode()
    fmt.Println(val_url_head.Encode()) //age=1&name=text,根据原理我们直接在后面加这个字符串都行
    fmt.Println(request.URL.String())  //http://httpbin.org/get?age=1&name=text
}

3.3 设置请求头信息(在构造请求时设置)

如果我们需要设置请求头,如UA,那么我们需要使用请求体的Header。

type Header map[string][]string
// 可以看到本质是一个map,同样我们可以使用Add和Set,与上面说的url.Values类型差不多

例子:

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    // 构造请求
    request, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil)
    if err != nil {
        fmt.Println("[-] http.NewRequest Error:", err)
        return
    }
    // 使用request.Header.Set设置请求头信息
    request.Header.Set("User-Agent", "as")
    // 发起请求
    resp, err := http.DefaultClient.Do(request)
    if err != nil {
        fmt.Println("[-] http.DefaultClient.Do Errot:", err)
        return
    }
    // 使用defer在最后关闭连接
    defer resp.Body.Close()
    // 显示响应信息
    // resp.Body就是响应信息,但是要输出到终端,自己实现比较麻烦,所以需要使用ioutil库进行终端输出
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("[-] ioutil.ReadAll Error:", err)
        return
    }
    // 这里的body是[]uint8类型需要转换成字符串才会打印出来
    fmt.Printf("%s", body)
}

3.4 设置post请求的数据(在构造请求时设置)

post请求往往需要请求数据,这些数据可能是data、json、文件等格式,我们可以在http.NewRequest的第三个body参数中设置请求数据,注意需要的是io.Reader类型。

3.4.1 data格式的请求

对于data格式的请求,我们可以使用传入参数,但是由于我们使用url.Values构造的数据是string类型,所以我们可以使用strings.NewReader方法将字符串转换为io.Reader类型。

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
    "strings"
)

func main() {
    // 可以直接在url.Values{}加参数,不过为了讲解多种方法,这里就忽略了
    data := url.Values{}
    data.Set("name", "A7cc")
    data.Set("Age", "18")
    // 构造请求
    // 可以看到第三个是请求信息data,
    request, err := http.NewRequest(http.MethodPost, "http://httpbin.org/post", strings.NewReader(data.Encode()))
    if err != nil {
        fmt.Println("[-] http.NewRequest Error:", err)
        return
    }
    // 发起请求
    resp, err := http.DefaultClient.Do(request)
    if err != nil {
        fmt.Println("[-] http.DefaultClient.Do Errot:", err)
        return
    }
    // 使用defer在最后关闭连接
    defer resp.Body.Close()
    // 显示响应信息
    // resp.Body就是响应信息,但是要输出到终端,就需要使用ioutil库进行终端输出
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("[-] ioutil.ReadAll Error:", err)
        return
    }
    // 这里的body是[]uint8类型需要转换成字符串才会打印出来
    fmt.Printf("%s", body)
}

3.4.2 json格式的请求

对于json格式的请求,我们使用的是json格式,那么我们就需要json库。

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    // 构造一个结构体类型
    jsondata := struct {
        // 可以看到属性改为json格式
        Name string `json:"name"`
        Age  int    `json:"age"`
    }{
        Name: "A7cc",
        Age:  18,
    }
    // 进行反序列化
    data, _ := json.Marshal(jsondata)
    // 构造请求
    // 可以看到第三个是请求信息data,由于是byte格式使用使用bytes
    request, err := http.NewRequest(http.MethodPost, "http://httpbin.org/post", bytes.NewReader(data))
    if err != nil {
        fmt.Println("[-] http.NewRequest Error:", err)
        return
    }

    // 发起请求
    resp, err := http.DefaultClient.Do(request)
    if err != nil {
        fmt.Println("[-] http.DefaultClient.Do Errot:", err)
        return
    }
    // 使用defer在最后关闭连接
    defer resp.Body.Close()
    // 显示响应信息
    // resp.Body就是响应信息,但是要输出到终端,就需要使用ioutil库进行终端输出
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("[-] ioutil.ReadAll Error:", err)
        return
    }
    // 这里的body是[]uint8类型需要转换成字符串才会打印出来
    fmt.Printf("%s", body)
}

3.4.3 文件格式的请求(了解就行)

对于文件上传,我们需要构建文件上传body,同时在请求头需要设置multipart/form-data,在传统的Post数据请求时,只能上传名字,例如文件名,而文件内部的数据,在这种情况下是不能上传的,所以需要设置multipart/form-data类型,来上传文件数据。

package main

import (
    "bytes"
    "fmt"
    "io"
    "io/ioutil"
    "mime/multipart"
    "net/http"
    "os"
)

func postFile() {
    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)
    // 这里这步操作是设置参数
    // WriteField方法需要两个参数,第一个是请求的字段名,第二个是值
    writer.WriteField("words", "123")

    // 这里这步操作是上传文件
    // CreateFormFile方法需要两个参数,第一个是请求的字段名,第二个是文件名
    upfilewriter1, _ := writer.CreateFormFile("uploadfile1", "file1")
    // 打开文件
    upfile1, _ := os.Open("./file1")
    // 关闭
    defer upfile1.Close()
    // 将文件copy到body中
    io.Copy(upfilewriter1, upfile1)
    // 第二个文件
    upfilewriter2, _ := writer.CreateFormFile("uploadfile2", "file2")
    // 打开文件
    upfile2, _ := os.Open("./file2")
    // 关闭
    defer upfile2.Close()
    // 将文件copy到body中
    io.Copy(upfilewriter2, upfile2)
    // 这里是为了闭合文件字,而且这是构造完文件数据和其他上传的参数后才执行的
    writer.Close()

    // 输出
    // 输出文件的上传数据
    fmt.Println(body.String())
    // 输出multipart/form-data
    fmt.Println(writer.FormDataContentType())

    // 请求
    resp, err := http.Post("http://httpbin.org/post", writer.FormDataContentType(), body)
    if err != nil {
        fmt.Println("err:http.Post")
    }
    // 关闭
    defer resp.Body.Close()
    data, _ := ioutil.ReadAll(resp.Body)
    // 输出
    fmt.Printf("%s", data)
}

func main() {
    postFile()
}

3.5 获取响应状态码(在获取响应后设置)

如果需要获取响应码,我们可以通过Status(状态码信息,string类型)和StatusCode(单纯状态码,int类型)。

// 构造请求
    request, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil)
    if err != nil {
        fmt.Println("[-] http.NewRequest Error:", err)
        return
    }
    // 发起请求
    resp, err := http.DefaultClient.Do(request)
    if err != nil {
        fmt.Println("[-] http.DefaultClient.Do Errot:", err)
        return
    }
    // 使用defer在最后关闭连接
    defer resp.Body.Close()
    // 获取响应状态码
    fmt.Println("状态信息:", resp.Status, "\t状态码:", resp.StatusCode) // 状态信息: 200 OK       状态码: 200

3.6 获取响应头(在获取响应后设置)

如果我们要获取响应头信息,可以使用resp.Header,返回的是map类型,所以可以使用对应的键指定。

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    // 构造请求
    request, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil)
    if err != nil {
        fmt.Println("[-] http.NewRequest Error:", err)
        return
    }
    // 发起请求
    resp, err := http.DefaultClient.Do(request)
    if err != nil {
        fmt.Println("[-] http.DefaultClient.Do Errot:", err)
        return
    }
    // 使用defer在最后关闭连接
    defer resp.Body.Close()
    fmt.Println("响应头信息:", resp.Header) //响应头信息: map[Access-Control-Allow-Credentials:[true] Access-Control-Allow-Origin:[*] Connection:[keep-alive] Content-Length:[270] Content-Type:[application/json] Date:[Mon, 04 Apr 2022 16:50:01 GMT] Server:[gunicorn/19.9.0]]
    fmt.Println("响应头信息:", resp.Header.Get("Content-Type"))	// 响应头信息: application/json
    // 如果要获取单个,在这里建议使用Get,而不是map的键值对获取方式,因为Get可以区分大小写
}

3.7 获取响应编码(在获取响应后设置)

  • 通过content-type="text/html;charset=utf-8"
  • html的head标签中的meta标签,例如:<meta http-equiv="content-type" content="text/html;charset=utf-8">
  • 通过网页的头部进行猜测,比较麻烦,一般情况下前面两种就可以识别绝大多数编码。

这里介绍使用库golang.org/x/net/html——获取编码信息,

package main

import (
    "bufio"
    "fmt"
    "net/http"

    "golang.org/x/net/html/charset"
)

func main() {
    // 构造请求
    request, err := http.NewRequest(http.MethodGet, "https://baidu.com", nil)
    if err != nil {
        fmt.Println("[-] http.NewRequest Error:", err)
        return
    }
    // 发起请求
    resp, err := http.DefaultClient.Do(request)
    if err != nil {
        fmt.Println("[-] http.DefaultClient.Do Errot:", err)
        return
    }
    // 使用defer在最后关闭连接
    defer resp.Body.Close()
    // 下面就是获取编码
    // 获取前1024个字节
    r := bufio.NewReader(resp.Body)
    b, err := r.Peek(1024)
    if err != nil {
        fmt.Println("[-] r.Peek Errot:", err)
        return
    }
    // 返回值有三个
    // 第一个是编码的结构体
    // 第二个是编码的名称,可以忽略
    // 第三个是是否是正确的编码,可以忽略
    e, _, _ := charset.DetermineEncoding(b, resp.Header.Get("content-type"))
    // 获取成功并输出
    fmt.Println(e)
}

3.8 重定向的限制与禁止(在构造请求时设置)

当我们要限制或者禁用重定向时,我们可以通过重写ClientCheckRedirect这个方法,在http请求中这个方法就是对重定向进行设置的,由于发起请求的底层就是使用了&Client{}Do方法。其实在http.DefaultClient里面有限制重定向次数,次数为10,原理和我们现在说的一样,获取via(已经请求过的请求,就是历史请求)的长度来做到限制。

上面是限制重定向次数,那么如果禁止重定向,我们只需要判断是否存在重定向就行,不需要判断via的值长度,返回错误。

package main

import (
    "errors"
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    // 限制重定向次数
    // 由于发起请求的底层就是使用了&Client{}的Do方法,使用我们这里重写Client的CheckRedirect这个方法,在http请求中这个方法就是对重定向进行设置的
    // 在这里我们直接将func(req *http.Request, via []*http.Request) error这个匿名方法赋值给http.DefaultClient.CheckRedirect也是可以的,这里由于想扩展开来说,就新建了一个&http.Client类型,http.DefaultClient本质就是&http.Client类型
    client := &http.Client{
        CheckRedirect: func(req *http.Request, via []*http.Request) error {
            // req是这次的请求
            // via是已经请求过的请求,就是历史请求,通过获取历史请求的长度来做到限制
            fmt.Println(len(via))
            if len(via) > 1 {
                // 这是给一个报错,然后终止请求
                return errors.New("超过重定向次数")
                // 如果需要进行请求我们可以返回一个不进行下次请求进入上次请求的结果错误
                // return http.ErrUseLastResponse
            }
            return nil
        },
    }
    // 	// 禁止重定向
    // 	client := &http.Client{
    // 		CheckRedirect: func(req *http.Request, via []*http.Request) error {
    // 			// 当发生重定向时我们直接返回一个不进行下次请求进入上次请求的结果错误
    // 			return http.ErrUseLastResponse
    // 		},
    // 	}
    // 构造请求
    request, err := http.NewRequest(http.MethodGet, "https://image.baidu.com/asasa", nil)
    if err != nil {
        fmt.Println("[-] http.NewRequest Error:", err)
        return
    }

    // 发起请求
    resp, err := client.Do(request)
    if err != nil {
        fmt.Println("[-] http.DefaultClient.Do Errot:", err)
        return
    }
    // 使用defer在最后关闭连接
    defer resp.Body.Close()
    // 显示响应状态码信息
    fmt.Println(resp.StatusCode)
    // 显示最后请求的URL
    fmt.Println(resp.Request.URL)
}

3.9 cookie(看情况)

cookie一般用来维持http连接的会话状态,具体的cookie介绍可以看这篇文章,这里我们主要讲解Go语言如何使用cookie。

要想获取cookie,我们需要访问网站,然后使用Cookies()从A(当前响应)中获取cookie,接着我们打开另一个请求B,将之前A的cookie赋值给现在这个请求B,这时我们可以使用B的AddCookie方法发起请求,这样就实现了Cookie请求。

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    // 这里之所以禁用重定向是因为这个例子http://httpbin.org/cookies/set在获取cookie时会重定向到http://httpbin.org/cookies,这时候cookie会被清空,所以使用禁用重定向
    http.DefaultClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
        return http.ErrUseLastResponse
    }
    // 构造第一次请求
    request1, err := http.NewRequest(http.MethodGet, "http://httpbin.org/cookies/set?name=a7cc&passwd=123456", nil)
    if err != nil {
        fmt.Println("[-] http.NewRequest Error:", err)
        return
    }

    // 发起第一次请求
    resp1, err := http.DefaultClient.Do(request1)
    if err != nil {
        fmt.Println("[-] client.Do Errot:", err)
        return
    }
    // 使用defer在最后关闭连接
    defer resp1.Body.Close()
    // 构造第二次请求
    request2, err := http.NewRequest(http.MethodGet, "http://httpbin.org/cookies", nil)
    if err != nil {
        fmt.Println("[-] http.NewRequest Error:", err)
        return
    }
    // 将第一次响应的Cookie赋值给第二次请求
    // 这里之所以用client.Do而不用http.DefaultClient.Do(),是因为这个例子的网站有重定向问题,如果没有我们可以直接赋值
    for _, cookie := range resp1.Cookies() {
        request2.AddCookie(cookie)
    }
    // 发起第二次请求
    resp2, err := http.DefaultClient.Do(request2)
    if err != nil {
        fmt.Println("[-] client.Do Errot:", err)
        return
    }
    // 使用defer在最后关闭连接
    defer resp2.Body.Close()
    // 显示响应状态码信息
    body, err := ioutil.ReadAll(resp2.Body)
    if err != nil {
        fmt.Println("[-] ioutil.ReadAll Errot:", err)
        return
    }
    fmt.Printf("Cookie:%s", body)
}

3.9.2 cookiejar

上面的例子显然太繁琐,那么我们要如何简化呢?Go语言使用一个叫Cookie jar的设置来对cookie进行存储设置。我们只需要实例化一个cookiejar,然后将这个cookiejar赋值给发起请求操作的http.DefaultClientJar中。

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "net/http/cookiejar"
)

func main() {
    // 实例化一个cookiejar
    val_jar, err := cookiejar.New(nil)
    if err != nil {
        fmt.Println("[-] cookiejar.New Errot:", err)
        return
    }
    // 将创建的cookiejar赋值给要发起请求的http.DefaultClient中
    http.DefaultClient.Jar = val_jar

    // 构造第一次请求
    request1, err := http.NewRequest(http.MethodGet, "http://httpbin.org/cookies/set?name=a7cc&passwd=123456", nil)
    if err != nil {
        fmt.Println("[-] http.NewRequest Error:", err)
        return
    }
    // 发起第一次请求
    resp1, err := http.DefaultClient.Do(request1)
    if err != nil {
        fmt.Println("[-] client.Do Errot:", err)
        return
    }
    // 使用defer在最后关闭连接
    defer resp1.Body.Close()
    // 到了这里就可以再次使用http.DefaultClient进行请求了,因为这个已经包含了cookie,直接使用http.DefaultClient.Do去请求登陆后的链接即可,由于没有现成的环境,我们进行简单的描述即可,下面的这个例子如果拿去运行是会出现404页面的,因为这里没有实际的例子去访问,就照猫画虎搞了一下
    request2, err := http.NewRequest("GET", "http://httpbin.org/cookies/get?name=a7cc&passwd=123456", nil)
    if err != nil {
        log.Panicln(err)
    }
    request2.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0")
    // 注意这里的http.DefaultClient是包含了cookie的,前面的http.DefaultClient是没有cookie
    resp2, err := http.DefaultClient.Do(request2)
    if err != nil {
        log.Panicln(err)
    }
    defer resp2.Body.Close()
    // 显示响应状态码信息
    body, err := ioutil.ReadAll(resp2.Body)
    if err != nil {
        fmt.Println("[-] ioutil.ReadAll Errot:", err)
        return
    }
    fmt.Printf("有cookie后请求的内容:%s", body)
}

Cookie是有超时时间的,那么我们会发现上面每次请求Cookie过期时间是很久,但是我们只要关闭程序,Cookie就不可以使用了,原因是cookie有两种,一种是会话期的cookie,也就是上面用的,存活期只在程序运行时,另一种是持久化,不管有没有关闭会话,都存储才一个地方,当Cookie过期时才真正过期。当然会话期cookie已经可以满足基本的请求。

3.9.3 cookie持久化

在Go的内置库中,只能使用会话期的Cookie,而如果要使用持久化Cookie,则需要使用第三方库:https://github.com/juju/persistent-cookiejar

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "github.com/juju/persistent-cookiejar"
)

func main() {
    // 实例化一个cookiejar
    // 关于persistent-cookiejar使用,我们只需要将上面的例子中的cookiejar.New(nil)改成cookiejar2.New(nil),然后将jar保存。
    val_jar, err := cookiejar2.New(nil)
    if err != nil {
        fmt.Println("[-] cookiejar.New Errot:", err)
        return
    }
    // 将创建的cookiejar赋值给要发起请求的http.DefaultClient中
    http.DefaultClient.Jar = val_jar

    // 构造第一次请求
    request1, err := http.NewRequest(http.MethodGet, "http://httpbin.org/cookies/set?name=a7cc&passwd=123456", nil)
    if err != nil {
        fmt.Println("[-] http.NewRequest Error:", err)
        return
    }

    // 发起第一次请求
    resp1, err := http.DefaultClient.Do(request1)
    if err != nil {
        fmt.Println("[-] client.Do Errot:", err)
        return
    }
    // 保存请求,之后就可以不用登录,直接使用,这里没用设置登录例子,将就一下吧。。。。
    // 保存的cookiejar放在当前文件夹下的.go-cookies
    jar.save()
    // 使用defer在最后关闭连接
    defer resp1.Body.Close()
    // 显示响应状态码信息
    body, err := ioutil.ReadAll(resp1.Body)
    if err != nil {
        fmt.Println("[-] ioutil.ReadAll Errot:", err)
        return
    }
    fmt.Printf("Cookie:%s", body)
}

3.10 请求超时(在构造请求时设置)

我们通常说的请求超时是指http会话请求的整个阶段,当然这整个阶段有很多时间,在没有备注的情况下,我们说的请求超时是http会话请求的整个阶段超时。详细了解看这篇文章

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "time"
)

func main() {
    // http.DefaultClient.Timeout只是简单的请求超时设置,这里我们设置10秒
    http.DefaultClient.Timeout = 1 * time.Second
    // // http.DefaultClient.Transport是对详细的请求超时进行设置,了解就行
    // http.DefaultClient.Transport = &http.Transport{
    // 	DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
    // 		return net.DialTimeout(network, addr, 2*time.Second)
    // 	},
    // 	ResponseHeaderTimeout: 5 * time.Second,
    // 	TLSHandshakeTimeout:   2 * time.Second,
    // 	IdleConnTimeout:       60 * time.Second,
    // }
    // 构造第一次请求
    request1, err := http.NewRequest(http.MethodGet, "http://google.com", nil)
    if err != nil {
        fmt.Println("[-] http.NewRequest Error:", err)
        return
    }

    // 发起第一次请求
    resp1, err := http.DefaultClient.Do(request1)
    if err != nil {
        fmt.Println("[-] client.Do Errot:", err)
        return
    }
    // 使用defer在最后关闭连接
    defer resp1.Body.Close()
    // 显示响应状态码信息
    body, err := ioutil.ReadAll(resp1.Body)
    if err != nil {
        fmt.Println("[-] ioutil.ReadAll Errot:", err)
        return
    }
    fmt.Printf("Cookie:%s", body)
}

3.11 设置代理(在构造请求时设置)

代理一般有http、socks5等,在Go语言中设置代理,只需要添加一个代理,然后将其赋值给Transport参数的Proxy中。

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
)

func main() {
    // 由于Proxy参数接收的是一个URL类型所以我们使用url.Parse将socks5://127.0.0.1:1080转换成URL格式
    // 这个是socks5代理
    proxyURL, err := url.Parse("socks5://127.0.0.1:1080")
    // // 这个是http代理
    // proxyURL, err := url.Parse("http://127.0.0.1:8080")
    // fmt.Printf("%T", proxyURL)
    if err != nil {
        fmt.Println("[-] url.Parse Error:", err)
        return
    }
    // 设置代理
    http.DefaultClient.Transport = &http.Transport{
        // 设置代理的关键参数Proxy
        Proxy: http.ProxyURL(proxyURL),
    }
    // 构造第一次请求
    request1, err := http.NewRequest(http.MethodGet, "https://google.com", nil)
    if err != nil {
        fmt.Println("[-] http.NewRequest Error:", err)
        return
    }

    // 发起第一次请求
    resp1, err := http.DefaultClient.Do(request1)
    if err != nil {
        fmt.Println("[-] client.Do Errot:", err)
        return
    }
    // 使用defer在最后关闭连接
    defer resp1.Body.Close()
    // 显示响应状态码信息
    body, err := ioutil.ReadAll(resp1.Body)
    if err != nil {
        fmt.Println("[-] ioutil.ReadAll Errot:", err)
        return
    }
    fmt.Printf("%s", body)
}

3.12 client端忽略证书的校验

有点时候有些网站由于使用不知名CA签发的证书,会出现一个页面拦截,需要我们确认,但是我们需要通过爬虫进入的话不能手动点击确认,我们可以使用client端忽略证书的校验方法进行通过。

package main

import (
    "crypto/tls"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
    "strings"
)

func main() {
    // 忽略对证书的校验,设置tls.Config的InsecureSkipVerify为true,client将不再对服务端的证书进行校验
    tr := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }
    client := &http.Client{Transport: tr}
    // 设置数据包
    data := url.Values{}
    data.Set("name", "A7cc")
    data.Set("Age", "18")
    // 构造请求
    requ, err := http.NewRequest("POST", "http://httpbin.org/post", strings.NewReader(data.Encode()))
    // 设置请求头信息
    requ.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0")
    requ.Header.Set("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
    if err != nil {
        fmt.Println("[-] http.NewRequest Error:", err.Error())
        return
    }
    // 发起请求,不过这里可以看到使用的是结果设置的client而不是http.DefaultClient
    resp, err := client.Do(requ)
    if err != nil {
        fmt.Println("[-] client.Do Errot:", err)
        return
    }
    // 使用defer在最后关闭连接
    defer resp.Body.Close()
    // 显示响应状态码信息
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("[-] ioutil.ReadAll Errot:", err)
        return
    }
    fmt.Printf("%s", body)
}

肆 使用

具体使用在方法说明里,这里就是一个简单代理,主要是懒。

package main

import (
    "crypto/tls"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/url"
    "strings"
)

func run(Url string) ([]byte, int, error) {
    // 由于Proxy参数接收的是一个URL类型所以我们使用url.Parse将socks5://127.0.0.1:1080转换成URL格式
    // 这个是socks5代理
    // proxyURL, err := url.Parse("socks5://127.0.0.1:1080")
    // 这个是http代理
    proxyURL, err := url.Parse("http://127.0.0.1:8080")
    if err != nil {
        return nil, 0, err
    }
    // 设置代理
    http.DefaultClient.Transport = &http.Transport{
        // 设置代理的关键参数Proxy
        Proxy: http.ProxyURL(proxyURL),
        // 忽略对证书的校验,设置tls.Config的InsecureSkipVerify为true,client将不再对服务端的证书进行校验
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }
    // 可以直接在url.Values{}加参数,不过为了讲解多种方法,这里就忽略了
    data := url.Values{}
    data.Set("name", "A7cc")
    data.Set("Age", "18")
    // 构造第一次请求
    request1, err := http.NewRequest(http.MethodPost, Url, strings.NewReader(data.Encode()))
    if err != nil {
        return nil, 0, err
    }
    // 使用request.Header.Set设置请求头信息
    request1.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0")

    // 发起第一次请求
    resp1, err := http.DefaultClient.Do(request1)
    if err != nil {
        return nil, 0, err
    }
    // 使用defer在最后关闭连接
    defer resp1.Body.Close()
    // 显示响应状态码信息
    // go1.16H后建议使用io.ReadAll
    body, err := ioutil.ReadAll(resp1.Body)
    if err != nil {
        return nil, 0, err
    }
    return body, resp1.StatusCode, nil
}
func main() {
    b, code, err := run("https://www.baidu.com")
    if err != nil {
        fmt.Println(err.Error())
    }
    fmt.Println(string(b), code)
}

伍 参考

avatar
小C&天天

修学储能 先博后渊


今日诗句