Voltar ao blogue
Guias
Raluca PenciucLast updated on Mar 31, 20269 min read

Da análise de sentimentos ao marketing: as inúmeras vantagens da extração de dados do Twitter

Da análise de sentimentos ao marketing: as inúmeras vantagens da extração de dados do Twitter

O Twitter é um popular site de microblogging e rede social que permite aos utilizadores publicar e interagir com mensagens conhecidas como «tweets». Estes tweets podem conter uma variedade de informações, incluindo texto, imagens e links, tornando-os uma fonte valiosa de dados para diversos casos de utilização.

Desde investigadores individuais a empresas, a extração de dados do Twitter pode ter muitas aplicações práticas: monitorização de tendências e notícias, análise do sentimento dos consumidores, melhorias em campanhas publicitárias, etc.

Embora o Twitter forneça uma API para aceder aos dados, existem algumas ressalvas que deve ter em conta:

  • limitação de taxa: só pode efetuar um determinado número de pedidos dentro de um determinado período de tempo. Se exceder estes limites, o seu acesso à API poderá ser temporariamente suspenso;
  • disponibilidade de dados: tem acesso a um conjunto limitado de dados, tais como tweets, perfis de utilizadores e mensagens diretas. Alguns dados, como tweets eliminados, não estão disponíveis através da API.

Neste artigo, iremos discutir o processo de web scraping do Twitter utilizando Typescript e Puppeteer. Abordaremos a configuração do ambiente necessário, a localização e extração de dados, bem como as potenciais utilizações destes dados.

Além disso, discutiremos também por que razão a utilização de um scraper profissional é melhor para a extração de dados do Twitter do que a utilização apenas da API do Twitter. Este artigo fornecerá um guia passo a passo sobre como extrair dados do Twitter de forma eficaz, facilitando a recolha dos dados de que necessita.

Pré-requisitos

Antes de começarmos, vamos certificar-nos de que temos as ferramentas necessárias à disposição.

Primeiro, descarregue e instale o Node.js a partir do site oficial, certificando-se de que utiliza a versão de Suporte a Longo Prazo (LTS). Isto também instalará automaticamente o Node Package Manager (NPM), que utilizaremos para instalar outras dependências.

Para este tutorial, iremos utilizar o Visual Studio Code como nosso Ambiente de Desenvolvimento Integrado (IDE), mas pode utilizar qualquer outro IDE à sua escolha. Crie uma nova pasta para o seu projeto, abra o terminal e execute o seguinte comando para configurar um novo projeto Node.js:

npm init -y

Isto irá criar um ficheiro package.json no diretório do seu projeto, que armazenará informações sobre o seu projeto e as suas dependências.

Em seguida, precisamos de instalar o TypeScript e as definições de tipos para o Node.js. O TypeScript oferece tipagem estática opcional, o que ajuda a evitar erros no código. Para o fazer, execute no terminal:

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

Pode verificar a instalação executando:

npx tsc --version

O TypeScript utiliza um ficheiro de configuração chamado tsconfig.json para armazenar opções do compilador e outras definições. Para criar este ficheiro no seu projeto, execute o seguinte comando:

npx tsc -init

Certifique-se de que o valor de “outDir” está definido como “dist”. Desta forma, separaremos os ficheiros TypeScript dos ficheiros compilados. Pode encontrar mais informações sobre este ficheiro e as suas propriedades na documentação oficial do TypeScript.

Agora, crie um diretório “src” no seu projeto e um novo ficheiro “index.ts”. É aqui que iremos manter o código de scraping. Para executar código TypeScript, é necessário compilá-lo primeiro; por isso, para garantir que não nos esquecemos deste passo adicional, podemos usar um comando definido por nós.

Vá até ao ficheiro “package.json” e edite a secção “scripts” da seguinte forma:

"scripts": {

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

}

Desta forma, quando executar o script, basta digitar “npm run test” no seu terminal.

Por fim, para extrair os dados do site, vamos usar o Puppeteer, uma biblioteca de navegador headless para Node.js que permite controlar um navegador web e interagir com sites de forma programática. Para instalá-lo, execute este comando no terminal:

npm install puppeteer

É altamente recomendado quando se pretende garantir a integridade dos dados, uma vez que muitos sites atuais contêm conteúdo gerado dinamicamente. Se estiver curioso, pode consultar a documentação do Puppeteer antes de continuar para ver todas as suas capacidades.

Localização dos dados

Agora que já tem o seu ambiente configurado, podemos começar a ver como extrair os dados. Para este artigo, optei por extrair o perfil do Twitter da Netflix: https://twitter.com/netflix.

Vamos extrair os seguintes dados:

  • o nome do perfil;
  • o nome de utilizador do perfil;
  • a biografia do utilizador;
  • a localização do utilizador;
  • o site do utilizador;
  • a data de registo do utilizador;
  • o número de pessoas seguidas pelo utilizador;
  • o número de seguidores do utilizador;
  • informações sobre os tweets do utilizador - nome do autor - nome de utilizador - data de publicação - conteúdo do texto - multimédia (vídeos ou fotos) - número de respostas - número de retweets - número de gostos - número de visualizações.

Pode ver todas estas informações destacadas na captura de ecrã abaixo:

Ao abrir as Ferramentas do Desenvolvedor em cada um destes elementos, poderá observar os seletores CSS que utilizaremos para localizar os elementos HTML. Se ainda não estiver familiarizado com o funcionamento dos seletores CSS, consulte este guia para principiantes.

Extrair os dados

Antes de escrever o nosso script, vamos verificar se a instalação do Puppeteer correu bem:

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

Aqui, abrimos uma janela do navegador, criamos uma nova página, navegamos até ao nosso URL de destino e, em seguida, fechamos o navegador. Por uma questão de simplicidade e depuração visual, abro a janela do navegador maximizada no modo não headless.

Agora, vamos dar uma olhada na estrutura do site e extrair a lista de dados anterior gradualmente:

À primeira vista, deve ter reparado que a estrutura do site é bastante complexa. Os nomes das classes são gerados aleatoriamente e muito poucos elementos HTML são identificados de forma única.

Felizmente para nós, à medida que navegamos pelos elementos pai dos dados alvo, encontramos o atributo “data-testid”. Uma pesquisa rápida no documento HTML confirma que este atributo identifica de forma única o elemento que pretendemos.

Portanto, para extrair o nome e o identificador do perfil, iremos extrair o elemento “div” que tem o atributo “data-testid” definido como “UserName”. O código ficará assim:

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

Como tanto o nome do perfil como o identificador do perfil têm o mesmo pai, o resultado final aparecerá concatenado. Para corrigir isto, usamos o método “split” para separar os dados.

Em seguida, aplicamos a mesma lógica para extrair a biografia do perfil. Neste caso, o valor do atributo “data-testid” é “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)

O resultado final é descrito pela propriedade “textContent” do elemento HTML.

Passando para a secção seguinte dos dados do perfil, encontramos a localização, o site e a data de adesão sob a mesma estrutura.

// 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 obter o número de seguidores e seguidos, precisamos de uma abordagem ligeiramente diferente. Veja a captura de ecrã abaixo:

Não existe o atributo “data-testid” e os nomes das classes continuam a ser gerados aleatoriamente. Uma solução seria direcionar os elementos âncora, uma vez que estes fornecem um 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 tornar o código disponível para qualquer perfil do Twitter, definimos o seletor CSS para selecionar os elementos âncora cujo atributo “href” termina em “/following” ou “/followers”, respetivamente.

Passando para a lista de tweets, podemos novamente identificar facilmente cada um deles usando o atributo “data-testid”, conforme destacado abaixo:

O código não difere do que fizemos até este ponto, com a exceção de utilizar o método «querySelectorAll» e converter o resultado numa 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)

No entanto, embora o seletor CSS esteja certamente correto, deve ter reparado que a lista resultante está quase sempre vazia. Isso acontece porque os tweets são carregados alguns segundos após a página ter sido carregada.

A solução simples para este problema é adicionar um tempo de espera extra depois de navegarmos para o URL de destino. Uma opção é experimentar com um número fixo de segundos, enquanto outra é esperar até que um seletor CSS específico apareça no DOM:

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

Assim, aqui instruímos o nosso script para esperar até que um elemento “div” cujo atributo “aria-label” comece por “Timeline: “ fique visível na página. E agora o trecho anterior deve funcionar na perfeição.

Continuando, podemos identificar os dados sobre o autor do tweet tal como antes, utilizando o atributo “data-testid”.

No algoritmo, iremos percorrer a lista de elementos HTML e aplicar o método “querySelector” a cada um deles. Desta forma, podemos garantir melhor que os seletores que utilizamos são únicos, uma vez que o âmbito visado é muito mais reduzido.

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

Os dados sobre o autor também aparecerão concatenados aqui, por isso, para garantir que o resultado faz sentido, aplicamos o método “split” em cada secção.

O conteúdo de texto do tweet é bastante simples:

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

Para as fotos do tweet, iremos extrair uma lista de elementos “img”, cujos elementos pai são elementos “div” com o atributo “data-testid” definido como “tweetPhoto”. O resultado final será o atributo “src” destes elementos.

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

const tweetPhotosArray = Array.from(tweetPhotos)

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

E, finalmente, a secção de estatísticas do tweet. O número de respostas, retweets e gostos é acessível da mesma forma, através do valor do atributo “aria-label”, depois de identificarmos o elemento com o atributo “data-testid”.

Para obter o número de visualizações, selecionamos o elemento âncora cujo atributo “aria-label” termina com a string “Visualizações. Ver análises do tweet”.

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") : ''

Como o resultado final também conterá caracteres, usamos o método “split” para extrair e devolver apenas o valor numérico. O trecho de código completo para extrair dados de tweets é mostrado abaixo:

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

Após executar todo o script, o seu terminal deverá apresentar algo semelhante a isto:

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'

  }

]

Escalabilidade

Embora o scraping do Twitter possa parecer fácil à primeira vista, o processo pode tornar-se mais complexo e desafiante à medida que amplia o seu projeto. O site implementa várias técnicas para detetar e impedir o tráfego automatizado, pelo que o seu scraper ampliado começa a ser limitado em termos de taxa ou mesmo bloqueado.

Uma forma de superar estes desafios e continuar a fazer scraping em grande escala é utilizar uma API de scraping. Este tipo de serviços oferece uma forma simples e fiável de aceder a dados de sites como o twitter.com, sem a necessidade de criar e manter o seu próprio scraper.

A WebScrapingAPI é um exemplo desse tipo de produto. O seu mecanismo de rotação de proxies evita completamente os bloqueios, e a sua base de conhecimento alargada permite randomizar os dados do navegador para que pareça um utilizador real.

A configuração é rápida e fácil. Basta registar uma conta para receber a sua chave API. Esta pode ser acedida a partir do seu painel de controlo e é utilizada para autenticar os pedidos que enviar.

Como já configurou o seu ambiente Node.js, podemos utilizar o SDK correspondente. Execute o seguinte comando para o adicionar às dependências do seu projeto:

npm install webscrapingapi

Agora, basta enviar uma solicitação GET para recebermos o documento HTML do site. Note que esta não é a única forma de aceder à 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();

Ao ativar o parâmetro “render_js”, enviamos a solicitação usando um navegador headless, tal como fez anteriormente neste tutorial.

Depois de receber o documento HTML, pode usar outra biblioteca para extrair os dados de interesse, como o Cheerio. Nunca ouviu falar dele? Consulte este guia para o ajudar a começar!

Conclusão

Este artigo apresentou um guia abrangente sobre como fazer web scraping no Twitter de forma eficaz utilizando TypeScript. Abordámos os passos para configurar o ambiente necessário, localizar e extrair dados, bem como as potenciais utilizações desta informação.

O Twitter é uma fonte valiosa de dados para quem procura obter insights sobre o sentimento do consumidor, monitorização das redes sociais e inteligência empresarial. No entanto, é importante notar que utilizar apenas a API do Twitter pode não ser suficiente para aceder a todos os dados de que necessita, e é por isso que utilizar um scraper profissional é uma solução melhor.

Em geral, o web scraping do Twitter pode fornecer informações valiosas e constituir um grande trunfo para qualquer empresa ou indivíduo que pretenda obter uma vantagem competitiva.

Sobre o autor
Raluca Penciuc, Desenvolvedor Full-Stack @ WebScrapingAPI
Raluca PenciucDesenvolvedor Full-Stack

Raluca Penciuc é programadora Full Stack na WebScrapingAPI, onde desenvolve scrapers, aperfeiçoa estratégias de evasão e procura formas fiáveis de reduzir a deteção nos sites-alvo.

Comece a construir

Pronto para expandir a sua recolha de dados?

Junte-se a mais de 2.000 empresas que utilizam a WebScrapingAPI para extrair dados da Web à escala empresarial, sem quaisquer custos de infraestrutura.