一、简介
Golang诞生已经超过十个年头了,发展得愈发完善,其简单方便的协程并发机制使得其在爬虫领域有着一定的天赋。
首先我们来看一看,Golang相对于Python这个爬虫领域的传统强者,有哪些优点和缺点。
优点:
- 完善简便的协程并发机制
- 并发数量大
- 占用资源少
- 运行速度更快
- 部署方便
缺点:
- 数据处理比较繁琐
- 成熟工具不是很多
- 资料较少
- 实现相同逻辑需要的代码更多
由于Golang本身静态语言的特性,和其特别的异常处理方式等等原因,在发起较复杂的请求时需要的代码量自然会比Python多不少,但是其并发的数量也是远超Python的,所以两者应用的场景并不十分相同,我们可以根据需要灵活的选择。
在刚刚接触Golang的http包时,觉得其非常的方便,发起请求只需要一行代码:
http.Get("https://www.baidu.com")
requestshttp.Get
net
所以本篇文章的目的,是为了让那些使用Golang的朋友,对如何使用Golang发起请求有一个比较全面的了解。
注1:Golang中文官网的文档版本比较低,有些地方与最新版本不同,有条件的同学可以爬爬梯子,去golang.org英文官网看文档。
注2:文中代码为了简洁,省略掉了异常处理的部分,实际使用时需要按情况加上。
二、简单请求
nethttpurl
import (
"net/http"
"net/url"
)
http
resp, err := http.Get("http://example.com/")
...
resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)
...
resp, err := http.PostForm("http://example.com/form",
url.Values{"key": {"Value"}, "id": {"123"}})
可以看到,我们非常简单的就发起了请求并获得了响应,这里需要注意一点的是,获得的响应body需要我们手动关闭:
resp, err := http.Get("http://example.com/")
if err != nil {
// 处理异常
}
defer resp.Body.Close() // 函数结束时关闭Body
body, err := ioutil.ReadAll(resp.Body) // 读取Body
// ...
这样的请求方式是非常方便的,但是当我们需要定制我们请求的其他参数时,就必须要使用其他组件了。
三、Client
Clienthttp
type Client struct {
Transport RoundTripper
CheckRedirect func(req *Request, via []*Request) error
Jar CookieJar
Timeout time.Duration // Go 1.3
}
首先是生成Client对象:
client := &http.Client{}
Client也有一些简便的请求方法,如:
resp, err := client.Get("http://example.com")
http.GetRequestDo
3.1. 设置超时
这是一张说明Client超时的控制范围的图:
http.Client.Timeout
http.Clienthttp.Client.Timeout
client := &http.Client{
Timeout: 15 * time.Second
}
还有一些更细粒度的超时控制:
net.Dialer.Timeouthttp.Transport.TLSHandshakeTimeouthttp.Transport.ResponseHeaderTimeouthttp.Transport.ExpectContinueTimeoutExpect: 100-continue
Transport
c := &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
},
}
Docontext
3.2. 控制重定向
CheckRedirectdefaultCheckRedirect
默认的转发策略是最多转发10次。
AuthorizationWWW-AuthenticateCookie
http
- 301 (Moved Permanently)
- 302 (Found)
- 303 (See Other)
- 307 (Temporary Redirect)
- 308 (Permanent Redirect)
301、302和303请求将会改用Get访问新的请求,而307和308会使用原有的请求方式。
CheckRedirectdefaultCheckRedirect
func defaultCheckRedirect(req *Request, via []*Request) error {
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
return nil
}
reqviaerrorerror
所以如果需要设置重定向次数,那么复制一份这个函数,修改函数名字和其中if判断的数字,然后在生成Client时设定到Client即可:
client := &http.Client{
CheckRedirect: yourCheckRedirect,
}
或者:
client := &http.Client{}
client.CheckRedirect = yourCheckRedirect
禁止重定向则可以把判断数字修改为0。最好相应地修改errors中提示的信息。
3.3. CookieJar管理
JarCookieJar
nil
options := cookiejar.Options{
PublicSuffixList: publicsuffix.List
}
jar, err := cookiejar.New(&options)
client := &http.Client{
Jar: jar,
}
publicsuffix.Listnil
PublicSuffixListnil
jar, err := cookiejar.New(nil)
client := &http.Client{
Jar: jar,
}
publicsuffix.List
import "golang.org/x/net/publicsuffix"
四、 Request
这是Golang源码中Request定义的字段,可以看到非常的多,有兴趣的可以去源码或者官方文档看有注释的版本,本文只介绍一些比较重要的字段。
type Request struct {
Method string
URL *url.URL
Proto string // "HTTP/1.0"
ProtoMajor int // 1
ProtoMinor int // 0
Header Header
Body io.ReadCloser
GetBody func() (io.ReadCloser, error)
ContentLength int64
TransferEncoding []string
Close bool
Host string
Form url.Values
PostForm url.Values
MultipartForm *multipart.Form
Trailer Header
RemoteAddr string
RequestURI string
TLS *tls.ConnectionState
Cancel <-chan struct{}
Response *Response
}
NewRequestNewRequest
func NewRequest(method, url string, body io.Reader) (*Request, error)
methodurlbodynil
Do
req, err := NewRequest("GET", "https://www.baidu.com", nil)
resp, err := client.Do(req)
4.1. Method
请求方法,必备的参数,如果为空字符则表示Get请求。
CONNECT
4.2. URL
一个被解析过的url结构体。
4.3. Proto
HTTP协议版本。
HTTP1.1HTTP2.0HTTP1.1
HTTP2.0golang.org/x/net/http2
4.4. 发起Post请求
PostFormurl.Values
req, err := NewRequest("Post", "https://www.baidu.com", nil)
req.PostForm.Add("key", "value")
io.ReaderNewRequest
4.4. 设置Header
http.Header
可以使用这种方式设置Header:
req, err := NewRequest("Get", "https://www.baidu.com", nil)
req.Header.Add("key", "value")
SetDel
4.5. 添加Cookie
前文我们已经介绍了如何在Client中启用一直使用的CookieJar,使用它可以自动管理获得的Cookie。
AddCookie
func (r *Request) AddCookie(c *Cookie)
要注意的是,其传入的参数是Cookie类型,,以下是此类型包含的属性:
type Cookie struct {
Name string
Value string
Path string
Domain string
Expires time.Time
RawExpires string
MaxAge int
Secure bool
HttpOnly bool
Raw string
Unparsed []string
}
NameValue
c := &http.Cookie{
Name: "key",
Value: "value",
}
req.AddCookie(c)
五、Transport
TransportClientDefaultTransport
Transport承担起了Client中连接池的功能,它会将建立的连接缓存下来,这可能会在访问大量不同网站时,留下太多打开的连接,这可以使用Transport中的方法进行关闭。
Transport
type Transport struct {
Proxy func(*Request) (*url.URL, error)
DialContext func(ctx context.Context, network, addr string) (net.Conn, error) // Go 1.7
Dial func(network, addr string) (net.Conn, error)
DialTLS func(network, addr string) (net.Conn, error) // Go 1.4
TLSClientConfig *tls.Config
TLSHandshakeTimeout time.Duration // Go 1.3
DisableKeepAlives bool
DisableCompression bool
MaxIdleConns int // Go 1.7
MaxIdleConnsPerHost int
MaxConnsPerHost int // Go 1.11
IdleConnTimeout time.Duration // Go 1.7
ResponseHeaderTimeout time.Duration // Go 1.1
ExpectContinueTimeout time.Duration // Go 1.6
TLSNextProto map[string]func(authority string, c *tls.Conn) RoundTripper // Go 1.6
ProxyConnectHeader Header // Go 1.8
MaxResponseHeaderBytes int64 // Go 1.7
}
TransportClient
5.1. 拨号
Dial
DialDialContextnet.Dialer
type Dialer struct {
Timeout time.Duration
Deadline time.Time
LocalAddr Addr
DualStack bool
FallbackDelay time.Duration
KeepAlive time.Duration
Resolver *Resolver
Cancel <-chan struct{}
Control func(network, address string, c syscall.RawConn) error
}
这其中需要我们设置的并不多,主要是Timeout和KeepAlive。Timeout是Dial这个过程的超时时间,而KeepAlive是连接池中连接的超时时间,如下所示:
trans := &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
}
5.2. 设置代理
Proxy
package main
import (
"net/url"
"net/http"
)
func main() {
proxyURL, _ := url.Parse("https://127.0.0.1:1080")
trans := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
}
client := &http.Client{
Transport: trans,
}
client.Get("https://www.google.com")
}
golang.org/x/net/proxy
package main
import (
"net/url"
"net/http"
"golang.org/x/net/proxy"
)
func main() {
dialer, err := proxy.SOCKS5("tcp", "127.0.0.1:8080",
&proxy.Auth{User:"username", Password:"password"},
&net.Dialer {
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
},
)
trans := &http.Transport{
DialContext: dialer.DialContext
}
client := &http.Client{
Transport: trans,
}
client.Get("https://www.google.com")
}
proxy.SOCKS5DialerDialernil
5.3. 连接控制
众所周知,HTTP1.0协议使用的是短连接,而HTTP1.1默认使用的是长连接,使用长连接则可以复用连接,减少建立连接的开销。
Transport
DisableKeepAlives
trans := &http.Transport{
...
DisableKeepAlives: true,
}
MaxConnsPerHost intMaxIdleConns intMaxIdleConnsPerHost intIdleConnTimeout time.Duration
由于Transport负担起了连接池的功能,所以在并发使用时,最好将Transport与Client一起复用,不然可能会造成发起过量的长连接,浪费系统资源。
六、其他
6.1. 设置url参数
在Go的请求方式中,没有给我们提供可以直接设置url参数的方法,所以需要我们自己在url地址中进行拼接。
urlurl.Valuesmap[string][]string
URL := "http://httpbin.org/get"
params := url.Values{
"key1": {"value"},
"key2": {"value2", "value3"},
}
URL = URL + "&" + params.Encode()
fmt.Println(URL)
// 输出为:http://httpbin.org/get&key1=value&key2=value2&key2=value3
七、总结
总的来说,Go语言中内置的标准库功能是比较完善的,如果要写一个客户端的话,基本不需要用到标准库之外的内容,其可以控制的请求细节也比较多。
但相较于Python的Requests这类库,需要写的代码依然要多非常多,再加上特别的异常处理机制,在请求过程中要写大量的异常检查语句。需要使用的朋友可以考虑先将请求和异常处理的部分封装以后使用。
八、示例
以下是发起Get请求的一个例子:
// 生成client客户端
client := &http.Client{}
// 生成Request对象
req, err := http.NewRequest("Get", "http://httpbin.org/get", nil)
if err != nil {
fmt.Println(err)
}
// 添加Header
req.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36")
// 发起请求
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
}
// 设定关闭响应体
defer resp.Body.Close()
// 读取响应体
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(body))
参考: