在之前上一版本中,我們通過(guò)最簡(jiǎn)單的 Get 請(qǐng)求獲取了網(wǎng)頁(yè)地址,并解析出圖片地址,然后再通過(guò) Get 請(qǐng)求獲取了圖片內(nèi)容。接下來(lái)的問(wèn)題是,對(duì)于有登陸限制的網(wǎng)頁(yè),登陸之前是看不到相關(guān)信息的,那么對(duì)于這一類(lèi)網(wǎng)頁(yè)該如何處理呢?
1. HTTP 請(qǐng)求與響應(yīng)的格式
參見(jiàn)這篇文章,我們知道,在瀏覽器發(fā)出 Get 或 Post 請(qǐng)求時(shí),請(qǐng)求的格式大致如下:
<request-line>
<headers>
<blank line>
[<request-body>]
瀏覽器在我們?cè)L問(wèn)網(wǎng)頁(yè)時(shí)會(huì)自動(dòng)構(gòu)造相關(guān)請(qǐng)求內(nèi)容,在 chrome 中 F12 過(guò)后看到的視圖下,選擇 Network 標(biāo)簽并選擇下方的 Headers 標(biāo)簽,然后刷新網(wǎng)頁(yè),隨意選擇 Headers 標(biāo)簽左邊 Name 欄下的任一元素(一般選第一個(gè))便可看到瀏覽器發(fā)送與接收消息時(shí),消息的 Header 中的內(nèi)容,如下圖:
我們需要用到的就是最下面紅色標(biāo)注的 Request Headers 部分的內(nèi)容 —— 即 cookie 字段
圖中的 cookie 即網(wǎng)站存儲(chǔ)在瀏覽器中的 cookie 值,我們將該 cookie 值復(fù)制出來(lái),粘貼在我們下一步將要在代碼中構(gòu)造的 Request Header 頭中,網(wǎng)站就會(huì)認(rèn)為我們已經(jīng)登錄過(guò)了。
2. Go net/http 包 client 介紹
為了能夠控制 HTTP 客戶端的頭結(jié)構(gòu),重定向規(guī)則以及其他一些設(shè)置, Go 在 net/http 中給了一個(gè) client 結(jié)構(gòu)體類(lèi)型,結(jié)構(gòu)詳情如下:
type Client struct {
Transport RoundTripper
// CheckRedirect 明確了處理重定向的機(jī)制
// 如果該字段不為 nil,則客戶端在重新請(qǐng)求重定向的鏈接之前,先執(zhí)行該函數(shù)
CheckRedirect func(req *Request, via []*Request) error
// CookieJar 是用來(lái)管理與使用眾多 HTTP 請(qǐng)求中的 cookies的
Jar CookieJar
Timeout time.Duration
}
在構(gòu)造 http 請(qǐng)求時(shí),我們先構(gòu)造一個(gè) client,用來(lái)模擬本地的客戶端的種種行為。 client 有這樣一些方法:
// Do 方法發(fā)送一個(gè) HTTP request 并獲得一個(gè) HTTP response
// 所以在調(diào)用之前需要先構(gòu)造一個(gè) request
func (c *Client) Do(req *Request) (*Response, error)
// Get 方法向給定的 URL 發(fā)送 Get 請(qǐng)求
func (c *Client) Get(url string) (resp *Response, err error)
// Head 方法向給定的 URL 發(fā)送一個(gè) Head
func (c *Client) Head(url string) (resp *Response, err error)
// Post 方法向給定的 URL 發(fā)送 Post 請(qǐng)求
func (c *Client) Post(url string, contentType string, body io.Reader) (resp *Response, err error)
......
上一篇文章我們使用 http.Get() 方法發(fā)送了一個(gè) Get 請(qǐng)求,通過(guò)源碼我們看到, http.Get() 方法實(shí)際是構(gòu)造了一個(gè) DefaultClient = &Client{},再調(diào)用 Default.Get() 方法。而通過(guò)閱讀源碼發(fā)現(xiàn), func (c *Client) Get(url string) (resp *Response, err error)
方法的實(shí)現(xiàn)實(shí)際就是先構(gòu)造 Request
對(duì)象,再調(diào)用 Client.Do 方法,如下:
func (c *Client) Get(url string) (resp *Response, err error) {
req, err := NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
return c.Do(req)
}
總結(jié)來(lái)看,我們?cè)谑褂?Client 時(shí),先構(gòu)造一個(gè) Request 再調(diào)用 Client.Do() 方法。(關(guān)于 type Request struct
詳見(jiàn)該鏈接下的相關(guān)內(nèi)容)
3. 代碼實(shí)現(xiàn)
該代碼沒(méi)有實(shí)現(xiàn)模擬登陸存取網(wǎng)站返回的cookie,只是簡(jiǎn)單驗(yàn)證了構(gòu)造 header 使用 http 包中 client 結(jié)構(gòu)體訪問(wèn)相關(guān)有驗(yàn)證網(wǎng)頁(yè)的可行性。代碼如下:
package main
import (
"net/http"
"fmt"
"io/ioutil"
)
func check(err error){
if err != nil {
panic(err)
}
}
func main(){
url := "http://weibo.com/u/5765378903"
client := &http.Client{}
req, err := http.NewRequest("GET", url, nil)
check(err)
cookie_str := "your cookie" //從瀏覽器復(fù)制的 cookie 字符串
req.Header.Set("Cookie",cookie_str)
resp, err := client.Do(req)
check(err)
resp_byte, err := ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
fmt.Println(string(resp_byte))
}
最后結(jié)果表明該代碼能夠獲取所期望的網(wǎng)頁(yè)信息。