使用 Golang 写爬虫经验总结
模拟登录
模拟登录最重要的是保存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}
POST提交
GET提交方式很简单,直接拼接字符串就行了。POST提交需要一个可读的(io.Reader
)请求体body
,body中是形如a=b&c=d
格式的查询字符串(字节切片)。可以借助url.Values
方便的生成查询字符串,它本质是一个map[string][]string
,提供了Set
,Add
等方法,让操作这个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
作参数,这个参数即我们的表单体body
,body
除了需要可写,还要可读(让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
即表单体中分隔每个参数的一个标志位,如下图:
使用Fiddler调试程序
使用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的根证书来解决。具体操作:
- 打开Fiddler,依次点击菜单Tools->Options,打开设置对话框,点击选中"HTTPS"面板
- 取消“Decrypt HTTPS traffic”的选中状态,点击“Actions”按钮,点击“Reset All Certificates”,之后会弹出确认框问你是否要删除当前的证书并创建新的证书,一路允许即可
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: