Da análise de sentimentos ao marketing: Os muitos benefícios do Web Scraping Twitter

Raluca Penciuc em Abr 13 2023

imagem do blogue

O Twitter é um popular sítio Web de microblogging e redes sociais 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 hiperligações, o que os torna uma valiosa fonte de dados para vários casos de utilização.

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

Embora o Twitter forneça uma API para aceder aos dados, apresenta algumas advertências que devem ser tidas em conta:

  • limitação do ritmo: só pode efetuar um determinado número de pedidos num determinado período de tempo. Se exceder estes limites, o seu acesso à API pode ser temporariamente suspenso;
  • disponibilidade de dados: o utilizador tem acesso a um conjunto limitado de dados, como tweets, perfis de utilizador e mensagens diretas. Alguns dados, como tweets excluídos, não estão disponíveis por meio da API.

Neste artigo, discutiremos o processo de raspagem da Web do Twitter usando Typescript e Puppeteer. Abordaremos a configuração do ambiente necessário, a localização e a extração de dados e os possíveis usos desses dados.

Além disso, também discutiremos por que usar um raspador profissional é melhor para raspar o Twitter na Web do que usar apenas a API do Twitter. Este artigo fornecerá um guia passo a passo sobre como fazer um web scraping eficaz do Twitter, facilitando a coleta dos dados de que você precisa.

Pré-requisitos

Antes de começarmos, vamos certificar-nos de que dispomos das ferramentas necessárias.

Primeiro, baixe e instale o Node.js do site oficial, certificando-se de usar a versão Long-Term Support (LTS). Isso também instalará automaticamente o Node Package Manager (NPM), que usaremos para instalar outras dependências.

Para este tutorial, usaremos o Visual Studio Code como nosso ambiente de desenvolvimento integrado (IDE), mas você pode usar qualquer outro IDE de sua escolha. Crie uma nova pasta para seu projeto, abra o terminal e execute o seguinte comando para configurar um novo projeto Node.js:

npm init -y

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

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

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

Pode verificar a instalação executando:

npx tsc --versão

O TypeScript usa um arquivo de configuração chamado tsconfig.json para armazenar opções do compilador e outras configurações. Para criar esse arquivo em seu projeto, execute o seguinte comando:

npx tsc -init

Certifique-se de que o valor de "outDir" esteja definido como "dist". Desta forma, iremos separar 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 vamos manter o código de raspagem. Para executar o código TypeScript, é necessário compilá-lo primeiro, portanto, para garantir que não nos esqueçamos dessa etapa extra, podemos usar um comando definido de forma personalizada.

Vá ao ficheiro "package. json" e edite a secção "scripts" desta forma:

"scripts": {

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

}

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

Por fim, para extrair os dados do site, usaremos o Puppeteer, uma biblioteca de navegador sem cabeça para Node.js que permite controlar um navegador da Web e interagir com sites de forma programática. Para instalá-la, execute este comando no terminal:

npm install puppeteer

É altamente recomendado quando se pretende garantir a integridade dos dados, uma vez que muitos sítios Web actuais contêm conteúdo gerado dinamicamente. Se estiver curioso, pode consultar a documentação do Puppeteer antes de continuar para ver o que ele é capaz de fazer.

Localização dos dados

Agora que já tem o seu ambiente configurado, podemos começar a analisar a extração dos 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;
  • a pega do perfil;
  • a biografia do utilizador;
  • a localização do utilizador;
  • o sítio Web do utilizador;
  • a data de adesão do utilizador;
  • a contagem de utilizadores seguintes;
  • o número de seguidores do utilizador;
  • informações sobre os tweets do utilizador
    - nome do autor
    - identificador do autor
    - data de publicação
    - conteúdo do texto
    - meios de comunicação (vídeos ou fotografias)
    - 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 imagem de ecrã abaixo:

imagem do blogue

Ao abrir as Ferramentas do desenvolvedor em cada um desses elementos, você poderá observar os seletores CSS que usaremos para localizar os elementos HTML. Se não sabe muito bem como funcionam os selectores CSS, pode consultar 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é o URL de destino e fechamos o navegador. Por uma questão de simplicidade e depuração visual, abro a janela do navegador maximizada no modo sem cabeça.

Agora, vamos dar uma vista de olhos à estrutura do sítio Web e extrair gradualmente a lista anterior de dados:

imagem do blogue

À primeira vista, deve ter reparado que a estrutura do sítio Web é 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, ao navegarmos pelos elementos pai dos dados visados, encontramos o atributo "data-testid". Uma rápida pesquisa no documento HTML pode confirmar que este atributo identifica exclusivamente o elemento que pretendemos.

Por conseguinte, para extrair o nome do perfil e o identificador, extrairemos o elemento "div" que tem o atributo "data-testid" definido como "UserName". O código terá o seguinte aspeto:

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

Uma vez que tanto o nome do perfil como o identificador do perfil têm o mesmo pai, o resultado final aparecerá concatenado. Para resolver este problema, utilizamos o método "split" para separar os dados.

imagem do blogue

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

imagem do blogue

Passando à secção seguinte dos dados do perfil, encontramos na mesma estrutura a localização, o sítio Web e a data de adesão.

// 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 seguidores, precisamos de uma abordagem ligeiramente diferente. Veja a captura de ecrã abaixo:

imagem do blogue

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 visar os elementos âncora cujo atributo "href" termina em "/following" ou "/followers", respetivamente.

Passando à lista de tweets, podemos novamente identificar facilmente cada um deles utilizando o atributo "data-testid", conforme destacado abaixo:

imagem do blogue

O código não é diferente do que fizemos até este ponto, à exceção da utilização do método "querySelectorAll" e da conversão do resultado para uma matriz 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, você deve ter notado que a lista resultante está quase sempre vazia. Isso acontece porque os tweets são carregados alguns segundos depois de 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 é jogar com uma quantidade fixa 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 damos instruções ao nosso script para esperar até que um elemento "div" cujo atributo "aria-label" começa por "Timeline: " esteja visível na página. E agora o snippet anterior deve funcionar perfeitamente.

imagem do blogue

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

No algoritmo, vamos iterar através da lista de elementos HTML e aplicar o método "querySelector" em cada um deles. Desta forma, podemos garantir melhor que os selectores que utilizamos são únicos, uma vez que o âmbito visado é muito mais pequeno.

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

imagem do blogue

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

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

Para as fotografias do tweet, extrairemos uma lista de elementos "img", cujos pais 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'))
imagem do blogue

E, finalmente, a secção de estatísticas do tweet. O número de respostas, retweets e gostos são acessíveis 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, direcionamos o elemento âncora cujo atributo "aria-label" termina com a string "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") : ''

Uma vez que o resultado final também conterá caracteres, utilizamos 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)

Depois de executar o script completo, o seu terminal deve mostrar algo como 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'

}

]

Aumentar a escala

Embora a recolha de dados do Twitter possa parecer fácil no início, o processo pode tornar-se mais complexo e difícil à medida que aumenta a escala do seu projeto. O Web site implementa várias técnicas para detetar e impedir o tráfego automatizado, pelo que o seu scraper em grande escala começa a ter a taxa limitada ou mesmo a ser bloqueado.

Uma forma de ultrapassar estes desafios e continuar a fazer scraping em grande escala é utilizar uma API de scraping. Esses tipos de serviços fornecem uma maneira simples e confiável de acessar dados de sites como twitter.com, sem a necessidade de construir e manter seu próprio raspador.

O WebScrapingAPI é um exemplo de um produto deste género. O seu mecanismo de rotação de proxy evita completamente os bloqueios e a sua base de conhecimentos alargada torna possível aleatorizar os dados do browser para que se pareça com um utilizador real.

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

imagem do blogue

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 só falta enviar um pedido GET para recebermos o documento HTML do sítio Web. Note-se 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 o pedido utilizando um browser sem cabeça, tal como fez anteriormente ao longo deste tutorial.

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

Conclusão

Este artigo apresentou um guia abrangente sobre como fazer o web scrape do Twitter de forma eficaz usando o TypeScript. Cobrimos as etapas de configuração do ambiente necessário, localização e extração de dados e os possíveis usos dessas informações.

O Twitter é uma valiosa fonte de dados para quem procura obter informações sobre o sentimento dos consumidores, 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 raspador profissional é a melhor solução.

De um modo geral, a recolha de dados da Web no Twitter pode fornecer informações valiosas e pode ser um grande trunfo para qualquer empresa ou indivíduo que pretenda obter uma vantagem competitiva.

Notícias e actualizações

Mantenha-se atualizado com os mais recentes guias e notícias sobre raspagem da Web, subscrevendo a nossa newsletter.

We care about the protection of your data. Read our <l>Privacy Policy</l>.Privacy Policy.

Artigos relacionados

miniatura
GuiasScrapy vs. Beautiful Soup: Um guia de comparação abrangente para ferramentas de raspagem da Web

Explore uma comparação pormenorizada entre o Scrapy e o Beautiful Soup, duas ferramentas líderes de raspagem da Web. Compreenda as suas caraterísticas, prós e contras, e descubra como podem ser utilizadas em conjunto para satisfazer as várias necessidades do projeto.

WebscrapingAPI
avatar do autor
WebscrapingAPI
10 min. de leitura
miniatura
Ciência da recolha de dados da WebWeb Scraping facilitado: a importância da análise de dados

Descubra como extrair e organizar eficientemente dados para raspagem da Web e análise de dados através de análise de dados, bibliotecas de análise de HTML e metadados schema.org.

Suciu Dan
avatar do autor
Suciu Dan
12 min ler
miniatura
Casos de utilizaçãoXPath vs. seletores CSS

Os selectores XPath são melhores do que os selectores CSS para a recolha de dados da Web? Conheça os pontos fortes e as limitações de cada método e faça a escolha certa para o seu projeto!

Mihai Maxim
avatar do autor
Mihai Maxim
8 min. de leitura