... | ... | @@ -255,3 +255,252 @@ Como ya hemos comentado, el programa leerá un fichero con los usuarios de los q |
|
|
@Barclays
|
|
|
@RBSGroup
|
|
|
@jpmorgan
|
|
|
|
|
|
Volvemos al código de *TwitterCrawler.java* y en la parte de hacer la llamada a la función de TratarFicheros tenemos unas XXXXX. Lo que quiere decir es que hay que indicar el directorio de donde se va a leer el fichero con las cuentas. El inicio de la ruta va a ser diferente y si se quisiese cambiar el path completo se debería alterar toda la línea.
|
|
|
|
|
|
Después, viene la parte de la autenticación. Para poder usar este programa, se necesita que el usuario tenga una cuenta de Twitter y que introduzca sus credenciales. Una vez verificadas que las credenciales son correctas, se habilitan las llamadas asíncronas y se crea un objeto de la clase *Tweet* que será el que se encargue de descargar los tweets. El contenido de la clase es el siguiente:
|
|
|
|
|
|
package tweets_cd;
|
|
|
|
|
|
import java.awt.print.PrinterAbortException;
|
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
import java.io.File;
|
|
|
import java.io.FileNotFoundException;
|
|
|
import java.io.FileOutputStream;
|
|
|
import java.io.IOException;
|
|
|
import java.io.InputStreamReader;
|
|
|
import java.io.PrintWriter;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.List;
|
|
|
|
|
|
import twitter4j.*;
|
|
|
import twitter4j.auth.AccessToken;
|
|
|
import twitter4j.auth.RequestToken;
|
|
|
import twitter4j.conf.ConfigurationBuilder;
|
|
|
|
|
|
import java.io.FileReader;
|
|
|
import java.io.FileWriter;
|
|
|
import java.io.File;
|
|
|
|
|
|
import org.mongodb.morphia.Datastore;
|
|
|
|
|
|
import org.mongodb.morphia.Morphia;
|
|
|
|
|
|
import com.mongodb.Mongo;
|
|
|
|
|
|
import com.mongodb.MongoClient;
|
|
|
|
|
|
public class Tweet {
|
|
|
|
|
|
private Datastore dataStore;
|
|
|
|
|
|
public Tweet() throws Exception {
|
|
|
}
|
|
|
|
|
|
static void get_tweets(String[] ente) throws TwitterException, IOException {
|
|
|
System.out.println("Downloading tweets...");
|
|
|
Twitter twitter = TwitterFactory.getSingleton();
|
|
|
|
|
|
for (int i = 0; i < ente.length; i++) {
|
|
|
if (ente[i] != null)
|
|
|
more_than_100(twitter, ente[i]);
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
static void more_than_100(Twitter twitter, String ente) throws IOException {
|
|
|
|
|
|
MongoClient mongo = new MongoClient("localhost", 27017);
|
|
|
|
|
|
Morphia morphia = new Morphia();
|
|
|
|
|
|
morphia.map(ContenedorTweet.class);
|
|
|
|
|
|
Datastore ds = morphia.createDatastore(mongo, "Tweets");
|
|
|
|
|
|
Query query = new Query(ente);
|
|
|
int numberOfTweets = 3200;
|
|
|
long lastID = Long.MAX_VALUE;
|
|
|
System.out.println("-------------------");
|
|
|
ArrayList<Status> tweets = new ArrayList<Status>();
|
|
|
|
|
|
ArrayList<Status> tweets_list = new ArrayList<Status>();
|
|
|
int j = 1;
|
|
|
Paging page;
|
|
|
|
|
|
while (tweets.size() < numberOfTweets) {
|
|
|
|
|
|
if (numberOfTweets - tweets.size() > 100)
|
|
|
query.setCount(200);
|
|
|
else
|
|
|
query.setCount(numberOfTweets - tweets.size());
|
|
|
try {
|
|
|
QueryResult result = twitter.search(query);
|
|
|
|
|
|
page = new Paging(j, 200);
|
|
|
j++;
|
|
|
tweets = (ArrayList<Status>) twitter
|
|
|
.getUserTimeline(ente, page);
|
|
|
tweets_list.addAll(tweets);
|
|
|
|
|
|
System.out.println("Gathered " + tweets.size() + " tweets");
|
|
|
|
|
|
if (tweets.size() < 200) {
|
|
|
//System.out.println("break");
|
|
|
break;
|
|
|
}
|
|
|
for (Status t : tweets) {
|
|
|
// System.out.println(lastID);
|
|
|
if (t.getId() < lastID)
|
|
|
lastID = t.getId();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
catch (TwitterException te) {
|
|
|
System.out.println("Couldn't connect: " + te);
|
|
|
}
|
|
|
;
|
|
|
query.setMaxId(lastID - 1);
|
|
|
}
|
|
|
|
|
|
for (int i = 0; i < tweets_list.size(); i++) {
|
|
|
Status t = (Status) tweets_list.get(i);
|
|
|
|
|
|
GeoLocation loc = t.getGeoLocation();
|
|
|
|
|
|
String user = t.getUser().getScreenName();
|
|
|
String msg = t.getText();
|
|
|
ContenedorTweet nuevo = new ContenedorTweet(user, msg);
|
|
|
|
|
|
ds.save(nuevo);
|
|
|
|
|
|
ds.save(nuevo);
|
|
|
|
|
|
String time = "";
|
|
|
if (loc != null) {
|
|
|
Double lat = t.getGeoLocation().getLatitude();
|
|
|
Double lon = t.getGeoLocation().getLongitude();
|
|
|
System.out.println(i + " USER: " + user + " wrote: " + msg
|
|
|
+ " located at " + lat + ", " + lon);
|
|
|
} else
|
|
|
System.out.println(i + " USER: " + user + " wrote: " + msg);
|
|
|
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
Esta clase será la que descargue los Tweets y además los meta dentro de la base de datos. Por ello en la declaración de importaciones se incluyen las liberías de Morphia (liberías para mapear objetos Java a MongoDB).
|
|
|
|
|
|
Existe un método principal que se llama get\_tweets, que va recorriendo cada una de las cuentas en el String que obtuvimos anteriormente y realiza las peticiones de los tweets al método more\_than\_100.
|
|
|
|
|
|
El método more\_than\_100 hará peticiones de 200 tweets que es el límite que permite el user timeline hasta que consiga los 3200 tweets permitidos por cada cuenta. Cuando los obtenga los guardará en la base de datos.
|
|
|
|
|
|
En este punto es importante comentar las limitaciones de Twitter. En la idea original de la implementación de esta parte se pretendía usar en lugar del GET user\_timeline el GET search/tweets que nos permite descargar más de 3200 tweets de un usuario. Entre las limitaciones que tenía se encontraba la de que solo se podían descargar 100 tweets por página; de ahí que llamáramos a la función more\_than\_100 porque con el uso del control de páginas de Twitter4J, podíamos descargar más de 100. Sin embargo, existe otra limitación que solo permite descargar los tweets de la última semana. Esta limitación hizo que pensásemos cambiar a la opción del user\_timeline que permite descargar hasta 3200 tweets pero sea de la fecha que sea y como escribir 3200 tweets en una semana parece mucho, consideramos que con esta opción podemos obtener mayor información.
|
|
|
|
|
|
Sin embargo, aun cambiado de opción no podemos evitar las limitaciones. Ahora el número de tweets que pueden descargarse en una petición es 200, pero existe un límite de 180 peticiones en una ventana de 15 minutos, por lo que en esa ventana de tiempo podríamos descargar unos 36000 tweets. En la práctica, como según va pasando el tiempo se mueve la ventana de los últimos 15 minutos hasta llegar al máximo, se ha podido llegar a descargar unos 38000 tweets hasta llegar al máximo. Cuando esto ocurre en el programa, simplemente hay que esperar y cuando la ventana de los últimos 15 minutos se resetee, continuará la descarga. El mensaje que se obtiene en estos casos es:
|
|
|
|
|
|
TwitterException{exceptionCode=[4be80492-09de3b02], statusCode=429, message=Rate limit exceeded, code=88, retryAfter=-1, rateLimitStatus=RateLimitStatusJSONImpl{remaining=0, limit=180, resetTimeInSeconds=1419968236, secondsUntilReset=523}, version=4.0.2}
|
|
|
Couldn't connect: 429:Returned in API v1.1 when a request cannot be served due to the application's rate limit having been exhausted for the resource. See Rate Limiting in API v1.1.(https://dev.twitter.com/docs/rate-limiting/1.1)
|
|
|
message - Rate limit exceeded
|
|
|
code - 88
|
|
|
|
|
|
Relevant discussions can be found on the Internet at:
|
|
|
http://www.google.co.jp/search?q=506c3b98 or
|
|
|
http://www.google.co.jp/search?q=105d1078
|
|
|
|
|
|
Una vez que se descargan los tweets se guardan en la base de datos MongoDB, pero siguiendo la estructura del objeto ContentedorTweet. Podemos ver el contenido en *ContenedorTweet:java*.
|
|
|
|
|
|
package tweets_cd;
|
|
|
|
|
|
import org.bson.types.ObjectId;
|
|
|
import org.mongodb.morphia.annotations.Entity;
|
|
|
import org.mongodb.morphia.annotations.Id;
|
|
|
|
|
|
@Entity("ContenedorTweet")
|
|
|
public class ContenedorTweet {
|
|
|
|
|
|
@Id ObjectId id_mongo;
|
|
|
private String user;
|
|
|
private String msg;
|
|
|
|
|
|
public ContenedorTweet(String user, String msg) {
|
|
|
this.user = user;
|
|
|
this.msg = msg;
|
|
|
}
|
|
|
|
|
|
public ObjectId getId_mongo() {
|
|
|
return id_mongo;
|
|
|
}
|
|
|
|
|
|
public void setId_mongo(ObjectId id_mongo) {
|
|
|
this.id_mongo = id_mongo;
|
|
|
}
|
|
|
|
|
|
public String getUser() {
|
|
|
return user;
|
|
|
}
|
|
|
|
|
|
public void setUser(String user) {
|
|
|
this.user = user;
|
|
|
}
|
|
|
|
|
|
public String getMsg() {
|
|
|
return msg;
|
|
|
}
|
|
|
|
|
|
public void setMsg(String msg) {
|
|
|
this.msg = msg;
|
|
|
}
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
Este clase únicamente tiene tres atributos, de los cuales los más relevantes para el resto del programa son el usuario y el mensaje (tweet). Los únicos métodos que dispone la clase son los gets y los sets correspondientes a cada uno de los atributos.
|
|
|
|
|
|
Con todo lo expuesto hasta el momento, podríamos arrancar el servidor de MongoDB y ejecutar este código y guardaríamos todos los tweets en la base de datos.
|
|
|
|
|
|
Para arrancar MongoDB, si se quiere realizar manualmente, tenemos el comando:
|
|
|
> /home/teleco/cdtools/mongodb/bin/mongod --dbpath data
|
|
|
|
|
|
Donde se cambiaría la primera parte de la ruta por la correspondiente y se indicaría donde se encuentra *mongod* y luego con --dbpath se indica el lugar donde se van a guardar los datos.
|
|
|
|
|
|
Si falla el anterior comando porque se halla cerrado bruscamente la base de datos anteriormente, por ejemplo, se puede reparar con este comando:
|
|
|
|
|
|
> /home/teleco/cdtools/mongodb/bin/mongod --repair --dbpath data
|
|
|
|
|
|
Para detener la base de datos de forma limpia, NO debemos utilizar CTRL-C, sino el siguiente comando:
|
|
|
> /home/teleco/cdtools/mongodb/bin/mongod --shutdown --dbpath data
|
|
|
|
|
|
Por último, todos los tweets que se encuentran en la base de datos los exportaremos a un fichero con extensión .csv mediante *mongoexport*. El comando que permite hacer esta operación es:
|
|
|
|
|
|
> mongoexport --db Tweets --collection ContenedorTweet --csv -f "user","msg" -out /home/teleco/compdist_business_analysis_tweets/datos.csv
|
|
|
|
|
|
Indicamos el tipo de objeto de la colección, que en este caso es ContenedorTweet, los campos que queremos en la salida (solo nos interesa el usuario y el mensaje) y la ruta de salida es la que le indiquemos.
|
|
|
|
|
|
Para automatizar todas las operaciones descritas en este bloque, dentro de la carpeta de scripts, existe uno llamado "tweets.sh", que al ejecutarlo se encarga automáticamente de arrancar la base de datos, ejecutar el código Java, exportar el .csv y finalmente parar la base de datos de forma limpia. El contenido del mismo es:
|
|
|
|
|
|
#!/bin/bash
|
|
|
|
|
|
# -*- ENCODING: UTF-8 -*-
|
|
|
clear
|
|
|
cd
|
|
|
cd compdist_business_analysis_tweets
|
|
|
rm -R data
|
|
|
mkdir data
|
|
|
/home/teleco/cdtools/mongodb/bin/mongod --repair --dbpath data
|
|
|
/home/teleco/cdtools/mongodb/bin/mongod --dbpath data &
|
|
|
cd tweets
|
|
|
cd src
|
|
|
javac -d ../bin -cp ../javax.json-api-1.0.jar:../mongo-java-driver-2.12.4.jar:../morphia-0.108.jar:../twitter4j-4.0.2/lib/twitter4j-core-4.0.2.jar:../twitter4j-4.0.2/lib/twitter4j-async-4.0.2.jar tweets_cd/*.java
|
|
|
cd ../bin
|
|
|
java -cp .:../javax.json-api-1.0.jar:../mongo-java-driver-2.12.4.jar:../morphia-0.108.jar:../twitter4j-4.0.2/lib/twitter4j-core-4.0.2.jar:../twitter4j-4.0.2/lib/twitter4j-async-4.0.2.jar tweets_cd/TwitterCrawler
|
|
|
|
|
|
mongoexport --db Tweets --collection ContenedorTweet --csv -f "user","msg" -out /home/teleco/compdist_business_analysis_tweets/datos.csv
|
|
|
cd
|
|
|
cd compdist_business_analysis_tweets
|
|
|
/home/teleco/cdtools/mongodb/bin/mongod --shutdown --dbpath data
|
|
|
|
|
|
exit
|
|
|
|
|
|
De aquí simplemente hacemos notar que para que fucione es necesario cambiar el /home/teleco por el directorio raíz correspondiente y seguir la estructura de ficheros que hemos comentado. |
|
|
\ No newline at end of file |