使用 Golang 寫爬蟲經驗總結

諾大的院子發表於2019-08-09

模擬登入最重要的是儲存cookies的狀態,例如在填寫驗證碼的頁面,伺服器會傳給客戶端一個sessionID儲存在cookies中,客戶端在提交使用者賬戶和驗證碼等資訊時,需要連同這個cookies一起提交,否則伺服器就無法判斷兩次請求是否為同一個客戶端,從而導致驗證碼驗證失敗。
在Golang中可以使用CookieJar管理cookies,在建立http.Client的物件時,傳入一個非空的CookieJar物件即可。設定了之後,Golang在收到伺服器的響應之後,會自動把響應頭中的cookies資訊儲存到CookieJar中,在下次發起請求時,自動從CookieJar中取出cookies資訊放到請求頭中。

// &cookiejar.Options{PublicSuffixList: publicsuffix.List},這是為了可以根據域名安全地設定cookies
cookieJar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
if err != nil {
   panic(err)
}
client = http.Client{Jar: cookieJar, Timeout: time.Second * 3}

GET提交方式很簡單,直接拼接字串就行了。POST提交需要一個可讀的(io.Reader)請求體body,body中是形如a=b&c=d格式的查詢字串(位元組切片)。可以藉助url.Values方便的生成查詢字串,它本質是一個map[string][]string,提供了SetAdd等方法,讓操作這個map更簡單。

params := url.Values{}
// 新增引數
params.Add("memberAccount", "xxx")
// 新增sha1後的引數
params.Add("memberUmm", fmt.Sprintf("%x", sha1.Sum([]byte("xxxx"))))
params.Add("check", captcha)
params.Add("rememberMe", "on")
// 1.必須設定正確的Content-Type,否則伺服器無法正確識別引數
// 2.params.Encode()生成查詢字串,然後用string.NewReader包裹這個字串使其可讀
res, err := client.Post("https://www.example.com/login.json", "application/x-www-form-urlencoded", strings.NewReader(params.Encode()))
if err != nil {
   return "", err
}
// 記得關閉
defer res.Body.Close()

上傳檔案需要藉助multipart.Writer向請求體中寫入相應的資料,使用multipart.NewWriter生成這樣的一個寫入器,它接收一個io.Writer作引數,這個引數即我們的表單體bodybody除了需要可寫,還要可讀(讓httpClient讀取引數傳送到伺服器),並且它是流式的,所以選用bytes.Buffer,一個可讀寫大小可變的位元組流緩衝器。

body := new(bytes.Buffer)
// 建立body的寫入器
mulWriter := multipart.NewWriter(body)

寫入普通欄位

// 直接呼叫writeField方法即可,引數1是引數名,引數2是引數值
err := mulWriter.WriteField("user_name", "xxx")
if err != nil {
    return "", err
}

寫入檔案欄位

要寫入檔案,需要先呼叫CreateFormFile建立一個檔案內容寫入器,再通過寫入器寫入檔案的內容

// 建立檔案寫入器,並指明檔案引數名和引數值
fileWriter, err := mulWriter.CreateFormFile("upload", filepath.Base(filename))
if err != nil {
    return "", err
}
// 開啟需要上傳的檔案
file, err := os.Open(filename)
if err != nil {
    return "", err
}
defer file.Close()
// 複製檔案內容到寫入器
_, err = io.Copy(fileWriter, file)
if err != nil {
    return "", err
}
// 記得關閉,讓緩衝區的內容寫入body中
err = mulWriter.Close()
if err != nil {\
   return "", err\
}
res, err := client.Post("http://v1-http-api.jsdama.com/api.php?mod=php&act=upload", mulWriter.FormDataContentType(), body)

上面例子的最後一行,必須使用FormDataContentType()方法獲取正確的Content-Type,而不能自己寫multipart/form-data,這是因為boundary是隨機生成的,這個必須由Golang告訴我們正確的值。boundary即表單體中分隔每個引數的一個標誌位,如下圖:

使用 Golang 寫爬蟲經驗總結

使用Fiddler抓包可以讓我們方便的看到程式提交到伺服器的資料格式,使得除錯和修改程式更加簡單。Fiddler相當於一個正向代理伺服器,啟動後,它會把IE的代理伺服器設定為http://127.0.0.1:8888 ,即Fiddler預設的代理地址,這樣所有瀏覽器的請求都會先通過Fiddler,再由Fiddler轉發出去,實現抓包。
但是上面的機制只對系統的瀏覽器有效,要對其他程式也生效,需要單獨的設定程式的代理。
Golang設定代理比較簡單,只需要增加一個環境變數設定即可,可以修改系統的環境變數,也可以通過程式碼動態新增。

// 設定httpClient的代理
os.Setenv("HTTP_PROXY", "http://127.0.0.1:8888")

解決HTTPS解密失敗的問題

如果Fiddler出現無法解密HTTPS請求,看不到原始請求資料的情況,可以嘗試重新安裝Fiddler的根證照來解決。具體操作:

  1. 開啟Fiddler,依次點選選單Tools->Options,開啟設定對話方塊,點選選中"HTTPS"皮膚

使用 Golang 寫爬蟲經驗總結

  1. 取消“Decrypt HTTPS traffic”的選中狀態,點選“Actions”按鈕,點選“Reset All Certificates”,之後會彈出確認框問你是否要刪除當前的證照並建立新的證照,一路允許即可

使用 Golang 寫爬蟲經驗總結

相關文章