Volver al blog
Guías
Raluca PenciucLast updated on Mar 31, 20269 min read

Del análisis de opiniones al marketing: las numerosas ventajas del web scraping en Twitter

Del análisis de opiniones al marketing: las numerosas ventajas del web scraping en Twitter

Twitter es una popular red social y plataforma de microblogging que permite a los usuarios publicar e interactuar con mensajes conocidos como «tuits». Estos tuits pueden contener diversa información, como texto, imágenes y enlaces, lo que los convierte en una valiosa fuente de datos para diversos fines.

Desde investigadores individuales hasta empresas, el web scraping de Twitter puede tener muchas aplicaciones prácticas: seguimiento de tendencias y noticias, análisis de la opinión de los consumidores, mejoras en campañas publicitarias, etc.

Aunque Twitter proporciona una API para acceder a los datos, presenta algunas salvedades que debes tener en cuenta:

  • limitación de frecuencia: solo se puede realizar un número determinado de solicitudes en un periodo de tiempo concreto. Si se superan estos límites, el acceso a la API puede suspenderse temporalmente;
  • disponibilidad de datos: tienes acceso a un conjunto limitado de datos, como tuits, perfiles de usuario y mensajes directos. Algunos datos, como los tuits eliminados, no están disponibles a través de la API.

En este artículo, analizaremos el proceso de web scraping en Twitter utilizando Typescript y Puppeteer. Abordaremos la configuración del entorno necesario, la localización y extracción de datos, y los posibles usos de estos datos.

Además, también analizaremos por qué es mejor utilizar un scraper profesional para extraer datos de Twitter que utilizar únicamente la API de Twitter. Este artículo ofrece una guía paso a paso sobre cómo extraer datos de Twitter de forma eficaz, facilitándote la recopilación de los datos que necesitas.

Requisitos previos

Antes de empezar, asegurémonos de que contamos con las herramientas necesarias.

En primer lugar, descarga e instala Node.js desde la página web oficial, asegurándote de utilizar la versión de soporte a largo plazo (LTS). Esto también instalará automáticamente Node Package Manager (NPM), que utilizaremos para instalar otras dependencias.

Para este tutorial, utilizaremos Visual Studio Code como nuestro entorno de desarrollo integrado (IDE), pero puedes usar cualquier otro IDE de tu elección. Crea una nueva carpeta para tu proyecto, abre el terminal y ejecuta el siguiente comando para configurar un nuevo proyecto de Node.js:

npm init -y

Esto creará un archivo package.json en el directorio de tu proyecto, que almacenará información sobre tu proyecto y sus dependencias.

A continuación, debemos instalar TypeScript y las definiciones de tipos para Node.js. TypeScript ofrece tipado estático opcional, lo que ayuda a prevenir errores en el código. Para ello, ejecuta en la terminal:

npm install typescript @types/node --save-dev

Puedes verificar la instalación ejecutando:

npx tsc --version

TypeScript utiliza un archivo de configuración llamado tsconfig.json para almacenar las opciones del compilador y otros ajustes. Para crear este archivo en tu proyecto, ejecuta el siguiente comando:

npx tsc -init

Asegúrate de que el valor de «outDir» esté establecido en «dist». De esta forma separaremos los archivos de TypeScript de los compilados. Puedes encontrar más información sobre este archivo y sus propiedades en la documentación oficial de TypeScript.

Ahora, crea un directorio «src» en tu proyecto y un nuevo archivo «index.ts». Aquí es donde guardaremos el código de scraping. Para ejecutar código TypeScript tienes que compilarlo primero, así que, para asegurarnos de que no nos olvidamos de este paso adicional, podemos usar un comando definido por nosotros mismos.

Ve al archivo «package.json» y edita la sección «scripts» de la siguiente manera:

"scripts": {

    "test": "npx tsc && node dist/index.js"

}

De esta forma, cuando ejecutes el script, solo tendrás que escribir «npm run test» en tu terminal.

Por último, para extraer los datos del sitio web utilizaremos Puppeteer, una biblioteca de navegador sin interfaz gráfica para Node.js que te permite controlar un navegador web e interactuar con sitios web mediante programación. Para instalarlo, ejecuta este comando en la terminal:

npm install puppeteer

Es muy recomendable si quieres asegurarte de que tus datos estén completos, ya que muchos sitios web actuales contienen contenido generado dinámicamente. Si tienes curiosidad, puedes echar un vistazo a la documentación de Puppeteer antes de continuar para ver todo lo que es capaz de hacer.

Ubicación de los datos

Ahora que ya tienes tu entorno configurado, podemos empezar a ver cómo extraer los datos. Para este artículo, he elegido extraer el perfil de Twitter de Netflix: https://twitter.com/netflix.

Vamos a extraer los siguientes datos:

  • el nombre del perfil;
  • el nombre de usuario del perfil;
  • la biografía del usuario;
  • la ubicación del usuario;
  • el sitio web del usuario;
  • la fecha de registro del usuario;
  • el número de personas a las que sigue el usuario;
  • el número de seguidores del usuario;
  • información sobre los tuits del usuario - nombre del autor - nombre de usuario - fecha de publicación - contenido del texto - archivos multimedia (vídeos o fotos) - número de respuestas - número de retuits - número de «Me gusta» - número de visualizaciones.

Puedes ver toda esta información resaltada en la captura de pantalla siguiente:

Al abrir las Herramientas de desarrollador en cada uno de estos elementos, podrás ver los selectores CSS que utilizaremos para localizar los elementos HTML. Si eres bastante nuevo en el funcionamiento de los selectores CSS, no dudes en consultar esta guía para principiantes.

Extracción de los datos

Antes de escribir nuestro script, comprobemos que la instalación de Puppeteer se ha realizado correctamente:

import puppeteer from 'puppeteer';

async function scrapeTwitterData(twitter_url: string): Promise<void> {

    // Launch Puppeteer

    const browser = await puppeteer.launch({

        headless: false,

    	  args: ['--start-maximized'],

    	  defaultViewport: null

    })

    // Create a new page

    const page = await browser.newPage()

    // Navigate to the target URL

    await page.goto(twitter_url)

    // Close the browser

    await browser.close()

}

scrapeTwitterData("https://twitter.com/netflix")

Aquí abrimos una ventana del navegador, creamos una nueva página, navegamos a nuestra URL de destino y luego cerramos el navegador. En aras de la simplicidad y la depuración visual, abro la ventana del navegador maximizada en modo no headless.

Ahora, echemos un vistazo a la estructura del sitio web y extrayamos la lista de datos anterior poco a poco:

A primera vista, habrás notado que la estructura del sitio web es bastante compleja. Los nombres de las clases se generan aleatoriamente y muy pocos elementos HTML están identificados de forma única.

Por suerte para nosotros, al navegar por los elementos padres de los datos objetivo, encontramos el atributo «data-testid». Una búsqueda rápida en el documento HTML confirma que este atributo identifica de forma única el elemento que buscamos.

Por lo tanto, para extraer el nombre y el identificador del perfil, extraeremos el elemento «div» que tiene el atributo «data-testid» establecido en «UserName». El código tendrá este aspecto:

// Extract the profile name and handle

const profileNameHandle = await page.evaluate(() => {

    const nameHandle = document.querySelector('div[data-testid="UserName"]')

    return nameHandle ? nameHandle.textContent : ""

})

const profileNameHandleComponents = profileNameHandle.split('@')

console.log("Profile name:", profileNameHandleComponents[0])

console.log("Profile handle:", '@' + profileNameHandleComponents[1])

Dado que tanto el nombre del perfil como el identificador del perfil tienen el mismo elemento padre, el resultado final aparecerá concatenado. Para solucionar esto, utilizamos el método «split» para separar los datos.

A continuación, aplicamos la misma lógica para extraer la biografía del perfil. En este caso, el valor del atributo «data-testid» es «UserDescription»:

// Extract the user bio

const profileBio = await page.evaluate(() => {

    const location = document.querySelector('div[data-testid="UserDescription"]')

    return location ? location.textContent : ""

})

console.log("User bio:", profileBio)

El resultado final viene descrito por la propiedad «textContent» del elemento HTML.

Pasando a la siguiente sección de los datos del perfil, encontramos la ubicación, el sitio web y la fecha de registro bajo la misma estructura.

// Extract the user location

const profileLocation = await page.evaluate(() => {

    const location = document.querySelector('span[data-testid="UserLocation"]')

    return location ? location.textContent : ""

})

console.log("User location:", profileLocation)

// Extract the user website

const profileWebsite = await page.evaluate(() => {

    const location = document.querySelector('a[data-testid="UserUrl"]')

    return location ? location.textContent : ""

})

console.log("User website:", profileWebsite)

// Extract the join date

const profileJoinDate = await page.evaluate(() => {

    const location = document.querySelector('span[data-testid="UserJoinDate"]')

    return location ? location.textContent : ""

})

console.log("User join date:", profileJoinDate)

Para obtener el número de seguidores y seguidos, necesitamos un enfoque ligeramente diferente. Echa un vistazo a la captura de pantalla siguiente:

No hay ningún atributo «data-testid» y los nombres de las clases siguen generándose aleatoriamente. Una solución sería centrarse en los elementos de anclaje, ya que proporcionan un atributo «href» único.

// Extract the following count

const profileFollowing = await page.evaluate(() => {

    const location = document.querySelector('a[href$="/following"]')

    return location ? location.textContent : ""

})

console.log("User following:", profileFollowing)

// Extract the followers count

const profileFollowers = await page.evaluate(() => {

    const location = document.querySelector('a[href$="/followers"]')

    return location ? location.textContent : ""

})

console.log("User followers:", profileFollowers)

Para que el código sea válido para cualquier perfil de Twitter, hemos definido el selector CSS para que se centre en los elementos de anclaje cuyo atributo «href» termine en «/following» o «/followers», respectivamente.

Pasando a la lista de tuits, podemos identificar fácilmente cada uno de ellos utilizando el atributo «data-testid», tal y como se destaca a continuación:

El código no difiere de lo que hemos hecho hasta ahora, con la excepción de utilizar el método «querySelectorAll» y convertir el resultado en una matriz de JavaScript:

// Extract the user tweets

const userTweets = await page.evaluate(() => {

    const tweets = document.querySelectorAll('article[data-testid="tweet"]')

    const tweetsArray = Array.from(tweets)

    return tweetsArray

})

console.log("User tweets:", userTweets)

Sin embargo, aunque el selector CSS es sin duda correcto, quizá hayas notado que la lista resultante está casi siempre vacía. Esto se debe a que los tuits se cargan unos segundos después de que se haya cargado la página.

La solución sencilla a este problema es añadir un tiempo de espera adicional después de navegar a la URL de destino. Una opción es probar con una cantidad fija de segundos, mientras que otra es esperar hasta que aparezca un selector CSS específico en el DOM:

await page.waitForSelector('div[aria-label^="Timeline: "]')

Así pues, aquí le indicamos a nuestro script que espere hasta que un elemento «div» cuyo atributo «aria-label» comience por «Timeline: » sea visible en la página. Y ahora el fragmento anterior debería funcionar a la perfección.

Continuando, podemos identificar los datos sobre el autor del tuit igual que antes, utilizando el atributo «data-testid».

En el algoritmo, recorreremos la lista de elementos HTML y aplicaremos el método «querySelector» a cada uno de ellos. De esta forma, podemos asegurarnos mejor de que los selectores que utilizamos sean únicos, ya que el ámbito de aplicación es mucho más reducido.

// Extract the user tweets

const userTweets = await page.evaluate(() => {

    const tweets = document.querySelectorAll('article[data-testid="tweet"]')

    const tweetsArray = Array.from(tweets)

    return tweetsArray.map(t => {

        const authorData = t.querySelector('div[data-testid="User-Names"]')

        const authorDataText = authorData ? authorData.textContent : ""

        const authorComponents = authorDataText.split('@')

        const authorComponents2 = authorComponents[1].split('·')

        return {

            authorName: authorComponents[0],

            authorHandle: '@' + authorComponents2[0],

            date: authorComponents2[1],

        }

    })

})

console.log("User tweets:", userTweets)

Los datos sobre el autor también aparecerán concatenados aquí, así que, para asegurarnos de que el resultado tiene sentido, aplicamos el método «split» a cada sección.

El contenido de texto del tuit es bastante sencillo:

const tweetText = t.querySelector('div[data-testid="tweetText"]')

Para las fotos del tuit, extraeremos una lista de elementos «img», cuyos elementos padres son elementos «div» con el atributo «data-testid» establecido en «tweetPhoto». El resultado final será el atributo «src» de estos elementos.

const tweetPhotos = t.querySelectorAll('div[data-testid="tweetPhoto"] > img')

const tweetPhotosArray = Array.from(tweetPhotos)

const photos = tweetPhotosArray.map(p => p.getAttribute('src'))

Y, por último, la sección de estadísticas del tuit. Se puede acceder al número de respuestas, retuits y «me gusta» de la misma manera, a través del valor del atributo «aria-label», después de identificar el elemento con el atributo «data-testid».

Para obtener el número de visualizaciones, nos centramos en el elemento de anclaje cuyo atributo «aria-label» termina con la cadena «Views. View Tweet analytics».

const replies = t.querySelector('div[data-testid="reply"]')

const repliesText = replies ? replies.getAttribute("aria-label") : ''

const retweets = t.querySelector('div[data-testid="retweet"]')

const retweetsText = retweets ? retweets.getAttribute("aria-label") : ''

const likes = t.querySelector('div[data-testid="like"]')

const likesText = likes ? likes.getAttribute("aria-label") : ''

const views = t.querySelector('a[aria-label$="Views. View Tweet analytics"]')

const viewsText = views ? views.getAttribute("aria-label") : ''

Dado que el resultado final también contendrá caracteres, utilizamos el método «split» para extraer y devolver únicamente el valor numérico. A continuación se muestra el fragmento de código completo para extraer los datos de los tuits:

// Extract the user tweets

const userTweets = await page.evaluate(() => {

    const tweets = document.querySelectorAll('article[data-testid="tweet"]')

    const tweetsArray = Array.from(tweets)

    return tweetsArray.map(t => {

        

        // Extract the tweet author, handle, and date

        const authorData = t.querySelector('div[data-testid="User-Names"]')

        const authorDataText = authorData ? authorData.textContent : ""

        const authorComponents = authorDataText.split('@')

        const authorComponents2 = authorComponents[1].split('·')

        // Extract the tweet content

        const tweetText = t.querySelector('div[data-testid="tweetText"]')

        // Extract the tweet photos

        const tweetPhotos = t.querySelectorAll('div[data-testid="tweetPhoto"] > img')

        const tweetPhotosArray = Array.from(tweetPhotos)

        const photos = tweetPhotosArray.map(p => p.getAttribute('src'))

        // Extract the tweet reply count

        const replies = t.querySelector('div[data-testid="reply"]')

        const repliesText = replies ? replies.getAttribute("aria-label") : ''

        // Extract the tweet retweet count

        const retweets = t.querySelector('div[data-testid="retweet"]')

        const retweetsText = retweets ? retweets.getAttribute("aria-label") : ''

        // Extract the tweet like count

        const likes = t.querySelector('div[data-testid="like"]')

        const likesText = likes ? likes.getAttribute("aria-label") : ''

        // Extract the tweet view count

        const views = t.querySelector('a[aria-label$="Views. View Tweet analytics"]')

        const viewsText = views ? views.getAttribute("aria-label") : ''

        return {

            authorName: authorComponents[0],

            authorHandle: '@' + authorComponents2[0],

            date: authorComponents2[1],

            text: tweetText ? tweetText.textContent : '',

            media: photos,

            replies: repliesText.split(' ')[0],

            retweets: retweetsText.split(' ')[0],

            likes: likesText.split(' ')[0],

            views: viewsText.split(' ')[0],

        }

    })

})

console.log("User tweets:", userTweets)

Tras ejecutar todo el script, tu terminal debería mostrar algo como esto:

Profile name: Netflix

Profile handle: @netflix

User bio:

User location: California, USA

User website: netflix.com/ChangePlan

User join date: Joined October 2008

User following: 2,222 Following

User followers: 21.3M Followers

User tweets: [

  {

    authorName: 'best of the haunting',

    authorHandle: '@bestoffhaunting',

    date: '16 Jan',

    text: 'the haunting of hill house.',

    media: [

      'https://pbs.twimg.com/media/FmnGkCNWABoEsJE?format=jpg&name=360x360',

  	'https://pbs.twimg.com/media/FmnGkk0WABQdHKs?format=jpg&name=360x360',

  	'https://pbs.twimg.com/media/FmnGlTOWABAQBLb?format=jpg&name=360x360',

  	'https://pbs.twimg.com/media/FmnGlw6WABIKatX?format=jpg&name=360x360'

    ],

    replies: '607',

    retweets: '37398',

    likes: '170993',

    views: ''

  },

  {

    authorName: 'Netflix',

    authorHandle: '@netflix',

    date: '9h',

    text: 'The Glory Part 2 premieres March 10 -- FIRST LOOK:',

    media: [

  	'https://pbs.twimg.com/media/FmuPlBYagAI6bMF?format=jpg&name=360x360',

  	'https://pbs.twimg.com/media/FmuPlBWaEAIfKCN?format=jpg&name=360x360',

  	'https://pbs.twimg.com/media/FmuPlBUagAETi2Z?format=jpg&name=360x360',

  	'https://pbs.twimg.com/media/FmuPlBZaEAIsJM6?format=jpg&name=360x360'

    ],

    replies: '250',

    retweets: '4440',

    likes: '9405',

    views: '656347'

  },

  {

    authorName: 'Kurtwood Smith',

    authorHandle: '@tahitismith',

    date: '14h',

    text: 'Two day countdown...more stills from the show to hold you over...#That90sShow on @netflix',

    media: [

  	'https://pbs.twimg.com/media/FmtOZTGaEAAr2DF?format=jpg&name=360x360',

  	'https://pbs.twimg.com/media/FmtOZTFaUAI3QOR?format=jpg&name=360x360',

  	'https://pbs.twimg.com/media/FmtOZTGaAAEza6i?format=jpg&name=360x360',

  	'https://pbs.twimg.com/media/FmtOZTGaYAEo-Yu?format=jpg&name=360x360'

    ],

    replies: '66',

    retweets: '278',

    likes: '3067',

    views: ''

  },

  {

    authorName: 'Netflix',

    authorHandle: '@netflix',

    date: '12h',

    text: 'In 2013, Kai the Hatchet-Wielding Hitchhiker became an internet sensation   -- but that viral fame put his questionable past squarely on the radar of authorities. \n' +

  	'\n' +

  	'The Hatchet Wielding Hitchhiker is now on Netflix.',

    media: [],

    replies: '169',

    retweets: '119',

    likes: '871',

    views: '491570'

  }

]

Ampliación

Aunque el scraping de Twitter puede parecer fácil al principio, el proceso puede volverse más complejo y desafiante a medida que amplías tu proyecto. El sitio web implementa diversas técnicas para detectar y prevenir el tráfico automatizado, por lo que tu scraper ampliado empieza a sufrir limitaciones de velocidad o incluso a ser bloqueado.

Una forma de superar estos retos y seguir realizando scraping a gran escala es utilizar una API de scraping. Este tipo de servicios ofrecen una forma sencilla y fiable de acceder a datos de sitios web como twitter.com, sin necesidad de crear y mantener tu propio scraper.

WebScrapingAPI es un ejemplo de este tipo de productos. Su mecanismo de rotación de proxies evita los bloqueos por completo, y su amplia base de conocimientos permite aleatorizar los datos del navegador para que parezca un usuario real.

La configuración es rápida y sencilla. Solo tienes que registrarte para recibir tu clave API. Se puede acceder a ella desde tu panel de control y se utiliza para autenticar las solicitudes que envías.

Como ya has configurado tu entorno Node.js, podemos utilizar el SDK correspondiente. Ejecuta el siguiente comando para añadirlo a las dependencias de tu proyecto:

npm install webscrapingapi

Ahora solo queda enviar una solicitud GET para recibir el documento HTML del sitio web. Ten en cuenta que esta no es la única forma de acceder a la API.

import webScrapingApiClient from 'webscrapingapi';

const client = new webScrapingApiClient("YOUR_API_KEY");

async function exampleUsage() {

    const api_params = {

        'render_js': 1,

        'proxy_type': 'residential',

        'wait_for_css': 'div[aria-label^="Timeline: "]',

        'timeout': 30000

    }

    const URL = "https://twitter.com/netflix"

    const response = await client.get(URL, api_params)

    if (response.success) {

        console.log(response.response.data)

    } else {

        console.log(response.error.response.data)

    }

}

exampleUsage();

Al habilitar el parámetro «render_js», enviamos la solicitud utilizando un navegador sin interfaz gráfica, tal y como hiciste anteriormente en este tutorial.

Tras recibir el documento HTML, puedes utilizar otra biblioteca para extraer los datos que te interesen, como Cheerio. ¿No la conoces? ¡Echa un vistazo a esta guía para ayudarte a empezar!

Conclusión

Este artículo ha presentado una guía completa sobre cómo realizar web scraping en Twitter de forma eficaz utilizando TypeScript. Hemos cubierto los pasos para configurar el entorno necesario, localizar y extraer datos, y los posibles usos de esta información.

Twitter es una valiosa fuente de datos para quienes buscan obtener información sobre la opinión de los consumidores, el seguimiento de las redes sociales y la inteligencia empresarial. Sin embargo, es importante tener en cuenta que utilizar solo la API de Twitter puede no ser suficiente para acceder a todos los datos que necesitas, y por eso utilizar un scraper profesional es una mejor solución.

En general, el web scraping de Twitter puede proporcionar información valiosa y puede ser un gran activo para cualquier empresa o particular que busque obtener una ventaja competitiva.

Acerca del autor
Raluca Penciuc, Desarrollador full-stack @ WebScrapingAPI
Raluca PenciucDesarrollador full-stack

Raluca Penciuc es desarrolladora full stack en WebScrapingAPI, donde se dedica a crear rastreadores, mejorar las técnicas de evasión y buscar formas fiables de reducir la detección en los sitios web de destino.

Empieza a crear

¿Estás listo para ampliar tu recopilación de datos?

Únete a más de 2000 empresas que utilizan WebScrapingAPI para extraer datos de la web a escala empresarial sin ningún gasto de infraestructura.