Статьи

Go против Python: парсинг ответа JSON от HTTP API

В качестве части рекомендаций для Neo4j, которые я представил несколько раз за последний год, у меня есть набор сценариев, которые загружают некоторые данные из API meetup.com .

Все они написаны на Python, но я подумал, что было бы забавно посмотреть, как они будут выглядеть в Go. Моя конечная цель — попытаться распараллелить вызовы API.

Это версия скрипта на Python:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
import requests
import os
import json
  
key =  os.environ['MEETUP_API_KEY']
lat = "51.5072"
lon = "0.1275"
  
seed_topic = "nosql"
uri = "https://api.meetup.com/2/groups?⊤ic={0}⪫={1}&lon={2}&key={3}".format(seed_topic, lat, lon, key)
  
r = requests.get(uri)
all_topics = [topic["urlkey"for result in r.json()["results"] for topic in result["topics"]]
  
for topic in all_topics:
    print topic

запросы на импорт импорт или импорт json key = os.environ [‘MEETUP_API_KEY’] lat = «51.5072» lon = «0.1275» seed_topic = «nosql» uri = «https://api.meetup.com/2/groups?&topic= {0} & lat = {1} & lon = {2} & key = {3} ”. Format (seed_topic, lat, lon, key) r = arguments.get (uri) all_topics = [topic [« urlkey »] для результата в r.json () [«results»] для темы в результате [«themes»]] для темы в all_topics: распечатать тему

Мы используем библиотеку запросов, чтобы отправить запрос API-интерфейсу Meetup, чтобы получить группы с темой nosql в районе Лондона. Затем мы анализируем ответ и распечатываем темы.

Теперь сделать то же самое в Go! Первый бит сценария практически идентичен:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import (
    "fmt"
    "os"
    "net/http"
    "log"
    "time"
)
  
func handleError(err error) {
    if err != nil {
        fmt.Println(err)
        log.Fatal(err)
    }
}
  
func main() {
    var httpClient = &http.Client{Timeout: 10 * time.Second}
  
    seedTopic := "nosql"
    lat := "51.5072"
    lon := "0.1275"
    key := os.Getenv("MEETUP_API_KEY")
  
    uri := fmt.Sprintf("https://api.meetup.com/2/groups?⊤ic=%s⪫=%s&lon=%s&key=%s", seedTopic, lat, lon, key)
  
    response, err := httpClient.Get(uri)
    handleError(err)
    defer response.Body.Close()
    fmt.Println(response)
}

import («fmt», «os», «net / http», «log», «время») func handleError (ошибка err) {if err! = nil {fmt.Println (err) log.Fatal (err)}} func main ( ) {var httpClient = & http.Client {Timeout: 10 * time.Second} seedTopic: = «nosql» lat: = «51.5072» lon: = «0.1275» ключ: = os.Getenv («MEETUP_API_KEY») uri: = fmt Ответ .Sprintf («https://api.meetup.com/2/groups?&topic=%s&lat=%s&lon=%s&key=%s», seedTopic, lat, lon, key), err: = httpClient.Get ( uri) handleError (err) отложить ответ. Body.Close () fmt.Println (response)}

Если мы запустим это, мы увидим вывод:

1
2
3
$ go cmd/blog/main.go
  
&{200 OK 200 HTTP/2.0 2 0 map[X-Meetup-Request-Id:[2d3be3c7-a393-4127-b7aa-076f150499e6] X-Ratelimit-Reset:[10] Cf-Ray:[324093a73f1135d2-LHR] X-Oauth-Scopes:[basic] Etag:["35a941c5ea3df9df4204d8a4a2d60150"] Server:[cloudflare-nginx] Set-Cookie:[__cfduid=d54db475299a62af4bb963039787e2e3d1484894864; expires=Sat, 20-Jan-18 06:47:44 GMT; path=/; domain=.meetup.com; HttpOnly] X-Meetup-Server:[api7] X-Ratelimit-Limit:[30] X-Ratelimit-Remaining:[29] X-Accepted-Oauth-Scopes:[basic] Vary:[Accept-Encoding,User-Agent,Accept-Language] Date:[Fri, 20 Jan 2017 06:47:45 GMT] Content-Type:[application/json;charset=utf-8]] 0xc420442260 -1 [] false true map[] 0xc4200d01e0 0xc4202b2420}

$ go cmd / blog / main.go & {200 OK 200 HTTP / 2.0 2 0 map [X-Meetup-Request-Id: [2d3be3c7-a393-4127-b7aa-076f150499e6] X-Ratelimit-Reset: [10] Cf -Ray: [324093a73f1135d2-LHR] X-Oauth-Области применения: [базовый] Etag: [«35a941c5ea3df9df4204d8a4a2d60150»] Сервер: [cloudflare-nginx] Набор-cookie: [__ cfduid = d54d3046464499499299599175199199199199199299399399299299 истекает = суббота, 20 января-18 06:47:44 по Гринвичу; Путь = /; домен = .meetup.com; HttpOnly] X-Meetup-Server: [api7] X-Ratelimit-Limit: [30] X-Ratelimit-Remaining: [29] X-Accepted-Oauth-Scopes: [базовый] Варьируется: [Accept-Encoding, User-Agent , Accept-Language] Дата: [Пт, 20 января 2017 г. 06:47:45 GMT] Тип содержимого: [application / json; charset = utf-8]] 0xc420442260 -1 [] false true map [] 0xc4200d01e0 0xc4202b2420}

Все идет нормально. Теперь нам нужно разобрать ответ, который возвращается.

В большинстве примеров, с которыми я сталкивался, предлагается создать структуру со всеми полями, которые вы хотите извлечь из документа JSON, но для такого простого сценария это кажется слишком сложным.

Вместо этого мы можем просто создать карты (string -> interface {}) и затем применить преобразования типов, где это уместно. Я получил следующий код для извлечения тем:

01
02
03
04
05
06
07
08
09
10
11
12
13
import "encoding/json"
  
var target map[string]interface{}
decoder := json.NewDecoder(response.Body)
decoder.Decode(⌖)
  
for _, rawGroup := range target["results"].([]interface{}) {
    group := rawGroup.(map[string]interface{})
    for _, rawTopic := range group["topics"].([]interface{}) {
        topic := rawTopic.(map[string]interface{})
        fmt.Println(topic["urlkey"])
    }
}

импортировать «кодирование / json» var target map [string] interface {} decoder: = json.NewDecoder (response.Body) decoder.Decode (& target) для _, rawGroup: = range target [«results»]. ([] интерфейс {}) {group: = rawGroup. (map [string] interface {}) для _, rawTopic: = диапазонная группа [«themes»]. ([] interface {}) {topic: = rawTopic. (map [string] interface {}) fmt.Println (topic [“urlkey”])}}

Это более многословно, чем версия Python, потому что мы должны явно печатать каждую вещь, которую мы берем с карты на каждом этапе, но это не так уж плохо. Это полный скрипт:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package main
  
import (
    "fmt"
    "os"
    "net/http"
    "log"
    "time"
    "encoding/json"
)
  
func handleError(err error) {
    if err != nil {
        fmt.Println(err)
        log.Fatal(err)
    }
}
  
func main() {
    var httpClient = &http.Client{Timeout: 10 * time.Second}
  
    seedTopic := "nosql"
    lat := "51.5072"
    lon := "0.1275"
    key := os.Getenv("MEETUP_API_KEY")
  
    uri := fmt.Sprintf("https://api.meetup.com/2/groups?⊤ic=%s⪫=%s&lon=%s&key=%s", seedTopic, lat, lon, key)
  
    response, err := httpClient.Get(uri)
    handleError(err)
    defer response.Body.Close()
  
    var target map[string]interface{}
    decoder := json.NewDecoder(response.Body)
    decoder.Decode(⌖)
  
    for _, rawGroup := range target["results"].([]interface{}) {
        group := rawGroup.(map[string]interface{})
        for _, rawTopic := range group["topics"].([]interface{}) {
            topic := rawTopic.(map[string]interface{})
            fmt.Println(topic["urlkey"])
        }
    }
}

основной импорт пакета («fmt», «os», «net / http», «log», «time», «encoding / json») func handleError (ошибка err) {if err! = nil {fmt.Println (err) log.Fatal ( err)}} func main () {var httpClient = & http.Client {Timeout: 10 * time.Second} seedTopic: = «nosql» lat: = «51.5072» lon: = «0.1275» ключ: = os.Getenv (« MEETUP_API_KEY ») uri: = fmt.Sprintf (« https://api.meetup.com/2/groups?&topic=%s&lat=%s&lon=%s&key=%s », seedTopic, lat, lon, key) ответ, err: = httpClient.Get (uri) handleError (err) отложить response.Body.Close () var map map [string] interface {} decoder: = json.NewDecoder (response.Body) decoder.Decode (& target) для _, rawGroup: = целевой диапазон [«результаты»]. ([] interface {}) {group: = rawGroup. (map [string] interface {}) для _, rawTopic: = диапазонная группа [«themes»]. ([] interface {}) {topic: = rawTopic. (map [string] interface {}) fmt.Println (topic [“urlkey”])}}}

Как только я получу эти темы, следующий шаг — сделать больше вызовов API, чтобы получить группы для этих тем.

Я хочу выполнять эти вызовы API параллельно, не допуская превышения ограничений по скорости для API, и думаю, что я могу использовать для этого процедуры go, каналы и таймеры. Но это для другого поста!

Ссылка: Go vs Python: анализ ответа JSON от HTTP API от нашего партнера по JCG Марка Нидхэма в блоге Марка Нидхэма .