Raspar las cuentas de Twitter más populares
Gracias a la observación de publicaciones en Twitter, podemos rastrear diversas tendencias. En esta entrada, mostraré cómo descargar datos sobre cuentas en este servicio y seleccionar aquellas que tienen el mayor ratio de influencia.
Daniel Gustaw
• 8 min read
La lista de las cuentas más populares en Twitter se puede encontrar en la página de Trackalytics:
Los perfiles de Twitter más seguidos | Trackalytics
En esta publicación, mostraré cómo descargar estos datos y ordenarlos según el número de tweets por seguidores. Luego analizaremos cuántos creadores podríamos seguir simultáneamente sin exceder el límite de la API gratuita de Twitter: 500,000 tweets/mes.
Análisis de la Página Raspada
Antes de comenzar la recolección de datos, siempre es necesario elegir un vector de adquisición de datos apropiado. Lo primero que hay que revisar es la pestaña de red en el navegador. En nuestro caso, en la página:
https://www.trackalytics.com/the-most-followed-twitter-profiles/page/1/
Tenemos una solicitud para la página ya renderizada:
por lo que el renderizado debe tener lugar en el backend. Confirmaremos esto revisando el código fuente de la página.
view-source:https://www.trackalytics.com/the-most-followed-twitter-profiles/page/1/
De hecho, vemos datos listos para ser extraídos:
Escribiremos un script que lo obtenga y lo procese utilizando la biblioteca cheerio
.
Configuración del Proyecto
Inicializamos el proyecto con los comandos:
npm init -y && tsc --init
Creamos un directorio raw
para archivos descargados
mkdir -p raw
Estamos instalando TypeScript
npm i -D @types/node
El núcleo de nuestro programa puede verse así:
interface TwitterAccount {
// todo implement
}
class Page {
i: number;
constructor(i: number) {
this.i = i;
}
url() {
return `https://www.trackalytics.com/the-most-followed-twitter-profiles/page/${this.i}/`
}
file() {
return `${process.cwd()}/raw/${this.i}.html`
}
sync() {
// TODO implement
return false;
}
parse(): TwitterAccount[] {
// todo implement
return []
}
}
const main = async () => {
let i = 1;
const accounts = [];
while (new Page(i).sync()) {
const newAccounts = new Page(i).parse()
if (newAccounts.length === 0) break;
accounts.push(...newAccounts);
i++;
}
return accounts;
}
main().then(console.log).catch(console.error)
Tenemos aquí que implementar una interfaz para las cuentas resultantes de la estructura de los datos recuperados, una función para verificar si una página existe y guardar datos, y una función para el análisis.
Modelo de Datos
Al observar los datos mostrados:
Puedes crear la siguiente interfaz que describe una cuenta de Twitter.
interface TwitterAccount {
rank: number
avatar: string
name: string
url: string
followers_total: number
followers_today: number
following_total: number
following_today: number
tweets_total: number
tweets_today: number
}
Descargando Páginas
Usaremos la biblioteca axios
para descargar páginas. debug
será adecuado para registrar datos.
npm i axios debug
npm i -D @types/debug
Después de realizar varias importaciones:
import axios from "axios";
import * as fs from "fs";
import Debug from 'debug';
const debug = Debug('app');
La función de sincronización podría verse así:
async sync() {
try {
const fileExists = fs.existsSync(this.file())
if (fileExists) return true;
const {data, status} = await axios.get(this.url());
if (status !== 200) return false;
fs.writeFileSync(this.file(), data);
debug(`Saved ${this.file()}`)
return true;
} catch (e) {
console.error(e)
return false;
}
}
Procesando Páginas
[...document.querySelectorAll('.post-content>table>tbody tr')].map(tr => {
const cols = [3,4,5].map(i => tr.querySelector(`td:nth-child(${i})`).textContent.split(/\s+/).filter(x => x && x !== "(").map(x => parseInt(x.replace(/\)|\(|,/g,''))))
return {
rank: parseInt(tr.querySelector('.badge-info').textContent),
avatar: tr.querySelector('img').src,
name: tr.querySelector('td:nth-child(2) a').title,
url: tr.querySelector('td:nth-child(2) a').href,
followers_total: cols[0][0],
followers_today: cols[0][1],
following_total: cols[1][0],
following_today: cols[1][1],
tweets_total: cols[2][0],
tweets_today: cols[2][1]
}})
En node js
no tenemos un objeto document
y para realizar selectores en el árbol DOM necesitamos construirlo a partir de texto como lo hace el navegador. Sin embargo, en lugar de utilizar el mecanismo nativo incorporado, usaremos una de las bibliotecas populares. Las más conocidas son:
- cheerio
- jsdom
Una vez hice una comparación de ellas en términos de rendimiento:
¿Es cheerio todavía 8 veces más rápido que jsdom? · Issue #700 · cheeriojs/cheerio
Todo indica que cheerio
es una elección mucho mejor.
Para procesarlo en una forma aceptable por cheerio, necesitamos reemplazar document
con cheerio.load(content)
, y los elementos deben envolverse con cheerio(element).find
para buscar sus descendientes. Para los atributos, necesitamos la función attr
y para los arrays, la función toArray
. Estos son realmente todos los cambios, implementarlos toma un momento y como resultado de su aplicación al selector que funciona en el navegador obtendremos la implementación de la función parse
.
parse(): TwitterAccount[] {
const content = fs.readFileSync(this.file()).toString();
const $ = cheerio.load(content);
return $('.post-content>table>tbody tr').toArray().map(tr => {
const cols = [3, 4, 5].map(i => cheerio(tr)
.find(`td:nth-child(${i})`).text().split(/\s+/)
.filter(x => x && x !== "(").map(
x => parseInt(x.replace(/\)|\(|,/g, ''))))
return {
rank: parseInt(cheerio(tr).find('.badge-info').text()),
avatar: cheerio(tr).find('img').attr('src') || '',
name: cheerio(tr).find('td:nth-child(2) a').attr('title') || '',
url: cheerio(tr).find('td:nth-child(2) a').attr('href') || '',
followers_total: cols[0][0],
followers_today: cols[0][1],
following_total: cols[1][0],
following_today: cols[1][1],
tweets_total: cols[2][0],
tweets_today: cols[2][1]
}
})
}
Agregando una pequeña modificación al final del programa para que guarde los datos obtenidos en un archivo json
const main = async () => {
let i = 1;
const accounts = [];
while (await new Page(i).sync()) {
const newAccounts = new Page(i).parse()
if (newAccounts.length === 0) break;
accounts.push(...newAccounts);
i++;
debug(`Page ${i}`);
}
return accounts;
}
main().then(a => {
fs.writeFileSync(process.cwd() + '/accounts.json', JSON.stringify(a.map(a => ({
...a,
username: a.url.split('/').filter(a => a).reverse()[0]
}))));
console.log(a);
}).catch(console.error)
después de instalar el paquete cheerio
npm i cheerio
podemos iniciar nuestro programa con un comando
time DEBUG=app ts-node index.ts
A continuación vemos cómo se ve en el entorno del programa bmon
para monitorear interfaces de red y htop
para verificar el uso de memoria ram
y del procesador.
Para guardar este archivo en la base de datos mongo, podemos usar el comando:
mongoimport --collection twitter_accounts <connection_string> --jsonArray --drop --file ./accounts.json
A continuación, realizando agregación:
[{
$group: {
_id: null,
tweets_today: {
$sum: '$tweets_today'
},
tweets_total: {
$sum: '$tweets_total'
},
followers_today: {
$sum: '$followers_today'
},
followers_total: {
$sum: '$followers_total'
},
count: {
$sum: 1
}
}
}]
podemos aprender que las 16,000 cuentas más populares en Twitter generaron 0.6 mil millones de tuits, de los cuales 177 mil hoy.
tweets_today:177779
tweets_total:613509174
followers_today:9577284
followers_total:20159062136
count:16349
El número total de seguidores es de 20 mil millones (por supuesto, hay numerosos duplicados), y hoy los seguidores ganados por estas cuentas ascienden a 10 millones.
La API gratuita de Twitter permite la escucha en tiempo real de hasta 500 mil tweets. Esto significa que, en promedio, se pueden recopilar 16 mil diarios.
Supongamos que nuestra tarea es observar aquellas cuentas que logran el mayor alcance con el menor número de publicaciones. La siguiente agregación nos ayudará a encontrarlas:
[{$match: {
tweets_total: {$gt: 0}
}}, {$addFields: {
influence_by_tweet: {$divide: ['$followers_total','$tweets_total']}
}}, {$sort: {
influence_by_tweet: -1
}}, {$match: {
influence_by_tweet: {$gt: 100}
}}, {$group: {
_id: null,
tweets_today: {
$sum: '$tweets_today'
},
tweets_total: {
$sum: '$tweets_total'
},
followers_today: {
$sum: '$followers_today'
},
followers_total: {
$sum: '$followers_total'
},
count: {
$sum: 1
}
}}]
Gracias a esto, podemos seleccionar 3,798 cuentas que publican solo 17,161 tuits diarios pero tienen un alcance de hasta 14 mil millones de usuarios en total, y hoy ganaron 8 millones.
tweets_today:17161
tweets_total:32346484
followers_today:8197454
followers_total:14860523601
count:3798
Esto significa que el número de cuentas observadas ha caído al 23%, el número de tweets por día al 9%, pero el número total de seguidores se ha mantenido en el 73% del valor anterior (por supuesto, estos cálculos no tienen en cuenta la duplicación), y el número de seguidores que se están ganando hoy por estas cuentas seleccionadas es el 85% del valor original.
En resumen. Seleccionamos solo una parte de las cuentas que, escribiendo el 9% de los tweets en relación con todo el grupo de las cuentas más populares cada día, nos permiten lograr el 85% del alcance que nos interesa.
Nuestro criterio de corte es obtener al menos 100 seguidores por tweet. Debemos esperar aproximadamente 17000/24/60 = 11 tweets por minuto.
De acuerdo con la tradición de este blog, al final proporciono un enlace a los datos extraídos:
https://preciselab.fra1.digitaloceanspaces.com/blog/scraping/accounts.json
Other articles
You can find interesting also.
Compilación del intérprete PHP 7 en BunsenLabs
La compilación es un proceso que a veces requiere instalar paquetes o vincular dependencias. En este caso, la tarea era entregar php7 a un sistema que no lo tenía en los repositorios disponibles.
Daniel Gustaw
• 9 min read
Cómo instalar MongoDB 6 en Fedora 37
Instalación de Mongodb 6 en Fedora Linux 37. El artículo muestra un fragmento faltante de la documentación oficial y dos pasos después de la instalación que se presentan de manera extremadamente simple en comparación con otras fuentes.
Daniel Gustaw
• 2 min read
Simplificando la línea de comandos de Linux con GPT-CLI (rust, código abierto)
Ejecuta comandos de Linux con lenguaje natural. Ej.: 'muestra mi tarjeta gráfica' en lugar de 'lspci | grep VGA', proyecto de código abierto escrito en Rust
Daniel Gustaw
• 4 min read