Usar Medios Sociales para Localizar Testigos Oculares: la API de Twitter
Spanish (Español) translation by Javier Salesi (you can also view the original English article)



Ésta es la segunda de una serie de dos partes sobre el uso de medios sociales para localizar testigos oculares de eventos importantes. En la primera parte, te mostré cómo usar la API de Instagram para encontrar testigos oculares de una grabación de un video de Macklemore en Seattle. En ésta parte, usaremos la API de Twitter para encontrar asistentes del discurso del Presidente Obama en el Puente Edmund Pettus en Selma.
Puedes descargar código para ambos episodios al usar el enlace al respositorio en Github en la barra lateral. También pudieras estar interesado en mi serie de Tuts+, Creando con la API de Twitter.
Las capacidades de geobúsqueda de Twitter son más limitadas y por lo tanto requieren utilizar un código poco más detallado. Publicaciones geoetiquetadas en Twitter solo se pueden encontrar desde los últimos siete días. Y son solo buscables por fecha (no por hora), así que tienes que filtrar los resultados de la API para fines de precisión.
Yo sí participo en las discusiones de la sección de abajo. Si tienes una pregunta o sugerencia de algún tópico, por favor publica un comentario abajo. También puedes contactarme en Twitter @reifman o mandarme un correo electrónico directamente.
Lo que Cubrimos en la Parte Uno
Los teléfonos que cargamos en nuestros bolsillos graban cada uno de nuestros movimientos, compartiéndolos con proveedores de teléfonía celular y con frecuencia compañías de software de terceros cuyas motivaciones generalmente se enfocan en las ganancias.
Muchas personas no se dan cuenta que han dejado geoetiquetas en sus aplicaciones de medios sociales, publicando totalmente su ubicación con cada publicación en medios sociales. Ésto incluye al congresista del Partido Republicano Aaron Schock. La AP utilizó las geoetiquetas de su cuenta de Instagram para exponer su uso de fondos de contribuyentes para vuelos privados extravagantes y otras actividades de lujo.



Así que, el geoetiquetado puede ser usado para bien. En ésta serie, exploraremos como los periodistas o las fuerzas del orden podrían localizar a potenciales testigos oculares de eventos importantes, tales como un crimen o un accidente usando medios sociales.
Sin embargo, el geoetiquetado también puede ser usado de forma abusiva. Científicos de informática y educadores de Berkeley crearon la aplicación Ready or Not? para mostrar cómo el geoetiquetado en Twitter e Instagram registra cada movimiento.
Aquí esta la cuenta de Twitter de Steve Wozniak, cofundador de Apple en la app:



El geoetiquetado en Instagram y Twitter es lo suficientemente exacto para permitir a alguien determinar fácilmente su residencia, lugar de trabajo e itinerario de viaje.
En éste episodio, te guiaré usando la API de Twitter. He proporcionado un repositorio en Github (el enlace está en la barra lateral) para que lo descargues y pruebes el código. Mi aplicación "Eyewitness app" está escrita en el Framework Yii para PHP, del que puedes aprender más en mi serie Programando con Yii2 para Tuts+.
Si no deseas compartir tu ubicación para que la vea el público-o dejar un rastro histórico de tus viajes-la aplicación Ready or Not? ofrece enlaces y guías para desactivar éstas características (observa el enlace en su página de inicio). Francamente, he desactivado la mía y te animo a que lo hagas tú también.
Si eres un miembro de una agencia de las fuerzas del orden o una entidad de medios de comunicación que quisiera más información, no dudes en contactarme directamente. Estaría interesado también en cualquier uso exitoso de éste código (para bien)-harían una interesante nota posterior.
Lo que Hicimos con Instagram
En el último episodio, utilizamos la API de Instagram para encontrar testigos de la grabación del video de Mackemore en el 2013 para White Cadillac. Muy fácilmente, logramos encontrar la foto del miembro de Instagram Joshua Lewis captando a Macklemore saliendo de su vehículo (¿magnífico no?):



Ahora, comencemos a usar la API de Twitter.
Usar la API de Twitter
Al igual que en Instagram, necesitas ingresar a tu cuenta de Twitter y registrar una aplicación de desarrollador. Deberías registrar una aplicación como ésta:



Twitter te mostrará los detalles de tu aplicación:



Aquí esta la página de configuración:



Aquí están las claves y los tokens de acceso para la aplicación. Toma nota de éstos.



Luego, desplázate hacia abajo y crea tokens de acceso para tu cuenta. Toma nota de éstos también.



Agrega las cuatro claves y secretos de ésta configuración a tu archivo /var/secure/eyew.ini:
1 |
mysql_host="localhost" |
2 |
mysql_db="eyew" |
3 |
mysql_un="xxxxxxxxx" |
4 |
mysql_pwd="xxxxxxxxxxxx" |
5 |
instagram_client_id = "4xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx7" |
6 |
instagram_client_secret = "1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx4" |
7 |
twitter_key = "zxxxxxxxxxxxxxxxxxxxx2" |
8 |
twitter_secret ="4xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxp" |
9 |
twitter_oauth_token="1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxs" |
10 |
twitter_oauth_secret="exxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxV" |
Posteriormente, crearemos una migración de Active Record para crear nuestro modelo de Twitter. Ésto almacenará los tuits que recibimos de las llamadas API.
1 |
<?php |
2 |
|
3 |
use yii\db\Schema; |
4 |
use yii\db\Migration; |
5 |
|
6 |
class m150309_174014_create_twitter_table extends Migration |
7 |
{
|
8 |
public function up() |
9 |
{
|
10 |
$tableOptions = null; |
11 |
if ($this->db->driverName === 'mysql') { |
12 |
$tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; |
13 |
}
|
14 |
|
15 |
$this->createTable('{{%twitter}}', [ |
16 |
'id' => Schema::TYPE_PK, |
17 |
'moment_id' => Schema::TYPE_INTEGER . ' NOT NULL', |
18 |
'tweet_id' => Schema::TYPE_BIGINT . ' NOT NULL', |
19 |
'twitter_id' => Schema::TYPE_BIGINT . ' NOT NULL', |
20 |
'screen_name' => Schema::TYPE_STRING . ' NOT NULL DEFAULT 0', |
21 |
'text' => Schema::TYPE_TEXT . ' NOT NULL ', |
22 |
'tweeted_at' => Schema::TYPE_INTEGER . ' NOT NULL', |
23 |
'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', |
24 |
'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', |
25 |
], $tableOptions); |
26 |
$this->addForeignKey('fk_twitter_moment', '{{%twitter}}', 'moment_id', '{{%moment}}', 'id', 'CASCADE', 'CASCADE'); |
27 |
}
|
28 |
|
29 |
|
30 |
public function down() |
31 |
{
|
32 |
$this->dropForeignKey('fk_twitter_moment','{{%twitter}}'); |
33 |
$this->dropTable('{{%twitter}}'); |
34 |
}
|
35 |
}
|
Igual que lo hicimos en la parte uno, necesitas ejecutar la migración:
1 |
./yii migrate/up |
2 |
Yii Migration Tool (based on Yii v2.0.3) |
3 |
|
4 |
Total 1 new migration to be applied: |
5 |
m150309_174014_create_twitter_table |
6 |
|
7 |
Apply the above migration? (yes|no) [no]:yes |
8 |
*** applying m150309_174014_create_twitter_table
|
9 |
> create table {{%twitter}} ... done (time: 0.008s) |
10 |
> add foreign key fk_twitter_moment: {{%twitter}} (moment_id) references {{%moment}} (id) ... done (time: 0.007s) |
11 |
*** applied m150309_174014_create_twitter_table (time: 0.019s) |
12 |
|
13 |
Migrated up successfully. |
Después, usé el generador de código de yii2, Gii, para crear el modelo y los controladores CRUD para el modelo de Twitter. Si obtienes el código del más reciente repositorio en Github usando el enlace de la barra lateral en éste tutorial, tendrás el código también.
Crea un Momento Nuevo
Debido a que Twitter limita las búsquedas de geolocalización a la semana pasada, eventualmente elegí el discurso del Presidente Obama por el 50 Aniversario de un hecho sangriento en el Puente Edmund Pettus en Selma.
Utilicé Google Maps de nuevo para obtener las coordenadas GPS para el puente:



Luego, creé un Momento para buscar el discurso. Lo actualicé varias veces para modificar el radio geográfico de la búsqueda (es un puente) y el rango de tiempo:



Buscar usando la API de Twitter
Las limitaciones de la API de Twitter son que solo te permite buscar por fecha, ejemplo 2015-03-07, mientras que Instagram es indexado por Tiempo Unix preciso. Por lo tanto, tenemos que comenzar nuestra búsqueda en Twitter un día completo por anticipado y buscar hacia atrás.
Ya que probablemente obtendremos muchos tuits afuera de nuestro deseado rango de tiempo, tenemos que hacer llamadas reiteradas a la API de Twitter. Twitter regresa hasta 100 tuits por petición de la API, y permite 180 peticiones cada 15 minutos.
Voy a utilizar la Librería de la API de Twitter para PHP de James Mallison. Aquí está como configuramos la librería para efectuar las llamadas:
1 |
<?php
|
2 |
|
3 |
namespace app\models; |
4 |
|
5 |
use Yii; |
6 |
use yii\db\ActiveRecord; |
7 |
use app\models\Gram; |
8 |
use Instagram; |
9 |
use TwitterAPIExchange; |
10 |
|
11 |
...
|
12 |
|
13 |
public function searchTwitter() { |
14 |
date_default_timezone_set('America/Los_Angeles'); |
15 |
Yii::trace('start searchTwitter '.date('y-m-d h:m ')); |
16 |
// Load your Twitter application keys
|
17 |
$settings = array( |
18 |
'oauth_access_token' => \Yii::$app->params['twitter']['oauth_token'], |
19 |
'oauth_access_token_secret' => \Yii::$app->params['twitter']['oauth_secret'], |
20 |
'consumer_key' => \Yii::$app->params['twitter']['key'], |
21 |
'consumer_secret' => \Yii::$app->params['twitter']['secret'], |
22 |
);
|
23 |
// Connect to Twitter
|
24 |
$twitter = new TwitterAPIExchange($settings); |
Inicialmente, solicitamos 100 resultados de Twitter en nuestras coordenadas GPS hasta una fecha específica.
1 |
public function searchTwitter() { |
2 |
date_default_timezone_set('America/Los_Angeles'); |
3 |
Yii::trace('start searchTwitter '.date('y-m-d h:m ')); |
4 |
// Load your Twitter application keys
|
5 |
$settings = array( |
6 |
'oauth_access_token' => \Yii::$app->params['twitter']['oauth_token'], |
7 |
'oauth_access_token_secret' => \Yii::$app->params['twitter']['oauth_secret'], |
8 |
'consumer_key' => \Yii::$app->params['twitter']['key'], |
9 |
'consumer_secret' => \Yii::$app->params['twitter']['secret'], |
10 |
);
|
11 |
// Connect to Twitter
|
12 |
$twitter = new TwitterAPIExchange($settings); |
13 |
// Query settings for search
|
14 |
$url = 'https://api.twitter.com/1.1/search/tweets.json'; |
15 |
$requestMethod = 'GET'; |
16 |
// rate limit of 180 queries
|
17 |
$limit = 180; |
18 |
$query_count=1; |
19 |
$count = 100; |
20 |
$result_type = 'recent'; |
21 |
// calculate valid timestamp range
|
22 |
$valid_start = $this->start_at; |
23 |
// $until_date and $valid_end = // start time + duration
|
24 |
$valid_end = $this->start_at + ($this->duration*60); |
25 |
Yii::trace( 'Valid Range: '.$valid_start.' -> '.$valid_end); |
26 |
$until_date = date('Y-m-d',$valid_end+(24*3600)); // add one day |
27 |
$distance_km = $this->distance/1000; // distance in km |
28 |
// Unused: &since=$since_date
|
29 |
// $since_date = '2015-03-05';
|
30 |
// Perform first query with until_date
|
31 |
$getfield ="?result_type=$result_type&geocode=".$this->latitude.",".$this->longitude.",".$distance_km."mi&include_entities=false&until=$until_date&count=$count"; |
Sólo registramos tuits dentro de nuestro rango de tiempo preciso, ignorando los otros resultados. Mientras procesamos éstos, tomamos nota del más bajo ID de tuit recibido.
1 |
$tweets = json_decode($twitter->setGetfield($getfield) |
2 |
->buildOauth($url, $requestMethod) |
3 |
->performRequest()); |
4 |
if (isset($tweets->errors)) { |
5 |
Yii::$app->session->setFlash('error', 'Twitter Rate Limit Reached.'); |
6 |
Yii::error($tweets->errors[0]->message); |
7 |
return; |
8 |
}
|
9 |
$max_id = 0; |
10 |
Yii::trace( 'Count Statuses: '.count($tweets->statuses)); |
11 |
Yii::trace( 'Max Tweet Id: '.$max_id); |
12 |
foreach ($tweets->statuses as $t) { |
13 |
// check if tweet in valid time range
|
14 |
$unix_created_at = strtotime($t->created_at); |
15 |
Yii::trace('Tweet @ '.$t->created_at.' '.$unix_created_at.':'.$t->user->screen_name.' '.(isset($t->text)?$t->text:'')); |
16 |
if ($unix_created_at >= $valid_start && $unix_created_at <= $valid_end) |
17 |
{
|
18 |
// print_r($t);
|
19 |
$i = new Twitter(); $i->add($this->id,$t->id_str,$t->user->id_str,$t->user->screen_name,$unix_created_at,(isset($t->text)?$t->text:'')); |
20 |
}
|
21 |
if ($max_id ==0) { |
22 |
$max_id = intval($t->id_str); |
23 |
} else { |
24 |
$max_id = min($max_id, intval($t->id_str)); |
25 |
}
|
26 |
}
|
Luego iteramos, haciendo peticiones reiteradas a Twitter (hasta 179 veces más), solicitando registros adicionales que son más antiguos del ID más bajo del tuit del lote previo. En otras palabras, en peticiones subsecuentes, en lugar de consultar una fecha específica, consultamos el max_id del más bajo ID del tuit que hemos recibido.
Paramos cuando sean regresados menos de 100 registros o cuando los tuits regresados sean anteriores a nuestro rango actual.
Si necesitas accesar a más de 18,000 tuits, necesitarás implementar una tarea en segundo plano para llamara a la API de Twitter, como lo hicimos en nuestra otra serie de la API de Twitter.
Mientras procesamos los resultados de la API, necesitamos filtrar tuits, solamente registrando esos que caen dentro de nuestro tiempo de inicio y tiempo final.
Nota: la API de Twitter tiene muchas peculiaridades frustrantes que hacen la paginación más difícil de lo que debería ser. Muy frecuentemente Twitter no regresa resultados sin un código de error. Otras ocasiones, encontré que regresaba un número pequeño de resultados, pero eso no significaba que otra petición no regresaría más. No hay maneras muy claras de saber cuando Twitter termina de regresarte resultados. Es inconsistente. Así, puedes notar que mi código tiene tiene interesantes soluciones para ello, por ejemplo examina $count_max_repeats.
1 |
$count_repeat_max =0; |
2 |
// Perform all subsequent queries with addition of updated maximum_tweet_id
|
3 |
while ($query_count<=$limit) { |
4 |
$prior_max_id = $max_id; |
5 |
$query_count+=1; |
6 |
Yii::trace( 'Request #: '.$query_count); |
7 |
|
8 |
// Perform subsequent query with max_id
|
9 |
$getfield ="?result_type=$result_type&geocode=".$this->latitude.",".$this->longitude.",".$distance_km."mi&include_entities=false&max_id=$max_id&count=$count"; |
10 |
|
11 |
$tweets = json_decode($twitter->setGetfield($getfield) |
12 |
->buildOauth($url, $requestMethod) |
13 |
->performRequest()); |
14 |
|
15 |
if (isset($tweets->errors)) { |
16 |
Yii::$app->session->setFlash('error', 'Twitter Rate Limit Reached.'); |
17 |
Yii::error($tweets->errors[0]->message); |
18 |
return; |
19 |
}
|
20 |
// sometimes twitter api fails
|
21 |
if (!isset($tweets->statuses)) continue; |
22 |
|
23 |
Yii::trace( 'Count Statuses: '.count($tweets->statuses)); |
24 |
Yii::trace( 'Max Tweet Id: '.$max_id); |
25 |
foreach ($tweets->statuses as $t) { |
26 |
// check if tweet in valid time range
|
27 |
$unix_created_at = strtotime($t->created_at); |
28 |
if ($unix_created_at >= $valid_start && $unix_created_at <= $valid_end) |
29 |
{
|
30 |
$i = new Twitter(); $i->add($this->id,$t->id_str,$t->user->id_str,$t->user->screen_name,$unix_created_at,(isset($t->text)?$t->text:'')); |
31 |
} else if ($unix_created_at < $valid_start) { |
32 |
// stop querying when earlier than valid_start
|
33 |
return; |
34 |
}
|
35 |
$max_id = min($max_id,intval($t->id_str))-1; |
36 |
}
|
37 |
if ($prior_max_id - $max_id <=1 OR count($tweets->statuses)<1) { |
38 |
$count_repeat_max+=1; |
39 |
}
|
40 |
if ($count_repeat_max>5) { |
41 |
// when the api isn't returning more results
|
42 |
break; |
43 |
}
|
44 |
} // end while |
Uno de los primeros resultados regresados incluyeron el tuit de abajo de Fred Davenport mostrando al Presidente Obama en el escenario:



Aquí está en Twitter:



Luego, mientras navegas por más resultados, puedes encontrar mucha más gente presente tuiteando sobre Obama-incluyendo medios de comunicación:



Ahora, hagamos una búsqueda más local.
Una Segunda Búsqueda más local
Key Arena es el recinto para conciertos y eventos deportivos más grande en Seattle. Éste último fin de semana fue sede del Torneo Femenil de Basquetbol Pac-12:



Obtengamos nuestras coordenadas GPS para la Key Arena de Google Maps:



Posteriormente, creé y modifiqué un momento para encontrar un rango de tiempo más largo para el fin de semana de tuits:



Y, aquí están algunos de los resultados. Mi favorito es:
"Quiero irme de éste juego de basquetbol. Odio el basquetbol."



Para la mayor parte, me parece que la API de Instagram es mucho más poderosa que la de Twitter y arroja generalmente resultados más intrigantes. Sin embargo, depende de la clase de persona que estás buscando. Si solo quieres identificar personas que estuvieron ahí, cualquier API funciona bien.
Lo que Hemos aprendido
Espero que hayas disfrutado ésta serie. La encontré fascinante y estuve impresionado con los resultados. Y resalta la preocupación que deberíamos tener sobre nuestro nivel de privacidad en ésta era digital interconectada.
Las APIs de Instagram y Twitter son servicios increíblemente poderosos para encontrar usuarios de medios sociales que estuvieron cerca de ciertos lugares a una cierta hora. Ésta información puede ser usada para bien y se puede abusar de ella. Probablemente deberías considerar desactivar la publicación de tu geolocalización-seguir los enlaces en la aplicación Ready or Not?.
También pudieras querer revisar mi serie Creando con la API de Twitter, también en Tuts+.
Por favor, con toda confianza publica tus preguntas y comentarios abajo. También puedes contactarme en Twitter @reifman o mandarme un correo electrónico directamente. Agradeceria especialmente escuchar de periodistas y miembros de las fuerzas del orden que hagan uso de éstos ejemplos.
También puedes navegar por mi página de instructor en Tuts+ para ver otros tutoriales que he escrito.
Enlaces Relacionados
- La API de Twitter
- La API de Instagram
- Ready or Not? (Enseñando Privacidad)
- Cómo atrapamos al escritor desaparecido Evan Ratliff de la Revista Wired
- Yii Developer Exchange
¡Sé el primero en conocer las nuevas traducciones–sigue @tutsplus_es en Twitter!



