Парсим Google Analytics на GO #1

GO это уже даже не "стильно модно молодежно", а просто норма. Он постепенно становится стандартом для написания демонов и других мелких и больших системных утилит. На нем пишут апи, которые обрабатывают кучу запросов и многое другое.

Ссылки на базовые туториалы и другие классные статьи "как начать писать" проще найти в гугле. Здесь же опишу задачу и решение.

Задача

Написать парсер данных из Google Analytics. В первом приближении вытащить только просмотры (pageViews) у заданного массива ссылок.

Прототип

  1. Авторизуемся через OAuth2 в Google
  2. Запрашиваем список доступных View из аналитики
  3. Выбираем нужный View
  4. Получаем от пользователя список ссылок и даты начала и конца поиска

Проблемы по пути

  • Никогда ничего не писал на GO
  • Уродливая документация Google Analytics
  • Библиотеки от Google без нормальных примеров использования

Сразу предупреждаю, код не претендует на красоту и гениальность, это просто пробный заход, накиданный за пару вечеров.

Начало

Установить GO. Ставил через Homebrew версию 1.6.2 – последняя доступная на момент написания статьи.

Далее нужно создать приложение в Google Developers Console. Включить для него Analytics API и Analytics Reporting API V4. Для тестов callback url можно указать на localhost. У меня было указано localhost:3000.

Сохранить куда-то ClientID и ClientSecret. Их можно найти в учетных данных во вкладке слева.

Подготовительные действия закончены, можно приступать к написанию кода.

Для авторизации надо поднять простенький http сервер и сделать несколько страниц. Начать можно со статьи на golang.org – Writing Web Applications. Приятно удивила обширность функционала в стандартных пакетах. Для большинства задач можно не импортировать сторонние библиотеки, а использовать только базовые пакеты и утилиты в комплекте с языком.

Отдельная боль – пакетный менеджер. Его просто нет. На Github можно найти список пакетных менеджеров сторонних разработчиков и выбрать на свой вкус. В статье не буду подробно на нем останавливаться, потому что не использовал и не могу толком сказать какие плюсы и минусы у каждого. С кем я общался – советуют gvt.

К коду. Простой http сервер:

package main

import (  
  "net/http"
)

func indexHandler(w http.ResponseWriter, r *http.Request) {  
  fmt.Frintf(w, "Hello from Index")
}

func main() {  
  http.HandleFunc("/", indexHandler)
  http.HandleAndServe(":3000", nil)
}

Слушаем порт 3000 и по адресу http://localhost:3000/ – отдаем текстом Hello from Index.

Для OAuth 2.0 нам нужно сделать редирект на страницу в Google API, чтобы пользователь разрешил приложению делать разные манипуляции с данными и получить обратно в запросе access_token, который будем использовать в запросах к API.

func authHandler(w http.ResponseWriter, r *http.Request) {  
}

func authHandler(w http.ResponseWriter, r *http.Request) {  
}

func main() {  
  http.HandleFunc("/authorize", authHandler)
  http.HandleFunc("/connect", connectHandler)

  http.HandleFunc("/", indexHandler)
  http.HandleAndServe(":3000", nil)
}

На главной странице выведем ссылку на /authorize, откуда сделаем редирект в Google, чтобы пользователь выдал нам права. На /connect будем ждать ответа от Google с кодом авторизации, по которому получим access_token.

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

Для работы с токеном будем пользоваться библиотекой от Google oauth2, а для работы с сессиями библиотекой sessions из GorillaToolkit.

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

go get github.com/gorilla/sessions  
go get golang.org/x/oauth2  

Импортируем их в проекте.

import(  
  "net/http"
  "github.com/gorilla/sessions"
  "golang.rog/x/oauth2"
  "golang.rog/x/oauth2/google"
)

...

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

import(  
 ...
)

const (  
  clientID        = "your_client_id"
  clientSecret    = "your_client_secret"
  applicationName = "your_application_name"
)

var store = sessions.NewCookieStore([]byte("gaIntegrationStore"))

var config = &oauth2.Config{  
  ClientID: clientID,
  ClientSecret: clientSecret,
  Scopes: []string{"https://www.googleapis.com/auth/analytics", "https://www.googleapis.com/auth/analytics.readonly"},
  Endpoint: google.Endpoint,
  RedirectUrl: "http://localhost:3000/connect",
}

oauth2 – это набор методов для работы с OAuth 2.0 авторизацией, а в папках репозитория лежат в основном Endpoints для разных провайдеров – Google, Slack и т.д..
Сессии мы делаем через cookie без сохранения где-либо еще. При желании их можно хранить в memcache, redis и т.д..

Далее разберемся с шаблонами и отрендерим простую страницу с ссылкой для авторизации. Для этого положим в папку с проектом файл index.html:

<html>  
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta http-equiv="x-ua-compatible" content="ie=edge">

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.2/css/bootstrap.min.css" integrity="sha384-y3tfxAZXuh4HwSYylfB+J125MxIs6mR5FOHamPBG064zB+AFeWH94NdvaCBm8qnd" crossorigin="anonymous">
  </head>
  <body>
    <div class="container">
      <div class="row">
        <div class="col-sm-4">
          <a href="/authorize" class="btn btn-primary"> Authorize GA </a>
        </div>
        <div class="col-sm-4">
        </div>
        <div class="col-sm-4">
        </div>
      </div>
    </div>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.2/js/bootstrap.min.js" integrity="sha384-vZ2WRJMwsjRMW/8U7i6PWi6AlO1L79snBrmgiDpgIWJ82z8eA5lenwvxbMV1PAh7" crossorigin="anonymous"></script>
  </body>
</html>  

Естественно подключили вездесущий Bootstrap. Далее отрендерим шаблон, обратимся к сессии и сделаем логирование:

import(  
  "log"
  "html/template"
  ...
)

...

func indexHandler(w http.ResponseWriter, r *http.Request) {  
  session, err := store.Get(r, "GASession")
  if err != nil {
    log.Println("IH.Error while retrieving session: ", err)
  }
  log.Println("Session: ", session)


  t, _ := template.ParseFiles("index.html")
  t.Execute(w, struct{}{})
}

Что мы сделали – получили сессию, считали index.html и отрендерили страницу. С сессией пока ничего не делаем, понадобится дальше.

Теперь надо отправить пользователя на страницу сервиса Google, где он даст нашему приложения необходимые права.

func authorizeHandler(w http.ResponseWriter, r *http.Request) {  
  url := config.AuthCodeURL("state")
  http.Redirect(w, r, url, http.StatusFound)
}

После того как пользователь нажмет кнопку "Allow", сервис переправит его на урл, который мы указали в настройках /connect, поэтому напишу реализацию обработчика этого действия.

func connectHandler(w http.ResponseWriter, r *http.Request) {  
  code := r.FormValue("code")
  log.Println("AH.Code: ", code)

  tok, err := config.Exchange(oauth2.NoContext, code)
  if err != nil {
    log.Fatalln("AH.Error: ", err)
  }
  log.Println("AH.AccessToken: ", tok)

  session, err := store.Get(r, "GASession")
  if err != nil {
    log.Println("AH.Error :", err)
  }
  session.Values["access_token"] = tok
  session.Save(r, w)

  http.Redirect(w, r, "/", http.StatusFound)
}

Разберу подробнее.

code := r.FormValue("code")  

Когда пользователя средиректит обратно к нам, в параметрах запроса в ссылке будет поле code – проверочный код, по которому мы получим access_token с помощью метода Exchange из библиотеки oauth2.

tok, err := config.Exchange(oauth2.NoContext, code)  

К сожалению складывать все подряд в объект сессии у нас не получиться. Чтобы сохранить там что-то отличное от стандартных типов нужно зарегистрировать тип, который мы хотим сохранить. Регистрируется он в функции init(). Она вызывается после всех определений констант и переменных и до вызова функции main().

func init() {  
  gob.Register(&oauth2.Token{})
}

Далее сохраним объект сессии до отправки ответа:

func connectHandler(w http.ResponseWriter, r *http.Request) {  
  ...
  session.Values["access_token"] = tok
  session.Save(r, w)
  log.Println("AH.Session: ", session)

  http.Redirect(w, r, "/", http.StatusFound)
}

Теперь в indexHandler мы можем обратиться к сессии и вытащить access_token для дальнейшей работы. В других обработчиках мы также можем обращаться к access_token и использовать его для инициализации клиента для запросов в API Google Analytics.

На этом закончу первую часть. Во второй расскажу как использовать библиотеку google.golang.org/api/ для запросов в API Google.

Прочитать

[Отзыв]: Программист-прагматик. Путь от подмастерья к мастеру

Закрывая очередной пункт из нашего списка "must-read" книг в компании, напомню, как я и писал ранее, это будет последняя чисто техническая книга на ближайшие пару месяцев.

Сие произведение вызвало у меня достаточно противоречивые чувства. Во-первых, оно приятно удивило хорошими сравнениями и внятными разъяснениями что есть что, во-вторых, поразило обилием орфографических ошибок и опечаток, настолько некачественно изданную книгу я давно не видел. Ну и в конце оно все-таки свалилось к капитанским вещам.

На счет сравнений и умных мыслей -- писал пару статей(1, 2) по поводу того, что рассматривается в этой книге. Мой совет прочитать первые 2 части, их интересно читать, все остальное в той или иной мере уже где-то было и написано достаточно скучным языком.

Из самой последней части понравилось несколько идей:

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

Пользователей надо восхищать. Обладатели устройств от Apple, вспомните с какими чувствами вы открываете коробочку со своим i что-то, как будто подарок от Деда Мороза лет в 7. Вот именно к этому и надо стремится, чтобы человек приходил к вам и чувствовал себя ребенком, который готов радоваться и удивляться! Но яблочный гигант на самом деле делает еще круче, он как бы "управляет" нашими ожиданиями, т.е. говорит нам, на самом деле ты хочешь эту штуку, потому что она вот настолько крута! А когда мы открываем, то находим еще чуть чуть того о чем нам не сказали и восхищаемся еще больше.

Прагматики не уклоняются от ответственности. Вместо этого они испытывают радость, принимая вызовы и распространяя свой опыт. Если мы несем ответственность за проектное решение или фрагмент программы, мы делаем работу, которой можем гордиться. В прошлом мастеровые гордились, подписывая свою работу. Вы должны следовать их примеру.

В точку! Я даже не знаю что тут еще можно добавить... Просто вся суть работы в этих строчках.

В общем-то все, что я хотел сказать по этой книге. Осталось добавить только ссылку:

"Программист-прагматик. Путь от подмастерья к мастеру" Э. Хант, Д. Томас  
The Pragmatic Programmer: From Journeyman to Master ISBN 5-85582-213-3"Программист-прагматик. Путь от подмастерья к мастеру" Э. Хант, Д. Томас The Pragmatic Programmer: From Journeyman to Master ISBN 5-85582-213-3

Прочитать

Языки

Продолжаю черпать из умных книг всякие мысли:

Границы моего языка есть границы моего мира.

Людвиг фон Витгенштейн

Это многогранная фраза, которая может быть применима для многих вещей и областей. Если вы знаете всего один разговорный язык, например русский, то вы не сможете полностью понять культуру англичан или американцев, их юмор, их сериалы и многое другое. Вам так и будет казаться, что, например, "Гриффины" это глупый сериал со стандартным "туалетным" юмором, а не комедия, которая по-черному высмеивает многие будничные аспекты жизни американских жителей, а также старается обратить внимание на какие-то важные политические события.

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

Это в продолжение темы портфеля знаний и необходимости изучать хотя бы по одному новому языку в год. Этим вы расширяете не только свой кругозор, но и свою способность увидеть проблему и знать её решение заранее. Именно поэтому стоит прочитать хотя бы одну книгу про функциональный подход, чтобы хотя бы понимать о чем это и зачем это.

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

Выделяйте время, чтобы учить новые языки, как для разговоров с людьми, так и для разговоров с машинами.

Прочитать

Портфель знаний

Инвестиции в знания окупаются лучше всего. Бенджамин Франклин

В одной умной книге вычитал отличную метафору для определения опыта программистов -- портфель знаний. Сравнение знаний и опыта профессионала с инвестиционным портфелем однозначно цепляет.

То, что вы получили за годы работы это ваши накопления -- опыт, то, что вы прочитали за эти годы это ваши вложения, а как вы наверное знаете самое важное отличие инвестиций от банальных спекуляций — это получение прибыли сразу. Чтобы получить прибыль на спекуляции вам нужно что сначала купить, т.е. вложить деньги, а затем это что-то продать, но всегда есть вероятность, что вы этого сделать не сможете в принципе или не сможете этого сделать по приемлемой для вас цене. Когда вы инвестируете, вы начинаете сразу получать прибыль.

Аналогии работают и для мира программирования, можно и нужно сразу применять все, что вы узнали из книг, необязательно бросаться сразу со скалы вниз и пытаться применить абсолютно все, инвесторы никогда не идут ва банк.

Самое классное, что управление портфелем знаний практически идентично на управление финансовым, есть набор правил, следую которым в итоге придешь к успеху:

  1. Инвестируй регулярно — должно стать привычкой
    • читай каждый квартал по технической книге
    • изучай что-то новое
    • посещай конференции
  2. Диверсификация — залог успеха
    • нельзя зацикливаться на одной технологии
    • нужно изучать новые языки, методологии и подходы
  3. Портфель должен быть сбалансирован
    • если умеешь делать Web приложение, попробуй сделать GUI
    • попробуй пописать приложения на других ЯП
    • займись front частью
  4. Покупай дешевле — продавай подороже
    • изучай новые технологии, когда они не на слуху, 9 из 10 ты не угадаешь будущий мейнстрим, но одного раза хватит, чтобы выпрыгнуть очень высоко
  5. Нужен периодический пересмотр портфеля
    • остановись и осмотрись, что сейчас популярно на рынке
    • старайся предвидеть переходы и лови тренд заранее

Следование этим простым 5 принципам сделает вашу карьеру успешной!

Прочитать

[Отзыв]: Рефакторинг с использованием шаблонов

Продолжаю осваивать техническую литературу из нашего списка. Хорошая книга, дала много полезных мыслей. Выпишу тут немного фраз, которые запомнились:

  • Рефакторинг - преобразование, сохраняющее поведение
  • В разработке необходимо гармоничное существование рефакторинга с коммерческими решениями
  • История про шляпу и "рефакторинг" вывески, про которую рассказывал ранее
  • Необходимо возвращать долги проектирования
  • Начальное проектирование по шаблоном возможно, но нужно применять его очень рассудительно и не повсеместно, лучше дать системе эволюционировать

Это достаточно общие мысли, которые применимы для систем в целом. Автор старался рассказывать о рефакторинге в контексте XP и применения шаблонов. Что радует в этой книге, так это её практическая сторона, автор старался брать примеры из своих работ, из тех мест, где он действительно их внедрял,а не просто синтетические примеры и ситуации.

Рассматриваются ситуации, когда применение шаблонов для рефакторинга только усложняет код и является плохим решением. \r\nНа мой взгляд, книга достаточно полезная. Из минусов -- ну не нравятся мне примеры с синтаксическими анализаторами или билдерами XML и HTML. Я понимаю, что они отлично подходят для примеров, но все равно остается ощущение натянутости этих решений, не по всей книге конечно, но в некоторых местах точно такое ощущение возникало.

Подводя итог, в очередной раз убеждаюсь, что в программировании нужно следовать одному простому правилу — думай головой.

Ссылка на книгу:

"Рефакторинг с использованием шаблонов" Джошуа Кериевски - Refactoring to Patterns ISBN 5-8459-1087-5"Рефакторинг с использованием шаблонов" Джошуа Кериевски - Refactoring to Patterns ISBN 5-8459-1087-5

Прочитать