Кілька слів про КПІшну практику, або "Як NodeJS'ер смикав Twitter API з Python'а"

Перш ніж перейти до суті проекту, розкажу передісторію. Якщо вам вона нецікава – переходьте відразу до початку розробки.

Передісторія

Все почалось ще ген зна коли. До мене дійшла інфа, що влітку 3-го курсу буде практика. Само собою, я про це не сильно хвилювався аж до травня цього року. Як бекендер на JS (специфічні смаки і все-таке :), на початках я збирався знайти практику на NodeJS, або хоча б підтягнути фронтенд. На горизонті майоріли світлі прапори Infopulse і EPAM, та до перших я так і не знайшов час податися, а до других мені після вхідного тесту перехотілося йти, так що я не засмутився, як не пройшов.

Так як глашатаї ФІОТа рознесли, що документи про проходження практики не на кафедрі/НІІ приймають до 26-го травня (насправді ні – можна прийти й домовитись про принесення доків пізніше, або просто принести пізніше, проте вас можуть послати, навіть якщо вас півгрупи), так що я задався ціллю знайти собі нормальну і цікаву практику до Дня X.

На той момент лишалось 10 днів до “дедлайну подачі”, тож я вирішив попитати знайомих старшокурсників, хто, як і де проходив практику, і чи не знають вони, де можна поJSсити. Так я вийшов на WDC, що має офіс у 6-му корпусі, і проект на NodeJS.

Тож, наступного дня, у вівторок, я зайшов з фразою “чув що у вас можна практику пройти на NodeJS” у 318-6, де секретар взяла мій контакти і… на тому все. Так як тиждень не було ніякої реакції, то я, для перестраховки, у суботу вирішив податись на організатора івентів по DevOps, аби розібратись що ж це таке, та й в організації івентів маю досвід. Коли мені звідти таки передзвонили, я вже влаштувався на практику в WDC :)

Скажу вам, що нічого смішнішого, ніж написання розділу скілів у резюме івент-мейкера, я у цій сфері ще не робив. Серйозно, скіл володіння МС Офісом, гугло-доками і формами, відеодзвінки і т.д. – це ж треш якийсь)
Остаточну версію можна переглянути тут.

Так як програмування мені імпонує трохи більше за організацію подій, я в понеділок (3 дні до дедлайну) вирішив навідатись в WDC і попитати щодо практики. Цього разу удача була на моїй стороні, бо на місці був і головний по практиці, і, як пізніше з’ясувалось, мій керівник з практики. NodeJS мені не дістався, бо усі проекти на ньому зав’язані на Болдака (це класнючий препод на кафедрі ОТ, ФІОТ), а він цього літа не хотів займатись даною справою.

Тож, мене відправили до Путренка Віктора Валентиновича - дуже прошареної в географії людини. Після маленьких розпитів на тему “хто я такий, що вмію і чого хтів би навчитись”, де я, між іншим, згадав, що влітку збираюсь повчити Python, мені була делегована доволі актуальна і цікава задача.

Задача

Задача полягала в тому, щоб розібратись, чи працює в QGIS (опенсорсна прога для візуалізації гео-даних) експериментальний плагін twitter2qgis (github) по діставанню геотвітів (твіти з координатами) і зберіганню їх у Shapefile (бажано), або у GeoJSON. Якщо не працюватиме – зробити свій, інакше – отримати іншу задачу.

Плагін виявився неробочим.

Наступним кроком був пошук будь-яких робочих опенсорс аналогів, які б працювали. Можливо я погано шукав, та вдалось знайти аж 1 консольну програму, що таки працювала! Правда, вона крешилася вже після захоплення одного-єдиного геотвіту, на моменті відображення координат у консольці. ¯_(ツ)_/¯

Реалізація

Функціональність

В процесі розробки мені вдалося реалізувати наступну функціональність:

  • Можливість підкидувати ключі авторизації до API твіттера через GUI і зберігати їх у файлі. В разі відсутності нових – підвантажувати попередні.
  • Дані для пошуку:
    • Локація (полігон)
    • Ключові слова
    • Кількість твітів
    • Час, коли зупинити пошук
  • Виведення результатів:
    • Метод пошуку:
      • В ріалтаймі (через Streaming API)
      • По всьому твіттеру (REST API)
    • Кількість даних, що зберігаються у кожному твіті:
      • Всі
      • Мінімальна кількість (дата створення, текст твіту і координати)
      • Жодних (тільки координати)
    • Формат даних на виході:
      • GeoJSON
      • Shapefile
      • У вигляді шару в QGIS
  • Help до кожного пункту, що може викликати питання.

Станом на 16.07.16 це виглядає так:

Більше інформації по кожному з пунктів доступно в документації проекту.

Розробка console-версії

Першим ділом я поринув у різнобарвний світ Twitter API, де й завис на день.

З хорошого: в документації є все. Або майже все.

З поганого: треба прочитати добіса тексту, інакше не зрозумієш, що відбувається.

Наступного дня, все ще не до кінця відійшовши від апі твіттера, вирішив заюзати вибрані методи. Проте з 2012-го року напряму до них достукатись не можна – потрібні ключі авторизації. Постійний ключ можна отримати виконуючи наступні інструкції.

Крім того, для простого доступу до апі, знадобиться бібліотека, в моєму випадку – tweepy. Хто ж хоче підтримку Python 3 – для них існує twython і трохи реалізації на ньому.

На жаль, QGIS не вміє у 3-ій Python, так що більшість подальших костилів будуть пов’язані з особливостями другої версії.

Спочатку я вирішив реалізувати звернення через метод filter з Streaming API, що дозволило б колекціонувати твіти в реальному часі.

class listener(StreamListener):
    def on_data(self, data):      
        return True

    def on_error(self, status):
        print 'ERROR ' + str(status)
        if status == 420:
            print 'Exceed a limit requests connect to' \
                + 'the streaming API in a window of time (15min)'
            return False  # Returning False in on_data disconnects the stream

auth = OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)

stream = Stream(auth, listener())
stream.filter(locations=locations, track=keywords)

В on_data потрапляють всі твіти, що задовольняють задані в stream.filter умови. Додавання locations обмежує вхідний потік тільки геотвітами, що прискорює пошук останніх в ~20 разів.

Наступний крок – перетворення отримуваного json в geojson.

Так, виглядає типовий geojson-файл:

{ "type": "FeatureCollection",
    "features": [
      { "type": "Feature",
        "geometry": {"type": "Point", "coordinates": [102.0, 0.5]},
        "properties": {"prop0": "value0"}
        },
       ]
     }

Так як Твіттер за один раз віддає 1 твіт, то перетворити його у geojson не складає проблем.

Для цього треба витягти геометрію за ключами з твіту і передати усю інформацію, що нас цікавить, у "properties".
Для полігонів координати лежать в ’[“place”][“bounding_box”][“coordinates”]’, для точок – в ["geo"]["coordinates"].

Ніби все просто, та тут виявилося, що QGIS відображає дані різних типів у різних шарах. Це ускладнює обробку інформації, бо замість1 таблиці з даними по твітах ви матимете 2-3 менших.

Тож було прийнято рішення звести кожен полігон до точки у його центрі.

Далі постала задача зробити shapefile з наявного geojson. Для цього я використав бібліотеку pyshp, що вміє у конвертацію всього і вся у шейпфайли. Швидко розібратись, що до чого, допоміг туторіал по перетворенню CSV в Shapefile.

Паралельно з усім описаним відбувалися танці з бубном навколо кодування рядків у другому пайтоні. Кому цікаво, у що це вилилось – ось код консольної версії.

І само собою, для гарного відображення в консольці додав функцію print_time, що відображає поточний стан речей.

Розробка GUI

До того моменту, як я прочитав у вікі, що QGIS – це скорочення від Quantum GIS, то щиро вірив що Q означає Qt, бо на ньому висить гуі як самого QGIS, так і плагінів до нього. Пояснити, чому саме Qt (і чому Qt4), буде простіше, якщо згадати, що до версії 0.8 QGIS вмів у плагіни тільки на C++.

Qt раніше не використовував, а ставити Qt Designer заради такого маленького плагіна мені здалось оверкілом. До того ж, я мав у своєму розпорядженні інтерфейс twitter2gis!

Так, він незрозумілий і абсолютно не інтуїтивний, проте це вже щось.

Згенерував собі чистий додаток за допомогою QGIS plugin creator і підкинув в нього .ui файл twitter2gis, який складається з одного лиш xml.

Першим ділом рефакторнув xml, що його 100% Qt Designer згенерував, бо нормальні люди код так не пишуть.

Потім рефакторнув ще раз, і ще раз.

Заліз у документацію, зрозумів що вона взагалі ніяк не розрахована на пряме редагування xml і, замість того, щоб поставити клятий Qt Designer, я почав експерементувати. Практика, все ж!

Наприклад, все, що в доках Qt починається на set, має одноіменний аналог в xml, тільки вже без set: setText(const QString &) в класі QLabel, аналогічно

<widget class="QLabel" name="label">
  <property name="text">
    <string></string>
  </property>
</widget>

Далі понавішував усіляких фінтєфлюшечок і розбив на категорії. Після декількох годин пересовування на 3 пікселя кнопочок, я був задоволений результатом. Отримав наступне:

Об’єднання і допилювання

Наступним етапом стало об’єднання консольного коду і графічного інтерфейсу.

Потрібно помістити код в if result == 1:, і тоді він виконуватиметься при натиснені “ОК” в GUI. Але так як ідея зміщувати весь основний код – не з розумних, і методом тику було встановлено, що def run працює у якомусь циклі (інакше як воно if result == 1 вчасно ловить), я вирішив оброблювати протилежну ситуацію:

if not result:
    return False

Перемістивши код під if, запустив плагін на виконання і… QGIS завис до моменту закінчення роботи плагіну. Це Вирішилося додаванням параметра async=True в streamAPI() tweepy.

Далі я захотів прогрес-бар, як в geotweet. Ну що ж, я його зробив (рерайтинг:)). Тільки виникло одне АЛЕ: QGIS в синхронному режимі роботи плагіна зависає і не виводить повідомлень до завершення виконання, а в асинхронному закривається після першого графічного повідомлення. Це нікуди не годиться.

Тож я звернув увагу на консольні повідомлення.

За замовчуванням ця панель логів прихована. Та в неті є хак-рецепт по її примусовому відкриттю:

logDock = self.iface.mainWindow().findChild(QDockWidget, 'MessageLog')
logDock.show()

Тож, при запуску плагіна на виконання, вискакує така панелька: В ній через кожних 50 запитів (~щосекунди) виводиться поточний стан справ, і, що головне, в той же час можна паралельно працювати в QGIS.

Плани на майбутнє

Допилити плагін, а саме:

  • Додати пошук через REST API (зараз тільки через Streaming API)
  • Додати тести по навантаженню на систему кожною конфігурацією та іншу документацію
  • Виводити в консоль QGIS усі помилки (python і т.д.)
  • Зробити повноцінну консольну версію
  • Зробити валідатор даних у geojson. Від виконуватиме перевірки і перетворення отриманих даних у валідний geojson в разі несанкціонованого закриття програми.

Репозиторій доступний тут. Наразі там 2 гілки: сам плагін (master) і зачатки консольної версії.

Конструктивна критика коду і стилю (з опором на стайлгайди) – вітається)

Tags: python, qt, qgis, літня практика, КПІ, WDC