GoLang—爬虫—解析JSON数据

痛定思痛。 2024-04-18 03:35 160阅读 0赞

JSON作为一种重要的数据格式,具有良好的可读性以及自描述性,广泛地应用在各种数据传输场景中。在网络爬虫中,当网页采用AJAX方式渲染数据时,我们必须找出AJAX的异步请求方式,并且模拟发送AJAX,从中获取数据内容,AJAX的响应数据大部分采用JSON格式表示。
GoLang可以使用标准库encoding/json解析JSON数据,此外还有第三方包ffjson、easyjson、jsoniter和jsonparser等等。在性能上,第三方包完胜标准库encoding/json,本文将分别讲述标准库encoding/json和第三包jsoniter的使用。

标准库encoding/json

回顾GoLang—爬虫入门基础—数据清洗(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数据,实现代码如下。

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "net/http"
  7. "net/url"
  8. "time"
  9. )
  10. // 使用映射传递函数参数,requestMode作为HTTP的请求方式
  11. func SendHttp(urls string, method string, rawurl string, cookie []http.Cookie)string{
  12. req, _ := http.NewRequest(method ,urls, nil)
  13. //为请求对象NewRequest设置请求头
  14. req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
  15. 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")
  16. //设置Cookies信息
  17. if cookie != nil {
  18. for _, v := range cookie{
  19. req.AddCookie(&v)
  20. }
  21. }
  22. //设置代理IP,代理IP必须以为fun形式表示
  23. client := &http.Client{}
  24. if rawurl != "" {
  25. proxy := func(_ *http.Request) (*url.URL, error) {
  26. return url.Parse(rawurl)
  27. }
  28. transport := &http.Transport{Proxy: proxy}
  29. //在Client对象设置参数Transport即可实现代理IP
  30. client.Transport = transport
  31. }
  32. //执行HTTP请求
  33. resp, _ := client.Do(req)
  34. //读取响应内容
  35. body, _ := ioutil.ReadAll(resp.Body)
  36. return string(body)
  37. }
  38. func main() {
  39. 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"
  40. method := "GET"
  41. //rawurl := "http://111.231.93.66:8888"
  42. rawurl := ""
  43. var cookie []http.Cookie
  44. c := http.Cookie{Name: "clientcookieid", Value: "121", Expires: time.Now().Add(111 * time.Second)}
  45. cookie = append(cookie, c)
  46. result := SendHttp(urls, method, rawurl, cookie)
  47. fmt.Println(result)
  48. // 定义make(map[string]interface{})
  49. r := make(map[string]interface{})
  50. fmt.Println([]byte(result))
  51. // 调用标准库encoding/json的Unmarshal
  52. // 将JSON数据(JSON以字符串形式表示)转换成[]byte,并将数据加载到对象r的内存地址
  53. json.Unmarshal([]byte(result), &r)
  54. // r["data"]是读取JSON最外层的key
  55. // 如果嵌套JSON数据,则使用map[string]interface{}读取下一层的JSON数据
  56. // 如读取key为data里面嵌套的result:r["data"].(map[string]interface{})["result"]
  57. // 如果JSON的某个key的数据以数组表示,则使用([]interface{})[index]读取数组中某个数据。
  58. // 如读取key为result的第四个数据:r["data"].(map[string]interface{})["result"].([]interface{})[3]
  59. fmt.Println(r["data"].(map[string]interface{})["result"].([]interface{})[3])
  60. }

运行上述代码,其运行结果如图所示。
在这里插入图片描述
除了可以使用Unmarshal函数来解封 JSON,还可以使用Decoder手动地将 JSON 数据解码到结构里面,以此来处理流式的 JSON 数据(即JSON数据过大的时候),如图所示。
在这里插入图片描述
通过调用NewDecoder并传入一个包含 JSON 数据的io.Reader,程序创建出了一个新的解码器。在把指向Post结构的引用传递给解码器的Decode方法之后,被传入的结构就会填充上相应的数据,然后这些数据就可以为程序所用了。当所有 JSON 数据都被解码完毕时,Decode方法将会返回一个EOF,而程序则会在检测到这个EOF之后退出for循环。

在面对 JSON 数据时,我们可以根据输入决定使用Decoder还是Unmarshal:如果 JSON 数据来源于io.Reader流,如http.Request的Body,那么使用Decoder更好;如果 JSON 数据来源于字符串或者内存的某个地方,那么使用Unmarshal更好。

**除此之外,标准库encoding/json还可以调用函数Marshal或MarshalIndent(MarshalIndent将JSON数据格式化输出,会将数据自动分段分行处理),将特定的数据转化成JSON数据。此外还可以使用Decoder,代码如下。
在这里插入图片描述
程序会创建一个用于存储 JSON 数据的 JSON 文件,并通过把这个文件传递给NewEncoder函数来创建一个编码器。接着,程序会调用编码器的Encode方法,并向其传递一个指向Post结构的引用。在此之后,Encode方法会从结构里面提取数据并将其编码为 JSON 数据,然后把这些 JSON 数据写入创建编码器时给定的 JSON 文件里面。

第三包jsoniter

第三包jsoniter是100% 兼容原生库,但是性能超级好,预先缓存了对应struct的decoder实例,然后unsafe.Pointer省掉了一些interface{}的开销,还有一些文本解析上的优化。
首先在CMD里输入第三包jsoniter的安装指令,如下所示:

  1. go get github.com/json-iterator/go

jsoniter的使用方式也相对简单,只需定义ConfigCompatibleWithStandardLibrary对象即可,由该对象调用Unmarshal或Marshal函数即可实现JSON的解析和转换,比如上述代码中,我们只需修改包的引入和定义ConfigCompatibleWithStandardLibrary即可,代码如下

  1. import (
  2. "fmt"
  3. "github.com/json-iterator/go"
  4. "io/ioutil"
  5. "net/http"
  6. "net/url"
  7. "time"
  8. )
  9. ……………………(省略相同代码)
  10. func main() {
  11. ……………………(省略相同代码)
  12. // 定义make(map[string]interface{})
  13. r := make(map[string]interface{})
  14. var json = jsoniter.ConfigCompatibleWithStandardLibrary
  15. json.Unmarshal([]byte(result), &r)
  16. fmt.Println(r["data"].(map[string]interface{})["result"].([]interface{})[3])

如果想要了解第三包jsoniter的实现原理,可以参考官方的Github地址:jsoniter

综合上述,本博文只简单讲述了标准库encoding/json和第三包jsoniter如何解析JSON数据,下一节将讲述如何将爬取的数据进行入库处理。

发表评论

表情:
评论列表 (有 0 条评论,160人围观)

还没有评论,来说两句吧...

相关阅读

    相关 GoLang爬虫解析JSON数据

    SON作为一种重要的数据格式,具有良好的可读性以及自描述性,广泛地应用在各种数据传输场景中。在网络爬虫中,当网页采用AJAX方式渲染数据时,我们必须找出AJAX的异步请求...

    相关 json解析数据

    今天工作中遇到了 js 解析 后端php传递过来的json字符串错误使用alert查看数据 结果返回的是Undefined类型(未定义或者属性为null),去网上看了下关于js