JSON作为一种重要的数据格式,具有良好的可读性以及自描述性,广泛地应用在各种数据传输场景中。在网络爬虫中,当网页采用AJAX方式渲染数据时,我们必须找出AJAX的异步请求方式,并且模拟发送AJAX,从中获取数据内容,AJAX的响应数据大部分采用JSON格式表示。
GoLand可以使用标准库encoding/json解析JSON数据,此外还有第三方包ffjson、easyjson、jsoniter和jsonparser等等。在性能上,第三方包完胜标准库encoding/json,本文将分别讲述标准库encoding/json和第三包jsoniter的使用。
标准库encoding/json
回顾GoLand—爬虫入门基础—数据清洗(goquery),我们将HTTP发起请求封装在函数SendHttp,主函数main实现函数SendHttp调用和响应内容的数据处理。本文继续沿用上一节的功能代码,将发送HTTP的网址改为12306的余票查询,当我们在网页上输入查询信息并点击查询按钮即触发AJAX请求,如图所示。
图上的AJAX请求的响应内容为JSON格式,标准库encoding/json解析JSON有两种方式:根据JSON内容格式定义对应的结构体(struct)、使用map[string]interface{}加载JSON数据。
实际工作中,个人不建议采用根据JSON内容格式定义对应的结构体(struct),当JSON内容格式过于复杂的时候,对应的结构体(struct)会随之增加,这样会增加大量的代码。我们采用使用map[string]interface{}加载JSON数据,实现代码如下。
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
)
// 使用映射传递函数参数,requestMode作为HTTP的请求方式
func SendHttp(urls string, method string, rawurl string, cookie []http.Cookie)string{
req, _ := http.NewRequest(method ,urls, nil)
//为请求对象NewRequest设置请求头
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36")
//设置Cookies信息
if cookie != nil {
for _, v := range cookie{
req.AddCookie(&v)
}
}
//设置代理IP,代理IP必须以为fun形式表示
client := &http.Client{}
if rawurl != "" {
proxy := func(_ *http.Request) (*url.URL, error) {
return url.Parse(rawurl)
}
transport := &http.Transport{Proxy: proxy}
//在Client对象设置参数Transport即可实现代理IP
client.Transport = transport
}
//执行HTTP请求
resp, _ := client.Do(req)
//读取响应内容
body, _ := ioutil.ReadAll(resp.Body)
return string(body)
}
func main() {
urls := "https://kyfw.12306.cn/otn/leftTicket/queryT?leftTicketDTO.train_date=2019-09-17&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=GZQ&purpose_codes=ADULT"
method := "GET"
//rawurl := "http://111.231.93.66:8888"
rawurl := ""
var cookie []http.Cookie
c := http.Cookie{Name: "clientcookieid", Value: "121", Expires: time.Now().Add(111 * time.Second)}
cookie = append(cookie, c)
result := SendHttp(urls, method, rawurl, cookie)
fmt.Println(result)
// 定义make(map[string]interface{})
r := make(map[string]interface{})
fmt.Println([]byte(result))
// 调用标准库encoding/json的Unmarshal
// 将JSON数据(JSON以字符串形式表示)转换成[]byte,并将数据加载到对象r的内存地址
json.Unmarshal([]byte(result), &r)
// r["data"]是读取JSON最外层的key
// 如果嵌套JSON数据,则使用map[string]interface{}读取下一层的JSON数据
// 如读取key为data里面嵌套的result:r["data"].(map[string]interface{})["result"]
// 如果JSON的某个key的数据以数组表示,则使用([]interface{})[index]读取数组中某个数据。
// 如读取key为result的第四个数据:r["data"].(map[string]interface{})["result"].([]interface{})[3]
fmt.Println(r["data"].(map[string]interface{})["result"].([]interface{})[3])
}
运行上述代码,其运行结果如图所示。
除此之外,标准库encoding/json还可以调用函数Marshal,将特定的数据转化成JSON数据,详细教程参考:请点击
第三包jsoniter
第三包jsoniter是100% 兼容原生库,但是性能超级好,预先缓存了对应struct的decoder实例,然后unsafe.Pointer省掉了一些interface{}的开销,还有一些文本解析上的优化。
首先在CMD里输入第三包jsoniter的安装指令,如下所示:
go get github.com/json-iterator/go
jsoniter的使用方式也相对简单,只需定义ConfigCompatibleWithStandardLibrary对象即可,由该对象调用Unmarshal或Marshal函数即可实现JSON的解析和转换,比如上述代码中,我们只需修改包的引入和定义ConfigCompatibleWithStandardLibrary即可,代码如下
import (
"fmt"
"github.com/json-iterator/go"
"io/ioutil"
"net/http"
"net/url"
"time"
)
……………………(省略相同代码)
func main() {
……………………(省略相同代码)
// 定义make(map[string]interface{})
r := make(map[string]interface{})
var json = jsoniter.ConfigCompatibleWithStandardLibrary
json.Unmarshal([]byte(result), &r)
fmt.Println(r["data"].(map[string]interface{})["result"].([]interface{})[3])
}
如果想要了解第三包jsoniter的实现原理,可以参考官方的Github地址:jsoniter
综合上述,本博文只简单讲述了标准库encoding/json和第三包jsoniter如何解析JSON数据,下一节将讲述如何将爬取的数据进行入库处理。