Кілька слів про КПІшну практику, або "Як 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