README.md 27.4 KB
Newer Older
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
1
Este proyecto NO se puede clonar.
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# Acceso a interfaces REST. Descarga masiva de Tweets

### Aplicaciones y librerías a utilizar

* Eclipse (podéis usar el que descargásteis el primer día)
* [Unirest](http://unirest.io/java.html): librería de peticiones HTTP (más adelante veremos como descargarla)
* [Commons](https://commons.apache.org/): librerías de funciones auxiliares (más adelante veremos como descargarla)
* [Google-Gson](https://github.com/google/gson): librería de manipulación de JSON (más adelante veremos como descargarla)

Será necesaria una cuenta de twitter

## Crear una aplicación en Twitter

Si no tenías twitter, ha llegado el momento de tenerlo, hazte una cuenta. Si ya tienes una, haz login en twitter.

Tuvieras o no, consulta #uc3m_cd que es la cuenta de la asignatura aunque se usa poco ;)

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
20
Vamos a descargar tweets. Para ello, debes dar de alta una aplicación. Ve a la [gestión de apps de twitter](https://developer.twitter.com/) y crea una aplicación si no lo has hecho ya.
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
21

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
22
El el dashboard verás tus proyectos y tus aplicaciones. Pulsando en la aplicación que has creado, podrás ver los tokens de seguridad (simbolo de la llave) debes copiar y pegar todos ellos (consumer keys, bearer token, authentication tokens) en algún lugar para usarlos después.
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
23

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
24
<img src="https://gitlab.pervasive.it.uc3m.es/distributed-computing-assignements/2-restAPIs-unirest-twitter/raw/master/img/keys_tokens.png" width="500px"/>
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
25
26
27
28
29
30
31
32
33
34

## Descarga de tweets con la ayuda de desarrollador de Twitter

Ahora vamos a usar un API de verdad. Está documentado correctamente, de hecho, el de twitter, está muy bien comentado y tiene utilidades que veremos. Ve a la página:

https://dev.twitter.com/rest/public

Observa como todo lo que se puede hacer con twitter tiene su correspondiente entrada en el API.
Vamos a echar un vistazo a la función para obtener tweets que es `GET statuses/user_timeline`

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
35
Para ello, ve a la página https://developer.twitter.com/en/docs/twitter-api/v1/tweets/timelines/api-reference/get-statuses-user_timeline  (nosotros usaremos el API v1.1.)
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
36
37
38

Twitter cuida mucho su interfaz, y ayuda a conservarlo y entenderlo. Pero dado su éxito, ha prohibido el acceso anónimo. Por lo que es necesario usar OAuth (usando los tokens que hemos generado) para acceder a cada función del API rest. Para usar OAuth, es necesario calcular una firma y añadirla a la cabecera Authorization de HTTP.

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
39
Con la información que proporciona, puedes ver cómo, mediante peticiones REST, puedes acceder a los contenidos de la siguiente manera:
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
40
41
42
43
44
```
GET 1.1/statuses/user_timeline.json?count=2&screen_name=twitterapi HTTP/1.1
Host: api.twitter.com
Authorization: ...
```
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
45
Esto nos daría un JSON de este tipo (en breve lo haremos nosotros):
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
46
47
48
49
50

```json
[{"retweeted_status":{"contributors":null,"text":"We have disabled SSLv3 protocol support in response to the vulnerability published today. You may need to update your browser to use Twitter","geo":null,"retweeted":false,"in_reply_to_screen_name":null,"truncated":false,"lang":"en","entities":{"symbols":[],"urls":[],"hashtags":[],"user_mentions":[]},"in_reply_to_status_id_str":null,"id":522190947782643712,"source":"<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client<\/a>","in_reply_to_user_id_str":null,"favorited":false,"in_reply_to_status_id":null,"retweet_count":695,"created_at":"Wed Oct 15 01:03:18 +0000 2014","in_reply_to_user_id":null,"favorite_count":226,"id_str":"522190947782643712","place":null,"user":{"location":"Twitter HQ","default_profile":false,"profile_background_tile":true,"statuses_count":40,"lang":"en","profile_link_color":"009999","id":1137751093,"following":false,"protected":false,"favourites_count":0,"profile_text_color":"333333","description":"The Product Security Team at Twitter.","verified":true,"contributors_enabled":false,"profile_sidebar_border_color":"EEEEEE","name":"Twitter Security","profile_background_color":"131516","created_at":"Thu Jan 31 19:37:42 +0000 2013","is_translation_enabled":false,"default_profile_image":false,"followers_count":13852,"profile_image_url_https":"https://pbs.twimg.com/profile_images/3321149170/6bf0ef1ae272b203acfdee4d7e61df49_normal.png","geo_enabled":false,"profile_background_image_url":"http://abs.twimg.com/images/themes/theme14/bg.gif","profile_background_image_url_https":"https://abs.twimg.com/images/themes/theme14/bg.gif","follow_request_sent":false,"entities":{"description":{"urls":[]},"url":{"urls":[{"expanded_url":"https://twitter.com/about/security","indices":[0,23],"display_url":"twitter.com/about/security","url":"https://t.co/E9FCUUoXKF"}]}},"url":"https://t.co/E9FCUUoXKF","utc_offset":-25200,"time_zone":"Pacific Time (US & Canada)","notifications":false,"profile_use_background_image":true,"friends_count":0,"profile_sidebar_fill_color":"EFEFEF","screen_name":"twittersecurity","id_str":"1137751093","profile_image_url":"http://pbs.twimg.com/profile_images/3321149170/6bf0ef1ae272b203acfdee4d7e61df49_normal.png","listed_count":188,"is_translator":false},"coordinates":null},"contributors":null,"text":"RT @twittersecurity: We have disabled SSLv3 protocol support in response to the vulnerability published today. You may need to update your \u2026","geo":null,"retweeted":false,"in_reply_to_screen_name":null,"truncated":false,"lang":"en","entities":{"symbols":[],"urls":[],"hashtags":[],"user_mentions":[{"id":1137751093,"name":"Twitter Security","indices":[3,19],"screen_name":"twittersecurity","id_str":"1137751093"}]},"in_reply_to_status_id_str":null,"id":522339641815748608,"source":"<a href=\"http://itunes.apple.com/us/app/twitter/id409789998?mt=12\" rel=\"nofollow\">Twitter for Mac<\/a>","in_reply_to_user_id_str":null,"favorited":false,"in_reply_to_status_id":null,"retweet_count":695,"created_at":"Wed Oct 15 10:54:09 +0000 2014","in_reply_to_user_id":null,"favorite_count":0,"id_str":"522339641815748608","place":null,"user":{"location":"San Francisco, CA","default_profile":false,"profile_background_tile":true,"statuses_count":3520,"lang":"en","profile_link_color":"0084B4","profile_banner_url":"https://pbs.twimg.com/profile_banners/6253282/1347394302","id":6253282,"following":false,"protected":false,"favourites_count":26,"profile_text_color":"333333","description":"The Real Twitter API. I tweet about API changes, service issues and happily answer questions about Twitter and our API. Don't get an answer? It's on my website.","verified":true,"contributors_enabled":false,"profile_sidebar_border_color":"C0DEED","name":"Twitter API","profile_background_color":"C0DEED","created_at":"Wed May 23 06:01:13 +0000 2007","is_translation_enabled":false,"default_profile_image":false,"followers_count":2418318,"profile_image_url_https":"https://pbs.twimg.com/profile_images/2284174872/7df3h38zabcvjylnyfe3_normal.png","geo_enabled":true,"profile_background_image_url":"http://pbs.twimg.com/profile_background_images/656927849/miyt9dpjz77sc0w3d4vj.png","profile_background_image_url_https":"https://pbs.twimg.com/profile_background_images/656927849/miyt9dpjz77sc0w3d4vj.png","follow_request_sent":false,"entities":{"description":{"urls":[]},"url":{"urls":[{"expanded_url":"http://dev.twitter.com","indices":[0,22],"display_url":"dev.twitter.com","url":"http://t.co/78pYTvWfJd"}]}},"url":"http://t.co/78pYTvWfJd","utc_offset":-25200,"time_zone":"Pacific Time (US & Canada)","notifications":false,"profile_use_background_image":true,"friends_count":48,"profile_sidebar_fill_color":"DDEEF6","screen_name":"twitterapi","id_str":"6253282","profile_image_url":"http://pbs.twimg.com/profile_images/2284174872/7df3h38zabcvjylnyfe3_normal.png","listed_count":12846,"is_translator":false},"coordinates":null},{"contributors":null,"text":"The OAuth / xAuth issue affecting REST endpoints has been resolved. Please visit our OAuth forums for support https://t.co/XiKCrlfCNX","geo":null,"retweeted":false,"in_reply_to_screen_name":"twitterapi","possibly_sensitive":false,"truncated":false,"lang":"en","entities":{"symbols":[],"urls":[{"expanded_url":"https://twittercommunity.com/category/oauth","indices":[110,133],"display_url":"twittercommunity.com/category/oauth","url":"https://t.co/XiKCrlfCNX"}],"hashtags":[],"user_mentions":[]},"in_reply_to_status_id_str":"515255192787230720","id":515288036242763776,"source":"<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client<\/a>","in_reply_to_user_id_str":"6253282","favorited":false,"in_reply_to_status_id":515255192787230720,"retweet_count":79,"created_at":"Thu Sep 25 23:53:36 +0000 2014","in_reply_to_user_id":6253282,"favorite_count":55,"id_str":"515288036242763776","place":null,"user":{"location":"San Francisco, CA","default_profile":false,"profile_background_tile":true,"statuses_count":3520,"lang":"en","profile_link_color":"0084B4","profile_banner_url":"https://pbs.twimg.com/profile_banners/6253282/1347394302","id":6253282,"following":false,"protected":false,"favourites_count":26,"profile_text_color":"333333","description":"The Real Twitter API. I tweet about API changes, service issues and happily answer questions about Twitter and our API. Don't get an answer? It's on my website.","verified":true,"contributors_enabled":false,"profile_sidebar_border_color":"C0DEED","name":"Twitter API","profile_background_color":"C0DEED","created_at":"Wed May 23 06:01:13 +0000 2007","is_translation_enabled":false,"default_profile_image":false,"followers_count":2418318,"profile_image_url_https":"https://pbs.twimg.com/profile_images/2284174872/7df3h38zabcvjylnyfe3_normal.png","geo_enabled":true,"profile_background_image_url":"http://pbs.twimg.com/profile_background_images/656927849/miyt9dpjz77sc0w3d4vj.png","profile_background_image_url_https":"https://pbs.twimg.com/profile_background_images/656927849/miyt9dpjz77sc0w3d4vj.png","follow_request_sent":false,"entities":{"description":{"urls":[]},"url":{"urls":[{"expanded_url":"http://dev.twitter.com","indices":[0,22],"display_url":"dev.twitter.com","url":"http://t.co/78pYTvWfJd"}]}},"url":"http://t.co/78pYTvWfJd","utc_offset":-25200,"time_zone":"Pacific Time (US & Canada)","notifications":false,"profile_use_background_image":true,"friends_count":48,"profile_sidebar_fill_color":"DDEEF6","screen_name":"twitterapi","id_str":"6253282","profile_image_url":"http://pbs.twimg.com/profile_images/2284174872/7df3h38zabcvjylnyfe3_normal.png","listed_count":12846,"is_translator":false},"coordinates":null}]
```

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
51
52
## Descarga de tweets programática

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
53
### Oauth bearer token
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
54

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
55
56
57
¿Qué es ese "bearer token" que he copiado en un notepar? Oauth permite delegar el uso de tu cuenta de twitter (o de Facebook o de google...) a una aplicación, de forma que pueda hacer uso de ella **en tu nombre**. Para hacer eso, es necesario incluir una firma generada mediante OAuth en todas las peticiones HTTP que haga tu aplicación al API. 

Lee detenidamente las siguientes instrucciones para usar OAUTH y permitir que el usuario delegue a la aplicación el uso (read/write) de tu cuenta de twitter aunque nosotros usaremos la última de todas (application only), que es para lo que se usa el "bearer token":
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
58
59
60
61
62
63
64

* Información de la aplicación: https://apps.twitter.com/
* Overview: https://dev.twitter.com/oauth/overview
* Autenticación por PIN: https://dev.twitter.com/oauth/pin-based
* Autenticación 3-Legged Oauth: https://dev.twitter.com/oauth/3-legged
* Application only https://dev.twitter.com/oauth/application-only

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
65
Bearer Token es una buena forma de recabar datos de twitter dado que sólo queremos acceso read only a la cuenta para leer tweets. El esquema es el siguiente:
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
66
67
68
69
70
71
72
73
74
75

<img src="https://g.twimg.com/dev/documentation/image/appauth_0.png" width="500">

### Crear proyecto y descargar dependencias

Crea un proyecto en Java. Transformalo en Maven (botón derecho sobre el proyecto > Configure > Convert to Maven Project).
Añade las dependencias:
```xml
	<dependencies>
		<dependency>
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
76
			<groupId>com.konghq</groupId>
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
77
			<artifactId>unirest-java</artifactId>
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
78
			<version>3.11.02</version>
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
79
		</dependency>
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
80

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
81
82
83
		<dependency>
			<groupId>com.google.code.gson</groupId>
			<artifactId>gson</artifactId>
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
84
			<version>2.8.6</version>
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
85
86
87
88
89
90
		</dependency>
	</dependencies>
```

### Obtención del Bearer Token

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
91
Antes, era necesario obtener el bearer token, ahora te lo dan generado (solo tienes que copiarlo). Esta es la explicación (**no tenéis que hacerlo**) de cómo se obtendría el bearer token a partir de los secretos de la aplicación:
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
92
93
94
95
96
97

1. codificar en forma de URL ([URLEncode](https://en.wikipedia.org/wiki/Percent-encoding)) tanto el consumer key como el consumer secret. 
2. unir ambos en una string separándolos por ":"
3. codificar el resultado con base64
4. hacer una petición a "https://api.twitter.com/oauth2/token" añadiendo una cabecera con el formato Authorization: Basic {Credenciales codificadas}

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
98
Para conseguirlo, era necesario usar código (**desde el 2019 no es necesario, se puede obtener el bearer token directamente de twitter**):
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
99
100
101

```java
package cdistRest;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
102
import java.io.IOException;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
103
import java.net.URLEncoder;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
104
import java.util.List;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
105
106

import org.apache.commons.codec.binary.Base64;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
107
108
109
110
111
112
113
/* es necesario incluir esta dependencia en el fichermo pom
	<dependency>
    	<groupId>commons-codec</groupId>
		<artifactId>commons-codec</artifactId>
		<version>1.10</version>
	</dependency>
*/
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
114
115

import com.google.gson.Gson;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
116
117
118
119
120

import kong.unirest.GetRequest;
import kong.unirest.HttpResponse;
import kong.unirest.Unirest;
import kong.unirest.UnirestException;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
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

public class TwitterCrawler {

	private static final String oauth_consumer_key = "XXXXXXXXx";
	private static final String oauth_consumer_secret = "XXXXXXXXXXXXXXXXXXXXXXXXXX";
	
	public class BearerToken
	{
		 private String access_token;
         private String token_type;
         
	}

	
	public static String deserializeJson(String input_json)
    {
          Gson gson = new Gson();
          BearerToken new_bearerToken = gson.fromJson(input_json, BearerToken.class);
          return new_bearerToken.access_token;
    }

	public static void main(String args[]) throws UnsupportedEncodingException {
		try {
			/* get bearer token according to https://dev.twitter.com/oauth/application-only */
			String URLEncoderConsumerKey = URLEncoder.encode(oauth_consumer_key, "UTF-8");
			String URLEncoderConsumerSecret = URLEncoder.encode(oauth_consumer_secret, "UTF-8");
			String AuthorizationHeader = URLEncoderConsumerKey+":"+URLEncoderConsumerSecret;
			String AuthorizationHeaderB64 = Base64.encodeBase64String(AuthorizationHeader.getBytes("UTF8"));
			System.out.println(AuthorizationHeaderB64);
			RequestBodyEntity postReq = Unirest.post("https://api.twitter.com/oauth2/token")
					.header("User-Agent","TwitterApp")
					.header("host", "api.twitter.com")
					.header("Authorization", "Basic " + AuthorizationHeaderB64)
					.header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
					.body("grant_type=client_credentials");

			HttpResponse<String> res = postReq.asString();
			System.out.println("Bearer Token : " + TwitterCrawler.deserializeJson(res.getBody()));
			
			/* we now have a brearer token so can make requests to Twitter */
			
		} catch (UnirestException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
```

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
170
Sustituyendo `oauth_consumer_key` y `oauth_consumer_secret` por los valores de tu aplicación se obtenía el bearer token.
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
171

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
172
173
### Gestión de tweets con Json

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
174
175
176
177
178
179
Los tweets devueltos por Twitter (en general cualquier información que devuelva el API) vienen codificados con Json. En la práctica 1 de rest, usamos Gson para almacenar datos en json dentro de clases java.

En esta ocasión, creamos dos clases para guardar, por un lado el Tweet y por otro lado el usuario que lo creó, aunque el primero apunta al segundo. Las clases son las siguientes (usad el mismo paquete para todos, en este caso, por ejemplo **cdistRest**), `Tweet` para almacenar el tweet propiamente dicho, y `User` para guardar los datos del usuario que envía el tweet:


**Clase `Tweet.java`:**
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
180
181
182

```java
package cdistRest;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
183

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
184
185
186
187
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
188

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
189
190
191
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
192

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
193
public class Tweet {
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267

    public static Tweet deserializeJson(String input_json) {
        Gson gson = new Gson();
        Tweet new_Tweet = gson.fromJson(input_json, Tweet.class);
        return new_Tweet;
  }

  public static List<Tweet> deserializeJsonArray(String input_json) {
        Gson gson = new Gson();
        Type collectionType = new TypeToken<Collection<Tweet>>() {}.getType();
        Collection<Tweet> new_tweet_collection = gson.fromJson(input_json,
                      collectionType);
        ArrayList<Tweet> new_Tweet_list = new ArrayList<Tweet>(
                      new_tweet_collection);

        return new_Tweet_list;
  }

  @SerializedName("geo")
  public String geo;

  @SerializedName("in_reply_to_status_id")
  public String in_reply_to_status_id;

  @SerializedName("truncated")
  public String truncated;

  @SerializedName("created_at")
  public String created_at;

  @SerializedName("retweet_count")
  public String retweet_count;

  @SerializedName("in_reply_to_user_id")
  public String in_reply_to_user_id;

  @SerializedName("id_str")
  public String id_str;

  @SerializedName("place")
  public transient String place;

  @SerializedName("favorited")
  public boolean favorited;

  @SerializedName("source")
  public String source;

  @SerializedName("in_reply_to_screen_name")
  public String in_reply_to_screen_name;

  @SerializedName("in_reply_to_status_id_str")
  public String in_reply_to_status_id_str;

  @SerializedName("id")
  public long id;

  @SerializedName("contributors")
  public String contributors;

  @SerializedName("coordinates")
  public String coordinates;

  @SerializedName("retweeted")
  public boolean retweeted;

  @SerializedName("text")
  public String text;

  @SerializedName("profile_image_url")
  public String profile_image_url;

  public User user;

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
268
}
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
269

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
270
271
```

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
272
273
**Clase `User.java`:**

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
```java
package cdistRest;

import com.google.gson.annotations.SerializedName;

public class User {
 
       @SerializedName("friends_count")
       public int friends_count;
 
       @SerializedName("profile_background_color")
       public String profile_background_color;
 
       @SerializedName("profile_background_image_url")
       public String profile_background_image_url;
 
       @SerializedName("created_at")
       public String created_at;
 
       @SerializedName("description")
       public String description;
 
       @SerializedName("favourites_count")
       public int favourites_count;
 
       @SerializedName("lang")
       public String lang;
 
       @SerializedName("notifications")
       public boolean notifications;
 
       @SerializedName("id_str")
       public String id_str;
 
       @SerializedName("default_profile_image")
       public boolean default_profile_image;
 
       @SerializedName("profile_text_color")
       public String profile_text_color;
 
       @SerializedName("default_profile")
       public boolean default_profile;
 
       @SerializedName("show_all_inline_media")
       public boolean show_all_inline_media;
 
       @SerializedName("contributors_enabled")
       public boolean contributors_enabled;
 
       @SerializedName("geo_enabled")
       public boolean geo_enabled;
 
       @SerializedName("screen_name")
       public String screen_name;
 
       @SerializedName("profile_sidebar_fill_color")
       public String profile_sidebar_fill_color;
 
       @SerializedName("profile_image_url")
       public String profile_image_url;
 
       @SerializedName("profile_background_tile")
       public boolean profile_background_tile;
 
       @SerializedName("follow_request_sent")
       public boolean follow_request_sent;
 
       @SerializedName("url")
       public String url;
 
       @SerializedName("statuses_count")
       public int statuses_count;
 
       @SerializedName("following")
       public boolean following;
 
       @SerializedName("time_zone")
       public String time_zone;
 
       @SerializedName("profile_link_color")
       public String profile_link_color;
 
       @SerializedName("protected")
       public boolean protectedd;
 
       @SerializedName("verified")
       public boolean verified;
 
       @SerializedName("profile_sidebar_border_color")
       public String profile_sidebar_border_color;
 
       @SerializedName("followers_count")
       public int followers_count;
 
       @SerializedName("location")
       public String location;
 
       @SerializedName("name")
       public String name;
 
       @SerializedName("is_translator")
       public boolean is_translator;
 
       @SerializedName("id")
       public long id;
 
       @SerializedName("listed_count")
       public int listed_count;
 
       @SerializedName("profile_use_background_image")
       public boolean profile_use_background_image;
 
       @SerializedName("utc_offset")
       public int utc_offset;
 
}
```

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
392
393
### Anade una consulta al API de Twetter

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
394
395
396
Vamos a probar con la función del API [get user timeline](https://developer.twitter.com/en/docs/twitter-api/v1/tweets/timelines/api-reference/get-statuses-user_timeline) que permite obtener los tweets más recientes de un usuario.

Inspecciona la página del API. Revisa los parámetros de la llamada a esa función del API Rest, verás que puedes usar `user_id` que es el ID de twetter (poco conocido, un número) o bien el `screen_name' que es el nombre de la cuenta de twitter que se muestra por internet, por ejemplo `@realmadrid`.
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
397

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
398
399
Además se pueden especificar el número de Tweets a obtener.

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
400
Para obtener 2 tweets de la cuenta `@realmadrid` usa el siguiente código:
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
401
402
403
404
405
406
407

```java
package cdistRest;
import java.io.IOException;
import java.util.List;


Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
408
409
410
411
import kong.unirest.GetRequest;
import kong.unirest.HttpResponse;
import kong.unirest.Unirest;
import kong.unirest.UnirestException;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
412
413


Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
414
public class TwitterCrawler {
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
415

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
416
417
	private static final String bearer_token = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
		
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
418
419
	public static void main(String args[]) throws IOException {
		try {
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
420
					
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436

						
			/* https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=realmadrid&count=2 */
			HttpResponse<String>  json_str_Response = null;
            GetRequest getReq = null;
            getReq = Unirest.get("https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name={screen_name}&count={count}")
            		.routeParam("screen_name","realmadrid")
            		.routeParam("count","2")
            		.header("Authorization", "Bearer " + bearer_token);
            System.out.println("Request to: " + getReq.getUrl());
            System.out.println("Authorization header Bearer " + bearer_token);
            json_str_Response = getReq.asString();
            System.out.println("Json : " + json_str_Response.getBody());      
           
            List<Tweet> tweet_list = Tweet.deserializeJsonArray(json_str_Response.getBody());
           
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
437
            Unirest.shutDown();
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
438
439
440
441
442
443
444
445
446
447
			
			
			
		} catch (UnirestException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
```
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
448

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
449
450
451
452
453
454
455
456
457
Observa, depurando paso a paso y usando break points (en ocasiones entrar dentro de una función con eclipse no funciona - dado que no encuentra el código - lo mejor es usar breakpoints en los métodos en los que queremos parar) lo siguiente:

* Creación de la cabecera de autenticación a partir del objeto BearerToken
* Como se procesa un array de tweets en Json usando el método deserializeJsonArray de la clase Tweet
* Observa cómo GSON automáticamente procesa el objeto User que está dentro de la cadena de texto json de cada tweet
* Observa cómo se procesan los campos, para ver claramente como es el JSON de partida usa un parser online como el de http://json.parser.online.fr/

### Peticiones con cursores

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
458
La función del API descrita en https://dev.twitter.com/rest/reference/get/statuses/user_timeline tiene un límite de 300 peticiones cada 15 minutos. Pero por lo general tiene un límite total de 1500 tweets por cuenta consultada (los 1500 últimos). Esto varía bastante, por lo que es recomendable leer https://developer.twitter.com/en/docs/twitter-api/v1/rate-limits.
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
459

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
460
Pese a la limitación, en cada petición se pueden pedir como máximo 200 (parámetro `count` de la llamada al API)
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
461

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
462
El problema es que los API REST no tienen estado, entonces, **¿cómo bajar los más de 200 tweets?**
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
463
464
465
466

Para ello, es necesario usar **cursores**, elementos que le informen a Twitter a partir de qué Tweet deseas obtener los siguientes 200.

Para ello utilizamos el parámetro `max_id` de la petición, usando la información contenida aquí https://dev.twitter.com/rest/public/timelines
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
467

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
468
Usemos una cuenta popular, cualquiera con más de 200 Tweets. Para ello usa cualquiera de las más populares del mundo (yo voy a usar `@realmadrid`).
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
469
470
471
472

De todos los tweets recibidos, hay que buscar cuál de ellos tiene menor id y usarlo como parámetro (restándole 1) para la siguiente petición.


Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
473
474
```java
package cdistRest;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
475

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
476
import java.io.IOException;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
477
import java.util.ArrayList;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
478
479
import java.util.List;

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
480
481
482
import kong.unirest.GetRequest;
import kong.unirest.HttpResponse;
import kong.unirest.Unirest;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
483
484
485

public class TwitterCrawler {

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
486
487
488
489
490
491
492
	private static final String bearer_token = "xxxxxx";
	private static final int MAX_TWEET_COUNT_PER_REQUEST = 200;

	private long max_id = Long.MAX_VALUE;
	private long tweetcount = 0;
	private String screen_name = "";
	private long pending = 0;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
493
	
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
494
	public TwitterCrawler(int count, String screen_name )
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
495
	{
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
496
497
		this.tweetcount = count;
		this.screen_name = screen_name;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
498
499
	}

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
	public List<Tweet> gettweets() {
		/*
		 * si los tweets caben en una sóla petición, o bien se piden más de
		 * MAX_TWEET_COUNT_PER_REQUEST en principio la primera petición hay que hacerla
		 * para obtener el max_id (máximo id de la secuencia de tweets)
		 */
		List<Tweet> tweet_list_total = new ArrayList<Tweet>();
		pending = tweetcount;
		long max_id = Long.MAX_VALUE;
		
		do {

			long request_twetcount = 0;
			long tweets_obtained = 0;
			if (pending > MAX_TWEET_COUNT_PER_REQUEST) {
				request_twetcount = MAX_TWEET_COUNT_PER_REQUEST;
			} else {
				request_twetcount = pending;
			}

			HttpResponse<String> json_str_Response = null;
			GetRequest getReq = null;
			getReq = Unirest.get(
					"https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name={screen_name}&count={count}&max_id={max_id}")
					.routeParam("screen_name", screen_name).routeParam("count", "" + request_twetcount)
					.routeParam("max_id", ""+(max_id-1))
					.header("Authorization", "Bearer " + bearer_token);
			System.out.println("Request " + request_twetcount + " tweets to: " + getReq.getUrl());
			json_str_Response = getReq.asString();
			List<Tweet> tweet_list_request = Tweet.deserializeJsonArray(json_str_Response.getBody());
			tweets_obtained = tweet_list_request.size();
			System.out.println("received " + tweets_obtained + " tweets");

			/* actualizamos tweetcount con los recibidos */
			pending -= tweets_obtained;
			
			/* actualizamos la lista total de tweets */
			tweet_list_total.addAll(tweet_list_request);
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
538

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
539
			
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
540

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
541
542
543
544
545
			/*
			 * PARADA
			 * si hemos hemos recibido en total de tweets, paramos 
			 */
			if(pending <= 0) break;
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
546
			
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
547
548
549
550
551
552
553
554
555
			/*
			 * PARADA
			 * si hemos hemos recibido menos de request_twetcount es que no hay más. 
			 * Hay que finalizar
			 */
			
			if(tweets_obtained < request_twetcount) break;
			
            for (Tweet tw : tweet_list_request) {
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
556
557
558
559
                if (tw.id < max_id)
                    max_id = tw.id;
            }
			
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
560
						
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
561
			
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
562
563
564
565
566
567
568
		} while (true);
		return tweet_list_total;
	}

	public static void main(String args[]) throws IOException {
		
		TwitterCrawler tc = new TwitterCrawler(400, "realmadrid");
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
569
        List<Tweet> my_tweets = tc.gettweets();
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
570
571
572
573
			
	}
}
```
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
574

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
575

Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
576
Prueba el código para más y menos de 200 tweets. 
Dr. Daniel Diaz Sánchez's avatar
Dr. Daniel Diaz Sánchez committed
577