Voltar ao blogue
Guias
Raluca Penciuc13 de abril de 202314 minutos de leitura

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

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:

Twitter profile page with highlighted fields like account name, follower count, and a tweet with media preview

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:

Twitter profile page with browser devtools highlighting the HTML for the account name and handle

À 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.

Twitter profile page with browser devtools highlighting the HTML for the bio section

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.

Twitter profile page with browser devtools highlighting the HTML for location, website, and join date fields

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:

Twitter profile page with browser devtools highlighting the HTML for following and follower counts

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:

Twitter timeline with browser devtools highlighting the HTML for tweet articles in the feed

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.

Twitter tweet header with browser devtools highlighting the HTML for the author name and timestamp

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.

Twitter timeline with browser devtools highlighting the HTML element containing the tweet text

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

const tweetText = t.querySelector('div[data-testid="tweetText"]')
Twitter timeline with browser devtools highlighting the HTML for an attached tweet image

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'))
Twitter tweet actions row with browser devtools highlighting buttons for reply, retweet, and like counts

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.

Dashboard quickstart guide showing three steps: API access key, API Playground, and integration into your application

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.