Статьи

Создание приложения для анализа настроений с использованием Node.js

Анализ настроений помогает вам понять, что люди думают об определенной теме. Приложение для анализа настроений, которое мы собираемся создать, примет ключевое слово (слова) и получит соответствующие твиты из Twitter. Затем он будет запускать каждый твит через модуль узлов анализа настроений с поддержкой AFINN. Этот модуль оценит текст твита и, наконец, отобразит соответствующую статистику.

Прежде чем мы начнем, вы можете посмотреть демо здесь . Вы можете скачать полный исходный код здесь .

Технологический стек

Это приложение построено поверх Nodejs . Мы будем использовать Express в качестве нашей серверной инфраструктуры и jQuery для манипулирования DOM на стороне клиента. Чтобы упростить для нас задачу, мы будем использовать генератор Slush с именем slush-express (написанный мной) для создания нового приложения Express для нас. Для хранения результатов мы будем использовать легковесную дисковую БД с именем diskDB (также написанную мной).

Мы будем использовать модуль Twitter для взаимодействия с Twitter и модуль Sentiment для выполнения анализа.

Итак, начнем.

Настройте приложение

Создайте новую папку с именем sentimentAnalysisApp и откройте новый терминал / приглашение здесь. Сначала мы собираемся установить генератор Gulp, Slush и Express с помощью следующей команды.

 npm i -g gulp slush slush-express 

После установки вышеуказанных модулей мы создадим новое приложение Express с помощью этой команды:

 slush express 

Slush попросит вас выбрать движок представления и движок таблиц стилей. Ответьте, как показано ниже.

 [?] Select a View Engine: HTML [?] Select a Stylesheet Engine: CSS 

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

 sentimentAnalysisApp ├── Gulpfile.js ├── app.js ├── bin │   └── www ├── bower.json ├── package.json ├── public │   └── stylesheets │   └── style.css ├── routes │   ├── index.js │   └── users.js └── views ├── error.html └── index.html 

Вот краткое объяснение различных файлов и папок.

  • bin / www — здесь указывается инициация сервера и port .
  • app.js — здесь настраиваются конфигурация сервера, маршруты и механизм просмотра.
  • gulpFile.jsзапуск задач для нашего проекта.
  • / public — Состоит из статических файлов, отправляемых в пользовательский интерфейс.
  • / маршруты — состоит из маршрутов приложения.
  • / views — Состоит из представлений приложения.

Вы можете запустить приложение, выполнив команду gulp . Это запустит сервер Express на порту 3000. Перейдите по http://localhost:3000 и вы увидите пример домашней страницы.

Разработка на стороне сервера

Сначала мы установим зависимости уровня приложения с помощью следующей команды:

 npm i twitter sentiment --save 

Затем создайте новую папку с именем logic в корне проекта. Создайте два файла с именами twitterSearch.js и sentimentAnalysis.js . Эти файлы состоят из логики для получения твитов из Twitter и выполнения анализа, соответственно. Создайте другую папку с именем db , в которой будут храниться данные.

Затем откройте routes/index.js в вашем любимом редакторе. Мы добавим новый маршрут, POST /search . Введенный пользователем текст поиска будет отправлен в эту конечную точку. Обновите routes/index.js как показано ниже.

 'use strict'; var express = require('express'); var router = express.Router(); var twitterSearch = require('../logic/twitterSearch'); /* GET home page. */ router.get('/', function(req, res) { res.render('index'); }); router.post('/search', function(req, res) { twitterSearch(req.body.search, function (data) { res.json(data); }); }); module.exports = router; 

twitterSearch() функция twitterSearch() будет принимать условия поиска и получать соответствующие твиты из Twitter. Эти твиты затем будут переданы в модуль анализа настроений, а результаты будут возвращены как обратный вызов. Просто, правда?

Затем откройте logic/twitterSearch.js и добавьте следующий код.

 //includes var util = require('util'), twitter = require('twitter'), sentimentAnalysis = require('./sentimentAnalysis'), db = require('diskdb'); db = db.connect('db', ['sentiments']); //config var config = { consumer_key: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', consumer_secret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', access_token_key: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxx', access_token_secret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx' }; module.exports = function(text, callback) { var twitterClient = new twitter(config); var response = [], dbData = []; // to store the tweets and sentiment twitterClient.search(text, function(data) { for (var i = 0; i < data.statuses.length; i++) { var resp = {}; resp.tweet = data.statuses[i]; resp.sentiment = sentimentAnalysis(data.statuses[i].text); dbData.push({ tweet: resp.tweet.text, score: resp.sentiment.score }); response.push(resp); }; db.sentiments.save(dbData); callback(response); }); } 

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

Перейдите к приложениям Twitter и нажмите « Создать новое приложение» . Заполните форму на следующей странице. Как только приложение будет создано, мы сгенерируем необходимые токены. Нажмите на вкладку API Keys и прокрутите вниз до нижней части страницы. Там нажмите на Создать мой токен доступа . Как только это будет сделано, вы увидите сообщение, чтобы обновить страницу, продолжайте и сделайте это. Теперь вы можете увидеть ключ API, секрет API , токен доступа и секрет токена доступа и заполнить объект config следующим образом:

 var config = { consumer_key: 'API key', consumer_secret: 'API secret', access_token_key: 'Access token', access_token_secret: 'Access token secret' }; 

Если у вас возникли проблемы, обратитесь к этому обсуждению .

Затем откройте logic/sentimentAnalysis.js и добавьте следующий код.

 var sentiment = require('sentiment'); module.exports = function(text) { return sentiment(text); }; 

Логика очень проста. мы берем текст твита и возвращаем объект sentiment . Это оборачивает наш сервер. Теперь давайте построим клиент.

Разработка на стороне клиента

Сначала создайте новую папку с именем scripts внутри общей папки. Внутри scripts создайте новый файл с именем app.js и откройте его в своем любимом редакторе. app.js отвечает за app.js формы через Ajax в конечную точку /search и отображение результатов на странице.

Мы собираемся использовать JavaScript- библиотеку Джона Резига для построения разметки на основе данных сервера ( если хотите, небольшого размера MV * ). Я изменил библиотеку шаблонов, чтобы мы могли использовать {{ }} вместо <% %> синтаксиса. Таким образом, мы можем использовать ejs качестве шаблонов на стороне сервера для рендеринга страниц расширения HTML. Завершенный app.js показан ниже.

 $(document).ready(function() { // handle the form submit $('#searchText').on('keypress', function(e) { if (e.which == 13 || e.keyCode == 13) { if ($(this).val().trim().length > 0) { // initiate an Ajax call to send the data fireAJAX($(this).val().trim()); } } }); function fireAJAX(text) { $.ajax({ type: 'POST', url: '/search', data: { search: text }, beforeSend: function(xhr) { $('.tweet-results').html(''); $('.results').show(); enableState(); }, success: parseData, error: oops }); } function parseData(data) { disableState(); var html = ''; for (var i = 0; i < data.length; i++) { var s = data[i].sentiment, t = data[i].tweet; var _o = { imgSrc: t.user.profile_image_url, tweetLink: 'http://twitter.com/' + t.user.screen_name + '/status/' + t.id_str, tweet: t.text, score: s.score ? s.score : '--', comparative: s.comparative ? s.comparative : '--', favorited: t.favorite_count ? t.favorite_count : 0, retweet: t.retweet_count ? t.retweet_count : 0, wordsMatched: s.words && s.words.length ? s.words : '--', positiveWords: s.positive && s.positive.length ? s.positive : '--', negativeWords: s.negative && s.negative.length ? s.negative : '--' }; html += tmpl('tweet_tmpl', _o); }; $('.tweet-results').html(html); } function oops(data) { $('.error').show(); disableState(); } function disableState() { $('.loading').hide(); $('#searchText').prop('disabled', false); } function enableState() { $('.loading').show(); $('#searchText').prop('disabled', true); } }); // Simple JavaScript Templating // John Resig - http://ejohn.org/ - MIT Licensed (function() { var cache = {}; this.tmpl = function tmpl(str, data) { // Figure out if we're getting a template, or if we need to // load the template - and be sure to cache the result. var fn = !/\W/.test(str) ? cache[str] = cache[str] || tmpl(document.getElementById(str).innerHTML) : // Generate a reusable function that will serve as a template // generator (and which will be cached). new Function("obj", "var p=[],print=function(){p.push.apply(p,arguments);};" + // Introduce the data as local variables using with(){} "with(obj){p.push('" + // Convert the template into pure JavaScript str .replace(/[\r\t\n]/g, " ") .split("{{").join("\t") // modified .replace(/((^|\}\})[^\t]*)'/g, "$1\r") // modified .replace(/\t=(.*?)}}/g, "',$1,'") // modified .split("\t").join("');") .split("}}").join("p.push('") // modified .split("\r").join("\\'") + "');}return p.join('');"); // Provide some basic currying to the user return data ? fn(data) : fn; }; })(); . $(document).ready(function() { // handle the form submit $('#searchText').on('keypress', function(e) { if (e.which == 13 || e.keyCode == 13) { if ($(this).val().trim().length > 0) { // initiate an Ajax call to send the data fireAJAX($(this).val().trim()); } } }); function fireAJAX(text) { $.ajax({ type: 'POST', url: '/search', data: { search: text }, beforeSend: function(xhr) { $('.tweet-results').html(''); $('.results').show(); enableState(); }, success: parseData, error: oops }); } function parseData(data) { disableState(); var html = ''; for (var i = 0; i < data.length; i++) { var s = data[i].sentiment, t = data[i].tweet; var _o = { imgSrc: t.user.profile_image_url, tweetLink: 'http://twitter.com/' + t.user.screen_name + '/status/' + t.id_str, tweet: t.text, score: s.score ? s.score : '--', comparative: s.comparative ? s.comparative : '--', favorited: t.favorite_count ? t.favorite_count : 0, retweet: t.retweet_count ? t.retweet_count : 0, wordsMatched: s.words && s.words.length ? s.words : '--', positiveWords: s.positive && s.positive.length ? s.positive : '--', negativeWords: s.negative && s.negative.length ? s.negative : '--' }; html += tmpl('tweet_tmpl', _o); }; $('.tweet-results').html(html); } function oops(data) { $('.error').show(); disableState(); } function disableState() { $('.loading').hide(); $('#searchText').prop('disabled', false); } function enableState() { $('.loading').show(); $('#searchText').prop('disabled', true); } }); // Simple JavaScript Templating // John Resig - http://ejohn.org/ - MIT Licensed (function() { var cache = {}; this.tmpl = function tmpl(str, data) { // Figure out if we're getting a template, or if we need to // load the template - and be sure to cache the result. var fn = !/\W/.test(str) ? cache[str] = cache[str] || tmpl(document.getElementById(str).innerHTML) : // Generate a reusable function that will serve as a template // generator (and which will be cached). new Function("obj", "var p=[],print=function(){p.push.apply(p,arguments);};" + // Introduce the data as local variables using with(){} "with(obj){p.push('" + // Convert the template into pure JavaScript str .replace(/[\r\t\n]/g, " ") .split("{{").join("\t") // modified .replace(/((^|\}\})[^\t]*)'/g, "$1\r") // modified .replace(/\t=(.*?)}}/g, "',$1,'") // modified .split("\t").join("');") .split("}}").join("p.push('") // modified .split("\r").join("\\'") + "');}return p.join('');"); // Provide some basic currying to the user return data ? fn(data) : fn; }; })(); 

Затем откройте views/index.html и добавьте следующий код.

 <!DOCTYPE html> <html> <head> <title>Sentiment Analysis App</title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <h1>Sentiment Analysis App</h1> <hr/> <input type="text" id="searchText" placeholder="Enter the text you would like to see the analysis for and hit return"> <div class="results"> <h3>Results</h3> <lable class="loading">Loading.. Please wait</lable> <br/> <lable class="error">Oops.. Something went wrong</lable> <br/> </div> <section class="tweet-results"> </section> <script src="//code.jquery.com/jquery-1.11.0.min.js"></script> <script type="text/javascript" src="scripts/app.js"></script> <script type="text/html" id="tweet_tmpl"> <article> <div class="left"> <img src="{{=imgSrc}}"> <p>{{=tweet}} <a href="{{=tweetLink}}" target="_blank">Link</a></p> </div> <div class="right"> <table> <tr> <td>Score</td> <td>{{=score}}</td> </tr> <tr> <td>Comparative</td> <td>{{=comparative}}</td> </tr> <tr> <td>Favorited</td> <td>{{=favorited}}</td> </tr> <tr> <td>Retweeted</td> <td>{{=retweet}}</td> </tr> <tr> <td>Words Matched</td> <td>{{=wordsMatched}}</td> </tr> <tr> <td>Positive Words</td> <td>{{=positiveWords}}</td> </tr> <tr> <td>Negative Words</td> <td>{{=negativeWords}}</td> </tr> </table> </div> </article> </script> </body> </html> В <!DOCTYPE html> <html> <head> <title>Sentiment Analysis App</title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <h1>Sentiment Analysis App</h1> <hr/> <input type="text" id="searchText" placeholder="Enter the text you would like to see the analysis for and hit return"> <div class="results"> <h3>Results</h3> <lable class="loading">Loading.. Please wait</lable> <br/> <lable class="error">Oops.. Something went wrong</lable> <br/> </div> <section class="tweet-results"> </section> <script src="//code.jquery.com/jquery-1.11.0.min.js"></script> <script type="text/javascript" src="scripts/app.js"></script> <script type="text/html" id="tweet_tmpl"> <article> <div class="left"> <img src="{{=imgSrc}}"> <p>{{=tweet}} <a href="{{=tweetLink}}" target="_blank">Link</a></p> </div> <div class="right"> <table> <tr> <td>Score</td> <td>{{=score}}</td> </tr> <tr> <td>Comparative</td> <td>{{=comparative}}</td> </tr> <tr> <td>Favorited</td> <td>{{=favorited}}</td> </tr> <tr> <td>Retweeted</td> <td>{{=retweet}}</td> </tr> <tr> <td>Words Matched</td> <td>{{=wordsMatched}}</td> </tr> <tr> <td>Positive Words</td> <td>{{=positiveWords}}</td> </tr> <tr> <td>Negative Words</td> <td>{{=negativeWords}}</td> </tr> </table> </div> </article> </script> </body> </html> 

Здесь мы имеем в виду jQuery и app.js Мы также создали шаблон ( tweet_tmpl ), который будет использоваться для отображения результатов. Наконец, откройте stylesheets/style.css и добавьте следующие классы.

 body { padding: 50px; font: 14px"Lucida Grande", Helvetica, Arial, sans-serif; background: #eee; } a { color: #00B7FF; } input { width: 98%; padding: 9px; font-size: 17px; } .results { display: none; } .error { color: red; display: none; } .tweet-results { width: 100%; overflow: hidden; padding-right: 18px; } .left { float: left; width: 39%; } .right { float: right; width: 55%; border-left: 1px dashed; padding-left: 21px; } article { background: #fff; display: block; padding: 18px; border: 1px solid #eee; margin-top: 21px; margin-bottom: 21px; overflow: hidden; box-shadow: 6px 4px 9px 1px rgba(119, 119, 119, 0.75); -moz-box-shadow: 6px 4px 9px 1px rgba(119, 119, 119, 0.75); -webkit-box-shadow: 6px 4px 9px 1px rgba(119, 119, 119, 0.75); } article img { width: 64px; float: left; margin:0 5px 0 0; } .right table { width: 100%; } .right table, .right table td { border: 1px solid; } .right table td { width: 50%; } 

Вот и все, мы закончили с нашим развитием. Давайте запустим приложение и протестируем его. Вернувшись в терминал, запустите команду gulp чтобы запустить сервер. Перейдите по http://localhost:3000/ и вы должны увидеть панель поиска. Введите «Это потрясающе» и нажмите «Return», и вы должны увидеть что-то вроде этого:

Положительные результаты

Здесь Score — это сумма баллов для каждого слова, присутствующего в твите, соответствующего источнику AFINN . Comparative равен score/total words . Words Matched показывают, сколько слов из твита соответствует словам AFINN во время обработки. Positive Words — это совпадающие позитивные слова, а Negative Words — это совпадающие негативные слова. Эти данные должны дать вам достаточно информации, чтобы принять решение и понять настроение.

Очистите панель поиска и введите sad broken и нажмите возврат. Ваши результаты должны выглядеть примерно так:

Отрицательные результаты

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

Тренировка ваших данных

Вы, должно быть, уже заметили, что не все твиты возвращают результаты. Это потому, что если ни одно из слов в твите не соответствует словам AFINN, оценка будет равна 0. Например:

Перед тренировкой

Если вы хотите это исправить, вы можете обучить модуль настроений. Создайте новый файл в папке logic именем training.js и добавьте следующий код.

 module.exports = { directives: 4, angular: 5, code: 3, scope: 3 }; 

Здесь мы обучаем модуль использовать вышеупомянутые оценки для упомянутых слов. Затем обновите logic/sentimentAnalysis.js следующим образом:

 var sentiment = require('sentiment'); var trainedData = require('./training.js'); module.exports = function(text) { return sentiment(text, trainedData); } 

После тренировки результаты будут выглядеть так:

После тренировки

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

Создание сервиса RESTful

Вы можете создать панель мониторинга в реальном времени, которая будет показывать твиты и результаты. Вы можете запустить асинхронное задание, которое будет время от времени обращаться к API Twitter, извлекать данные и сохранять их с помощью diskDB. Затем вы можете предоставить этот файл в качестве конечной точки RESTful. Добавьте следующий код в routes/index.js .

 router.get('/data', function(req, res) { res.json(require('diskdb') .connect('db', ['sentiments']) .sentiments.find()); }); 

Теперь, когда вы http://localhost:3000/data к http://localhost:3000/data вы можете видеть полные сохраненные данные. Живой пример доступен здесь .

Интегрировать социальные медиа

Подобно Twitter, вы можете интегрировать Facebook , Google+ и другие данные социальных сетей. Все, что вам нужно сделать, — это передать текст, который вы хотите проанализировать, в logic/sentimentAnalysis.js , и вы сможете увидеть его оценку.

Вывод

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