Volver al blog
Guías
Raluca Penciuc13 de abril de 202314 min de lectura

Del análisis del sentimiento al marketing: Las múltiples ventajas del Web Scraping Twitter

Del análisis del sentimiento al marketing: Las múltiples ventajas del Web Scraping Twitter

Requisitos previos

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

En primer lugar, descarga e instala Node.js desde el sitio web oficial, asegurándote de utilizar la versión Long-Term Support (LTS). Esto también instalará automáticamente Node Package Manager (NPM), que utilizaremos para instalar otras dependencias.

Para este tutorial, usaremos 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 Node.js:

npm init -y

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

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

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

Puede 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 su proyecto, ejecute el siguiente comando:

npx tsc -init

Asegúrate de que el valor de "outDir" se establece en "dist". De esta forma separaremos los archivos 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 olvidamos este paso extra, podemos usar un comando definido a medida.

Dirígete 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 vayas a ejecutar el script, sólo tienes que escribir "npm run test" en tu terminal.

Por último, para escrapear los datos del sitio web utilizaremos Puppeteer, una librería de navegador headless para Node.js que permite controlar un navegador web e interactuar con sitios web de forma programática. Para instalarlo, ejecuta este comando en el terminal:

npm install puppeteer

Es muy recomendable cuando se quiere asegurar la integridad de los datos, ya que hoy en día muchos sitios web contienen contenido generado dinámicamente. Si tienes curiosidad, puedes consultar antes de continuar la documentación de Puppeteer para ver completamente de lo que es capaz.

Localización de los datos

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

Vamos a extraer los siguientes datos:

  • el nombre del perfil;
  • el asa del perfil;
  • la biografía del usuario;
  • la ubicación del usuario;
  • el sitio web del usuario;
  • la fecha de alta del usuario;
  • el recuento de seguimiento del usuario;
  • el recuento 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.

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

Página de perfil de Twitter con campos destacados, como el nombre de la cuenta, el número de seguidores y un tuit con vista previa multimedia

Abriendo las Herramientas de Desarrollador en cada uno de estos elementos podrás notar los selectores CSS que usaremos para ubicar los elementos HTML. Si eres bastante novato en el funcionamiento de los selectores CSS, no dudes en consultar esta guía para principiantes.

Extracción de datos

Antes de escribir nuestro script, vamos a verificar 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 extraigamos gradualmente la lista anterior de datos:

Página de perfil de Twitter con las herramientas de desarrollo del navegador resaltando el código HTML del nombre de la cuenta y el nombre de usuario

A primera vista se habrá dado cuenta de que la estructura del sitio web es bastante compleja. Los nombres de las clases se generan aleatoriamente y muy pocos elementos HTML se identifican de forma unívoca.

Por suerte para nosotros, al navegar por los elementos padre de los datos objetivo, encontramos el atributo "data-testid". Una búsqueda rápida en el documento HTML puede confirmar que este atributo identifica de forma única el elemento al que nos dirigimos.

Por lo tanto, para extraer el nombre de perfil y el handle, extraeremos el elemento "div" que tenga el atributo "data-testid" configurado como "UserName". El código será el siguiente

// 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 de perfil como el identificador de perfil tienen el mismo padre, el resultado final aparecerá concatenado. Para solucionarlo, utilizamos el método "split" para separar los datos.

Página de perfil de Twitter con las herramientas de desarrollo del navegador resaltando el código HTML de la sección de biografía

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 se describe mediante la propiedad "textContent" del elemento HTML.

Página de perfil de Twitter con las herramientas de desarrollo del navegador resaltando el código HTML de los campos de ubicación, sitio web y fecha de registro

Pasando a la siguiente sección de los datos del perfil, encontramos la ubicación, el sitio web y la fecha de afiliación 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 siguiente captura de pantalla:

Página de perfil de Twitter en la que las herramientas de desarrollo del navegador resaltan el código HTML correspondiente al número de seguidores y de personas a las que se sigue

No existe el atributo "data-testid" y los nombres de las clases siguen generándose aleatoriamente. Una solución sería apuntar a 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 esté disponible para cualquier perfil de Twitter, definimos el selector CSS para que se dirija a los elementos de anclaje cuyo atributo "href" termine en "/following" o "/followers" respectivamente.

Pasando a la lista de tweets, podemos de nuevo identificar fácilmente cada uno de ellos utilizando el atributo "data-testid", como se destaca a continuación:

Línea de tiempo de Twitter con las herramientas de desarrollo del navegador resaltando el código HTML de los artículos de los tuits en el feed

El código no difiere de lo que hicimos hasta este punto, con la excepción de utilizar el método "querySelectorAll" y convertir el resultado en un array 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 seguramente correcto, habrá notado que la lista resultante está casi siempre vacía. Eso 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 que naveguemos a la URL de destino. Una opción es jugar con una cantidad fija de segundos, mientras que otra es esperar hasta que un selector CSS específico aparezca en el DOM:

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

Así pues, aquí ordenamos a nuestro script que espere hasta que un elemento "div" cuyo atributo "aria-label" empiece por "Timeline: "sea visible en la página. Y ahora el fragmento anterior debería funcionar sin problemas.

Cabecera de un tuit de Twitter con las herramientas de desarrollo del navegador resaltando el código HTML correspondiente al nombre del autor y la marca de tiempo

A continuación, 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 son únicos, ya que el ámbito objetivo es mucho menor.

// 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"en cada sección.

Línea de tiempo de Twitter con las herramientas de desarrollo del navegador resaltando el elemento HTML que contiene el texto del tuit

El contenido del texto del tuit es bastante sencillo:

const tweetText = t.querySelector('div[data-testid="tweetText"]')
Línea de tiempo de Twitter con las herramientas de desarrollo del navegador resaltando el código HTML de una imagen adjunta a un tuit

Para las fotos del tuit, extraeremos una lista de elementos "img", cuyos 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'))
Las acciones de los tuits de Twitter se muestran en la fila de herramientas de desarrollo del navegador, donde se resaltan los botones de respuesta, retuit y el recuento de «Me gusta»

Y por último, la sección de estadísticas del tweet. El número de respuestas, retweets y likes son accesibles de la misma forma, a través del valor del atributo "aria-label", después de que identifiquemos el elemento con el atributo "data-testid".

Para obtener el número de visualizaciones, nos dirigimos al 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 tweets:

// 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)

Después de 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 difícil a medida que se amplía el proyecto. El sitio web implementa varias técnicas para detectar y evitar el tráfico automatizado, por lo que tu scraper escalado empieza a ver limitada su velocidad o incluso a bloquearse.

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

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

La configuración es rápida y sencilla. Lo único que tienes que hacer es registrar una cuenta para recibir tu clave API. Puedes acceder a ella desde tu panel de control y se utiliza para autenticar las solicitudes que envías.

Guía de inicio rápido del panel de control que muestra tres pasos: clave de acceso a la API, API Playground e integración en tu aplicación

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

npm install webscrapingapi

Ahora sólo queda enviar una petición GET para que recibamos 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();

Habilitando el parámetro "render_js", enviamos la petición utilizando un navegador headless, tal y como has hecho previamente a lo largo de este tutorial.

Después de recibir el documento HTML, puede utilizar otra biblioteca para extraer los datos de interés, como Cheerio. ¿No lo conoces? Echa un vistazo a esta guía para empezar.

Conclusión

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

Twitter es una valiosa fuente de datos para aquellos que buscan obtener información sobre el sentimiento de los consumidores, la monitorización de las redes sociales y la inteligencia empresarial. Sin embargo, es importante tener en cuenta que el uso de la API de Twitter por sí sola puede no ser suficiente para acceder a todos los datos que necesita, y es por eso que el uso de un raspador profesional es una mejor solución.

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

Acerca del autor
Raluca Penciuc, desarrolladora full-stack en 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.