Hola de nuevo chicos, este es un post breve pero que quería compartir con ustedes desde hace mucho tiempo, fue hace un año aproximadamente que en la materia de análisis de redes sociales realicé un mini proyecto, este consistió en analizar la actividad de mi red social de twitter, y además tener una representación gráfica de la actividad de mi cuenta, es decir, a quienes sigo y las menciones y hashtags utilizados por mí y por aquellos a quienes sigo, incluso el número de veces que se ha utilizado cada hashtag.
Lo único que he hecho antes de subir el código a github fue actualizar la versión neo4j a 2.3.6 (base de datos de grafo donde se guardan las relaciones) y eliminar mis datos para el uso de la API de twitter.
Antes de ejecutar esta aplicación será necesario que generen un token y sigan los pasos necesarios para poder utilizar la API de twitter, por otro lado es importante destacar que este programa almacena las distintas relaciones entre entidades en una base de datos embebida de neo4j y para poder visualizar el resultado final de todas esas relaciones guardadas, lo que hice fue simplemente utilizar el navegador/visualizador por defecto que trae neo4j (que si no me equivoco esta creado con d3.js). Así que manos a la obra y comencemos descargando e instalando la versión 2.3.6 de Neo4j desde el siguiente enlace y una vez realizada la instalación pasamos al código fuente.
A continuación la clase principal:
1 2 3 4 5 6 7 8 9 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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
package com.josedeveloper.twitter; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.json.Json; import javax.json.JsonArray; import javax.json.JsonObject; import javax.json.JsonReader; import javax.json.JsonValue; import oauth.signpost.OAuthConsumer; import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer; import oauth.signpost.exception.OAuthCommunicationException; import oauth.signpost.exception.OAuthExpectationFailedException; import oauth.signpost.exception.OAuthMessageSignerException; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.HttpClientBuilder; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Label; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.RelationshipType; import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.factory.GraphDatabaseFactory; public class TwitterRelationshipApp { static final String TWITTER_DB_PATH = "DATABASE_PATH"; static final String CONSUMER_KEY = "YOUR_CONSUMER_KEY"; static final String CONSUMER_SECRET = "YOUR_CONSUMER_SECRET"; static final String ACCESS_TOKEN = "YOUR_ACCESS_TOKEN"; static final String ACCESS_TOKEN_SECRET = "YOUR_ACCESS_TOKEN_SECRET"; private final GraphDatabaseService graphDB; private final Set totalUsers; private final String account; private final int count; enum NodeType implements Label { TWITTER_USER, HASHTAG; } enum Relationships implements RelationshipType { USE, MENTION; } public TwitterRelationshipApp(final String account, final int count) { this.account = account; this.count = count; totalUsers = new HashSet<>(); graphDB = new GraphDatabaseFactory().newEmbeddedDatabase(new File(TWITTER_DB_PATH)); } public static void main(String[] args) throws OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException, ClientProtocolException, IOException { TwitterRelationshipApp app = new TwitterRelationshipApp("josedeveloper", 100); app.registerShutdownHook(); app.insertUsers(); app.insertUserMentionsRelationshipsByUser(); } private void insertUsers() throws OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException, ClientProtocolException, IOException { OAuthConsumer oAuthConsumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET); oAuthConsumer.setTokenWithSecret(ACCESS_TOKEN, ACCESS_TOKEN_SECRET); HttpGet httpGet = new HttpGet("https://api.twitter.com/1.1/friends/list.json?screen_name=" + account + "&count=" + count); //those who I follow oAuthConsumer.sign(httpGet); HttpClient httpClient = HttpClientBuilder.create().build(); HttpResponse httpResponse = httpClient.execute(httpGet); //int statusCode = httpResponse.getStatusLine().getStatusCode(); JsonReader reader = Json.createReader(httpResponse.getEntity().getContent()); JsonObject root = reader.readObject(); JsonArray users = root.getJsonArray("users"); Iterator iter = users.iterator(); while (iter.hasNext()) { JsonObject user = (JsonObject) iter.next(); try (Transaction tx = graphDB.beginTx()) { Node userNode = graphDB.createNode(NodeType.TWITTER_USER); userNode.setProperty("id", user.getString("id_str")); userNode.setProperty("name", user.getString("name")); userNode.setProperty("screen_name", user.getString("screen_name")); insertRelationshipsWithHashtagsByUser(userNode, graphDB); tx.success(); } catch (Exception e) { System.out.println(e); } totalUsers.add(user.getString("screen_name")); } } private void registerShutdownHook() { // Registers a shutdown hook for the Neo4j instance so that it // shuts down nicely when the VM exits (even if you "Ctrl-C" the // running application). Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { graphDB.shutdown(); } }); } private static void insertRelationshipsWithHashtagsByUser(Node user, final GraphDatabaseService db) throws OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException, ClientProtocolException, IOException { OAuthConsumer oAuthConsumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET); oAuthConsumer.setTokenWithSecret(ACCESS_TOKEN, ACCESS_TOKEN_SECRET); HttpGet httpGet = new HttpGet("https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=" + user.getProperty("screen_name")); oAuthConsumer.sign(httpGet); HttpClient httpClient = HttpClientBuilder.create().build(); HttpResponse httpResponse = httpClient.execute(httpGet); //int statusCode = httpResponse.getStatusLine().getStatusCode(); JsonReader timelineReader = Json.createReader(httpResponse.getEntity().getContent()); JsonArray tweets = timelineReader.readArray(); Iterator tweetsIter = tweets.iterator(); Map<String, Integer> usedHashtags = new HashMap<>(); while(tweetsIter.hasNext()) { JsonObject tweet = (JsonObject) tweetsIter.next(); JsonObject entities = tweet.getJsonObject("entities"); JsonArray hashtags = entities.getJsonArray("hashtags"); Iterator hashtagsIter = hashtags.iterator(); while (hashtagsIter.hasNext()) { String hashtag = ((JsonObject) hashtagsIter.next()).getString("text"); if (usedHashtags.containsKey(hashtag)) { Integer counter = usedHashtags.get(hashtag); usedHashtags.put(hashtag, ++counter); } else{ usedHashtags.put(hashtag, Integer.valueOf(1)); } } } for (String hashtag : usedHashtags.keySet()) { try (Transaction tx = db.beginTx()) { Node hashtagNode = db.findNode(NodeType.HASHTAG, "text", hashtag); if (hashtagNode == null) hashtagNode = db.createNode(NodeType.HASHTAG); hashtagNode.setProperty("text", hashtag); Integer timesUsed = usedHashtags.get(hashtag); Relationship use = user.createRelationshipTo(hashtagNode, Relationships.USE); use.setProperty("times", timesUsed); tx.success(); } catch (Exception e) { System.out.println(e); } } } private void insertUserMentionsRelationshipsByUser() throws OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException, ClientProtocolException, IOException { OAuthConsumer oAuthConsumer = new CommonsHttpOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET); oAuthConsumer.setTokenWithSecret(ACCESS_TOKEN, ACCESS_TOKEN_SECRET); for (String twitterUser : totalUsers) { HttpGet httpGet = new HttpGet("https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=" + twitterUser); oAuthConsumer.sign(httpGet); HttpClient httpClient = HttpClientBuilder.create().build(); HttpResponse httpResponse = httpClient.execute(httpGet); //int statusCode = httpResponse.getStatusLine().getStatusCode(); JsonReader timelineReader = Json.createReader(httpResponse.getEntity().getContent()); JsonArray tweets = timelineReader.readArray(); Iterator tweetsIter = tweets.iterator(); Map<String, Integer> userMentionsDone = new HashMap<>(); while(tweetsIter.hasNext()) { JsonObject tweet = (JsonObject) tweetsIter.next(); JsonObject entities = tweet.getJsonObject("entities"); JsonArray userMentions = entities.getJsonArray("user_mentions"); Iterator hashtagsIter = userMentions.iterator(); while (hashtagsIter.hasNext()) { String userMentioned = ((JsonObject) hashtagsIter.next()).getString("screen_name"); if (totalUsers.contains(userMentioned)) { if (userMentionsDone.containsKey(userMentioned)) { Integer counter = userMentionsDone.get(userMentioned); userMentionsDone.put(userMentioned, ++counter); } else{ userMentionsDone.put(userMentioned, Integer.valueOf(1)); } } } } for (String userMentionDone : userMentionsDone.keySet()) { try (Transaction tx = graphDB.beginTx()) { Node twitterUserMentionedNode = graphDB.findNode(NodeType.TWITTER_USER, "screen_name", userMentionDone); Node twitterUserNode = graphDB.findNode(NodeType.TWITTER_USER, "screen_name", twitterUser); Integer timesMentioned = userMentionsDone.get(userMentionDone); Relationship use = twitterUserNode.createRelationshipTo(twitterUserMentionedNode, Relationships.MENTION); use.setProperty("times", timesMentioned); tx.success(); } catch (Exception e) { System.out.println(e); } } } } } |
El código completo del proyecto pueden hallarlo en el siguiente enlace. Una vez hayamos ejecutado nuestra aplicación, en la ruta especificada TWITTER_DB_PATH encontraremos una carpeta con extensión .db donde estarán almacenadas las relaciones (todo el grafo), el siguiente paso para poder visualizar el grafo será editar el fichero RUTA_INSTALACION_NEO4J/conf/neo4j-server.properties y editar la ruta donde ha de estar ubicada la base de datos
org.neo4j.server.database.location=TWITTER_DB_PATH.db
TWITTER_DB_PATH= La ruta especificada donde se ha de crear la base de datos Neo4j donde se almacenarán las relaciones.
Ahora procedemos a arrancar la base de datos, que es bastante sencillo solo es necesario ejecutar el siguiente comando:
RUTA_INSTALACION_NEO4J/bin/neo4j start
Hecho esto desde un navegador (chrome o firefox por ejemplo) ir a la ruta http://localhost:7474 y veremos el cliente web de Neo4j
Luego desde la consola donde ejecutar las consultas (donde aparece el símbolo del $) ejecutar el siguiente comando para poder visualizar todo el grafo.
MATCH (n) RETURN n
En mi caso obtuve lo siguiente
Como podrán darse cuenta se pueden distinguir los distintos tipos de relaciones (USE y MENTION) entre los distintos nodos, además hay 2 tipos de nodos, los azules son las cuentas de twitter y los verdes son los hashtags.
Otra cosa interesante es que con el visualizador de Neo4j podemos ver los datos de las relaciones como por ejemplo el número de veces que una cuenta de twitter ha usado un hashtag o mencionado a otra cuenta como en la siguiente gráfica
Como se puede apreciar en la parte inferior de la gráfica, la cuenta Ben & Martijn ha utilizado 2 veces el hashtag #Java.
Bueno ya no me queda más nada que mostrar a este respecto, así que si te resulta interesante ejecuta este ejemplo y ve como es la actividad de tu cuenta de twitter y te aseguro que encontraras cosas que te llamarán la atención y si te parece compártelas con el resto.
Por último mencionarles que intentare en medida de lo posible actualizar este código para que trabaje con la versión 3 o superior de Neo4j, mejorar el código (hacerlo más claro) y actualizarlo a Java 8. Cualquier comentario y/o sugerencia soy todo oídos.