摘要:即是發送請求的客戶端,請求的執行都是由發起。響應信息執行請求成功,如何查看響應信息。提交文件文件提交應該是請求中較為復雜的內容了。主要涉及兩部分內容,即讀取響應的與設置請求的。的包中請求是不重定向的,但測試結果顯示的是自動重定向的。
前幾天在 "知乎想法" 談到了一個話題,如何模仿學習,舉了通過 net/http client 模仿 Pyhton 的requests的例子。但并未實踐,難道想法真的只能是想法嗎?當然不是,于是我決定先暫停一周 GO 筆記,來實踐下自己的想法。
有些新的知識,我們可以通過模仿學習
本文將通過 GO 實現 requests 的 quick start 文檔中的所有例子,系統學習http client的使用。雖然標題是 quick start,但其實內容挺多的。
快速體驗首先,我們來發起一個 GET 請求,代碼非常簡單。如下:
func get() { r, err := http.Get("https://api.github.com/events") if err != nil { panic(err) } defer func() { _ = r.Body.Close() }() body, _ := ioutil.ReadAll(r.Body) fmt.Printf("%s", body) }
通過 http.Get 方法,獲取到了一個 Response 和一個 error ,即 r 和 err。通過 r 我們能獲取響應的信息,err 可以實現錯誤檢查。
r.Body 被讀取后需要關閉,可以defer來做這件事。內容的讀取可通過 ioutil.ReadAll實現。
請求方法除了GET,HTTP還有其他一系列方法,包括POST、PUT、DELETE、HEAD、OPTIONS。快速體驗中的GET是通過一種便捷的方式實現的,它隱藏了很多細節。這里暫時先不用它。
我們先來介紹通用的方法,以幫我們實現所有HTTP方法的請求。主要涉及兩個重要的類型,Client 和 Request。
Client 即是發送 HTTP 請求的客戶端,請求的執行都是由 Client 發起。它提供了一些便利的請求方法,比如我們要發起一個Get請求,可通過 client.Get(url) 實現。更通用的方式是通過 client.Do(req) 實現,req 屬于 Request 類型。
Request 是用來描述請求信息的結構體,比如請求方法、地址、頭部等信息,我們都可以通過它來設置。Request 的創建可以通過 http.NewRequest 實現。
接下來列舉 HTTP 所有方法的實現代碼。
GET
r, err := http.DefaultClient.Do( http.NewRequest(http.MethodGet, "https://api.github.com/events", nil))
POST
r, err := http.DefaultClient.Do( http.NewRequest(http.MethodPost, "http://httpbin.org/post", nil))
PUT
r, err := http.DefaultClient.Do( http.NewRequest(http.MethodPut, "http://httpbin.org/put", nil))
DELETE
r, err := http.DefaultClient.Do( http.NewRequest(http.MethodDelete, "http://httpbin.org/delete", nil))
HEAD
r, err := http.DefaultClient.Do( http.NewRequest(http.MethodHead, "http://httpbin.org/get", nil))
OPTIONS
r, err := http.DefaultClient.Do( http.NewRequest(http.MethodOptions, "http://httpbin.org/get", nil))
上面展示了HTTP所有方法的實現。這里還幾點需要說明。
DefaultClient,它是 net/http 包提供了默認客戶端,一般的請求我們無需創建新的 Client,使用默認即可。
GET、POST 和 HEAD 的請求,GO提供了更便捷的實現方式,Request 不用手動創建。
示例代碼,每個 HTTP 請求方法都有兩種實現。
GET
r, err := http.DefaultClient.Get("http://httpbin.org/get") r, err := http.Get("http://httpbin.org/get")
POST
bodyJson, _ := json.Marshal(map[string]interface{}{ "key": "value", }) r, err := http.DefaultClient.Post( "http://httpbin.org/post", "application/json", strings.NewReader(string(bodyJson)), ) r, err := http.Post( "http://httpbin.org/post", "application/json", strings.NewReader(string(bodyJson)), )
這里順便演示了如何向 POST 接口提交 JSON 數據的方式,主要 content-type 的設置,一般JSON接口的 content-type 為 application/json。
HEAD
r, err := http.DefaultClient.Head("http://httpbin.org/get") r, err := http.Head("http://httpbin.org/get")
如果看了源碼,你會發現,http.Get 中調用就是 http.DefaultClient.Get,是同一個意思,只是為了方便,提供這種調用方法。Head 和 Post 也是如此。
URL參數通過將鍵/值對置于 URL 中,我們可以實現向特定地址傳遞數據。該鍵/值將跟在一個問號的后面,例如 http://httpbin.org/get?key=val。 手工構建 URL 會比較麻煩,我們可以通過 net/http 提供的方法來實現。
舉個栗子,比如你想傳遞 key1=value1 和 key2=value2 到 http://httpbin.org/get。代碼如下:
req, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil) if err != nil { panic(err) } params := make(url.Values) params.Add("key1", "value1") params.Add("key2", "value2") req.URL.RawQuery = params.Encode() // URL 的具體情況 http://httpbin.org/get?key1=value1&key2=value2 // fmt.Println(req.URL.String()) r, err := http.DefaultClient.Do(req)
url.Values 可以幫助組織 QueryString,查看源碼發現 url.Values 其實是 map[string][]string。調用 Encode 方法,將組織的字符串傳遞給請求 req 的 RawQuery。通過 url.Values也可以設置一個數組參數,類似如下的形式:
http://httpbin.org/get?key1=v...
怎么做呢?
params := make(url.Values) params.Add("key1", "value1") params.Add("key2", "value2") params.Add("key2", "value3")
觀察最后一行代碼。其實,只要在 key2 上再增加一個值就可以了。
響應信息執行請求成功,如何查看響應信息。要查看響應信息,可以大概了解下,響應通常哪些內容?常見的有主體內容(Body)、狀態信息(Status)、響應頭部(Header)、內容編碼(Encoding)等。
Body其實,在最開始的時候已經演示Body讀取的過程。響應內容的讀取可通過 ioutil 實現。
body, err := ioutil.ReadAll(r.Body)
響應內容多樣,如果是 json,可以直接使用 json.Unmarshal 進行解碼,JSON知識不介紹了。
r.Body 實現了 io.ReadeCloser 接口,為減少資源浪費要及時釋放,可以通過 defer 實現。
defer func() { _ = r.Body.Close() }()StatusCode
響應信息中,除了 Body 主體內容,還有其他信息,比如 status code 和 charset 等。
r.StatusCode r.Status
r.StatusCode 是 HTTP 返回碼,Status 是返回狀態描述。
Header響應頭信息通過 Response.Header 即可獲取,要說明的一點是,響應頭的 Key 是不區分大小寫。
r.Header.Get("content-type") r.Header.Get("Content-Type")
你會發現 content-type 和 Content-Type 獲取的內容是完全一樣的。
Encoding如何識別響應內容編碼呢?我們需要借助 http://golang.org/x/net/html/... 包實現。先來定義一個函數,代碼如下:
func determineEncoding(r *bufio.Reader) encoding.Encoding { bytes, err := r.Peek(1024) if err != nil { fmt.Printf("err %v", err) return unicode.UTF8 } e, _, _ := charset.DetermineEncoding(bytes, "") return e }
怎么調用它?
bodyReader := bufio.NewReader(r.Body) e := determineEncoding(bodyReader) fmt.Printf("Encoding %v ", e) decodeReader := transform.NewReader(bodyReader, e.NewDecoder())
利用 bufio 生成新的 reader,然后利用 determineEncoding 檢測內容編碼,并通過 transform 進行編碼轉化。
圖片下載如果訪問內容是一張圖片,我們如何把它下載下來呢?比如如下地址的圖片。
https://pic2.zhimg.com/v2-5e8...
其實很簡單,只需要創建新的文件并把響應內容保存進去即可。
f, err := os.Create("as.jpg") if err != nil { panic(err) } defer func() { _ = f.Close() }() _, err = io.Copy(f, r.Body) if err != nil { panic(err) }
r 即 Response,利用 os 創建了新的文件,然后再通過 io.Copy 將響應的內容保存進文件中。
定制請求頭如何為請求定制請求頭呢?Request 其實已經提供了相應的方法,通過 req.Header.Add 即可完成。
舉個例子,假設我們將要訪問 http://httpbin.org/get,但這個地址針對 user-agent 設置了發爬策略。我們需要修改默認的 user-agent。
示例代碼:
req, err := http.NewRequest(http.MethodGet, "http://httpbin.org/get", nil) if err != nil { panic(err) } req.Header.Add("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0)")
如上便可完成任務。
復雜的POST請求前面已經展示過了向 POST 接口提交 JSON 數據的方式。接下來介紹下另外幾種向 POST 接口提交數據的方式,即表單提交和文件提交。
表單提交表單提交是一個很常用的功能,故而在 net/http 中,除了提供標準的用法外,還給我們提供了簡化的方法。
我們先來介紹個標準的實現方法。
舉個例子,假設要向 http://httpbin.org/post 提交 name 為 poloxue 和 password 為 123456 的表單。
payload := make(url.Values) payload.Add("name", "poloxue") payload.Add("password", "123456") req, err := http.NewRequest( http.MethodPost, "http://httpbin.org/post", strings.NewReader(payload.Encode()), ) if err != nil { panic(err) } req.Header.Add("Content-Type", "application/x-www-form-urlencoded") r, err := http.DefaultClient.Do(req)
POST 的 payload 是形如 name=poloxue&password=123456 的字符串,故而我們可以通過 url.Values 進行組織。
提交給 NewRequest 的內容必須是實現 Reader 接口的類型,所以需要 strings.NewReader轉化下。
Form 表單提交的 content-type 要是 application/x-www-form-urlencoded,也要設置下。
復雜的方式介紹完了。接著再介紹簡化的方式,其實表單提交只需調用 http.PostForm 即可完成。示例代碼如下:
payload := make(url.Values) payload.Add("name", "poloxue") payload.Add("password", "123456") r, err := http.PostForm("http://httpbin.org/post", form)
竟是如此的簡單。
提交文件文件提交應該是 HTTP 請求中較為復雜的內容了。其實說難也不難,區別于其他的請求,我們要花些精力來讀取文件,組織提交POST的數據。
舉個例子,假設現在我有一個圖片文件,名為 as.jpg,路徑在 /Users/polo 目錄下。現在要將這個圖片提交給 http://httpbin.org/post。
我們要先組織 POST 提交的內容,代碼如下:
filename := "/Users/polo/as.jpg" f, err := os.Open(filename) if err != nil { panic(err) } defer func() { _ = f.Close() }() uploadBody := &bytes.Buffer{} writer := multipart.NewWriter(uploadBody) fWriter, err := writer.CreateFormFile("uploadFile", filename) if err != nil { fmt.Printf("copy file writer %v", err) } _, err = io.Copy(fWriter, f) if err != nil { panic(err) } fieldMap := map[string]string{ "filename": filename, } for k, v := range fieldMap { _ = writer.WriteField(k, v) } err = writer.Close() if err != nil { panic(err) }
我認為,數據組織分為幾步完成,如下:
第一步,打開將要上傳的文件,使用 defer f.Close() 做好資源釋放的準備;
第二步,創建存儲上傳內容的 bytes.Buffer,變量名為 uploadBody;
第三步,通過 multipart.NewWriter 創建 writer,用于向 buffer中寫入文件提供的內容;
第四步,通過writer.CreateFormFile 創建上傳文件并通過 io.Copy 向其中寫入內容;
最后,通過 writer.WriteField 添加其他的附加信息,注意最后要把 writer 關閉;
至此,文件上傳的數據就組織完成了。接下來,只需調用 http.Post 方法即可完成文件上傳。
r, err := http.Post("http://httpbin.org/post", writer.FormDataContentType(), uploadBody)
有一點要注意,請求的content-type需要設置,而通過 writer.FormDataContentType() 即能獲得上傳文件的類型。
到此,文件提交也完成了,不知道有沒有非常簡單的感覺。
Cookie主要涉及兩部分內容,即讀取響應的 cookie 與設置請求的 cookie。響應的 cookie 獲取方式非常簡單,直接調用 r.Cookies 即可。
重點來說說,如何設置請求 cookie。cookie設置有兩種方式,一種設置在 Client 上,另一種是設置在 Request 上。
Client 上設置 Cookie直接看示例代碼:
cookies := make([]*http.Cookie, 0) cookies = append(cookies, &http.Cookie{ Name: "name", Value: "poloxue", Domain: "httpbin.org", Path: "/cookies", }) cookies = append(cookies, &http.Cookie{ Name: "id", Value: "10000", Domain: "httpbin.org", Path: "/elsewhere", }) url, err := url.Parse("http://httpbin.org/cookies") if err != nil { panic(err) } jar, err := cookiejar.New(nil) if err != nil { panic(err) } jar.SetCookies(url, cookies) client := http.Client{Jar: jar} r, err := client.Get("http://httpbin.org/cookies")
代碼中,我們首先創建了 http.Cookie 切片,然后向其中添加了 2 個 Cookie 數據。這里通過 cookiejar,保存了 2 個新建的 cookie。
這次我們不能再使用默認的 DefaultClient 了,而是要創建新的 Client,并將保存 cookie 信息的 cookiejar 與 client 綁定。接下里,只需要使用新創建的 Client 發起請求即可。
請求上設置 Cookie請求上的 cookie 設置,通過 req.AddCookie即可實現。示例代碼:
req, err := http.NewRequest(http.MethodGet, "http://httpbin.org/cookies", nil) if err != nil { panic(err) } req.AddCookie(&http.Cookie{ Name: "name", Value: "poloxue", Domain: "httpbin.org", Path: "/cookies", }) r, err := http.DefaultClient.Do(req)
挺簡單的,沒什么要介紹的。
cookie 設置 Client 和 設置在 Request 上有何區別?一個最易想到的區別就是,Request 的 cookie 只是當次請求失效,而 Client 上的 cookie 是隨時有效的,只要你用的是這個新創建的 Client。
重定向和請求歷史默認情況下,所有類型請求都會自動處理重定向。
Python 的 requests 包中 HEAD 請求是不重定向的,但測試結果顯示 net/http 的 HEAD 是自動重定向的。
net/http 中的重定向控制可以通過 Client 中的一個名為 CheckRedirect 的成員控制,它是函數類型。定義如下:
type Client struct { ... CheckRedirect func(req *Request, via []*Request) error ... }
接下來,我們來看看怎么使用。
假設我們要實現的功能:為防止發生循環重定向,重定向次數定義不能超過 10 次,而且要記錄歷史 Response。
示例代碼:
var r *http.Response history := make([]*http.Response, 0) client := http.Client{ CheckRedirect: func(req *http.Request, hrs []*http.Request) error { if len(hrs) >= 10 { return errors.New("redirect to many times") } history = append(history, req.Response) return nil }, } r, err := client.Get("http://github.com")
首先創建了 http.Response 切片的變量,名稱為 history。接著在 http.Client 中為 CheckRedirect 賦予一個匿名函數,用于控制重定向的行為。CheckRedirect 函數的第一個參數表示下次將要請求的 Request,第二個參數表示已經請求過的 Request。
當發生重定向時,當前的 Request 會保存上次請求的 Response,故而此處可以將 req.Response 追加到 history 變量中。
超時設置Request 發出后,如果服務端遲遲沒有響應,那豈不是很尷尬。那么我們就會想,能否為請求設置超時規則呢?毫無疑問,當然可以。
超時可以分為連接超時和響應讀取超時,這些都可以設置。但正常情況下,并不想有那么明確的區別,那么也可以設置個總超時。
總超時總的超時時間的設置是綁定在 Client 的一個名為 Timeout 的成員之上,Timeout 是 time.Duration。
假設這是超時時間為 10 秒,示例代碼:
client := http.Client{ Timeout: time.Duration(10 * time.Second), }連接超時
連接超時可通過 Client 中的 Transport 實現。Transport 中有個名為 Dial 的成員函數,可用設置連接超時。Transport 是 HTTP 底層的數據運輸者。
假設設置連接超時時間為 2 秒,示例代碼:
t := &http.Transport{ Dial: func(network, addr string) (net.Conn, error) { timeout := time.Duration(2 * time.Second) return net.DialTimeout(network, addr, timeout) }, }
在 Dial 的函數中,我們通過 net.DialTimeout 進行網絡連接,實現了連接超時功能。
讀取超時讀取超時也要通過 Client 的 Transport 設置,比如設置響應的讀取為 8 秒。
示例代碼:
t := &http.Transport{ ResponseHeaderTimeout: time.Second * 8, } 綜合所有,Client 的創建代碼如下: t := &http.Transport{ Dial: func(network, addr string) (net.Conn, error) { timeout := time.Duration(2 * time.Second) return net.DialTimeout(network, addr, timeout) }, ResponseHeaderTimeout: time.Second * 8, } client := http.Client{ Transport: t, Timeout: time.Duration(10 * time.Second), }
除了上面的幾個超時設置,Transport 還有其他一些關于超時的設置,可以看下 Transport 的定義,還有發現三個與超時相關的定義:
// IdleConnTimeout is the maximum amount of time an idle // (keep-alive) connection will remain idle before closing // itself. // Zero means no limit. IdleConnTimeout time.Duration // ResponseHeaderTimeout, if non-zero, specifies the amount of // time to wait for a server"s response headers after fully // writing the request (including its body, if any). This // time does not include the time to read the response body. ResponseHeaderTimeout time.Duration // ExpectContinueTimeout, if non-zero, specifies the amount of // time to wait for a server"s first response headers after fully // writing the request headers if the request has an // "Expect: 100-continue" header. Zero means no timeout and // causes the body to be sent immediately, without // waiting for the server to approve. // This time does not include the time to send the request header. ExpectContinueTimeout time.Duration
分別是 IdleConnTimeout (連接空閑超時時間,keep-live 開啟)、TLSHandshakeTimeout (TLS 握手時間)和 ExpectContinueTimeout(似乎已含在 ResponseHeaderTimeout 中了,看注釋)。
到此,完成了超時的設置。相對于 Python requests 確實是復雜很多。
請求代理代理還是挺重要的,特別對于開發爬蟲的同學。那 net/http 怎么設置代理?這個工作還是要依賴 Client 的成員 Transport 實現,這個 Transport 還是挺重要的。
Transport 有個名為 Proxy 的成員,具體看看怎么使用吧。假設我們要通過設置代理來請求谷歌的主頁,代理地址為 http://127.0.0.1:8087。
示例代碼:
proxyUrl, err := url.Parse("http://127.0.0.1:8087") if err != nil { panic(err) } t := &http.Transport{ Proxy: http.ProxyURL(proxyUrl), TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client := http.Client{ Transport: t, Timeout: time.Duration(10 * time.Second), } r, err := client.Get("https://google.com")
主要關注 http.Transport 創建的代碼。兩個參數,分時 Proxy 和 TLSClientConfig,分別用于設置代理和禁用 https 驗證。我發現其實不設置 TLSClientConfig 也可以請求成功,具體原因沒仔細研究。
錯誤處理錯誤處理其實都不用怎么介紹,GO中的一般錯誤主要是檢查返回的error,HTTP 請求也是如此,它會視情況返回相應錯誤信息,比如超時、網絡連接失敗等。
示例代碼中的錯誤都是通過 panic 拋出去的,真實的項目肯定不是這樣的,我們需要記錄相關日志,時刻做好錯誤恢復工作。
總結本文以 Python 的 requests 文檔為指導方向,整理了 requests 快速入門文檔中的案例在 GO 的是如何實現的。要說明的是, GO 其實也提供了對應于 requests 的克隆版本,[github地址](
https://github.com/levigross/...。暫時我也還沒有看,有興趣的朋友可以去研究一下。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/45252.html
?????? ???Hello,大家好我叫是Dream呀,一個有趣的Python博主,小白一枚,多多關照??? ???CSDN Python領域新星創作者,大二在讀,歡迎大家找我合作學習 ?入門須知:這片樂園從不缺乏天才,努力才是你的最終入場券!??? ?最后,愿我們都能在看不到的地方閃閃發光,一起加油進步??? ???一萬次悲傷,依然會有Dream,我一直在最溫暖的地方等你,唱的就是我!哈哈哈~...
摘要:背景一個國人編寫的強大的網絡爬蟲系統并帶有強大的。框架學習時走過的一些坑錯誤我所遇到的一些錯誤首先,本爬蟲目標使用框架爬取網站的帖子中的問題和內容,然后將爬取的數據保存在本地。修飾器,表示每天會執行一次,這樣就能抓到最新的帖子了。 背景: PySpider:一個國人編寫的強大的網絡爬蟲系統并帶有強大的WebUI。采用Python語言編寫,分布式架構,支持多種數據庫后端,強大的WebUI...
摘要:在上篇文章實現簡單爬蟲框架單任務版爬蟲中我們實現了一個簡單的單任務版爬蟲,對于單任務版爬蟲,每次都要請求頁面,然后解析數據,然后才能請求下一個頁面。在上篇文章Golang實現簡單爬蟲框架(2)——單任務版爬蟲中我們實現了一個簡單的單任務版爬蟲,對于單任務版爬蟲,每次都要請求頁面,然后解析數據,然后才能請求下一個頁面。整個過程中,獲取網頁數據速度比較慢,那么我們就把獲取數據模塊做成并發執行。在...
摘要:在上篇文章實現簡單爬蟲框架單任務版爬蟲中我們實現了一個簡單的單任務版爬蟲,對于單任務版爬蟲,每次都要請求頁面,然后解析數據,然后才能請求下一個頁面。在上篇文章Golang實現簡單爬蟲框架(2)——單任務版爬蟲中我們實現了一個簡單的單任務版爬蟲,對于單任務版爬蟲,每次都要請求頁面,然后解析數據,然后才能請求下一個頁面。整個過程中,獲取網頁數據速度比較慢,那么我們就把獲取數據模塊做成并發執行。在...
摘要:全局安裝目前版本,這個版本不再需要使用命令了。現在嘗試深入抓取文章內容可以發現因為訪問服務器太迅猛,導致出現很多次錯誤。解決添加文件修改文件為修改文件為觀察輸出可以看到,程序實現了隔一秒再請求下一個內容頁。 全局安裝typescript: npm install -g typescript 目前版本2.0.3,這個版本不再需要使用typings命令了。但是vscode捆綁的版本是1....
閱讀 1136·2019-08-30 12:44
閱讀 642·2019-08-29 13:03
閱讀 2551·2019-08-28 18:15
閱讀 2419·2019-08-26 10:41
閱讀 3082·2019-08-26 10:28
閱讀 3029·2019-08-23 16:54
閱讀 1983·2019-08-23 15:16
閱讀 802·2019-08-23 14:55