Análise de conteúdo tabnews (fevereiro de 2023)
Salve povo!
Fiz uma análise das postagens do mês de fevereiro do tabnews, pra me familiarizar com a API e entender melhor alguns padrões de postagem aqui do fórum.
Todo o código desenvolvido nessa postagem está nesse notebook do kaggle: link.
Eu também fiz um post no meu blog no qual eu explico o passo a passo pra reproduzir os gráficos e valores que eu vou expor aqui.
Vamos abordar os seguintes tópicos:
- Buscando dados na API
- Automatizando as requisições com python
- Análise inicial dos dados
- Distribuição dos comentários
- Encontrando tags nas postagens
- Distribuição temporal das postagens
- Distribuição por dias da semana ao longo do mês novo!
- Distribuição por horário do dia
- Conclusão
Entendendo a API e buscando os dados
Pra coletar os dados, busquei uma forma de evitar fazer um consumo excesivo da API, e me baseei no cabeçalho x-pagination-total-rows
que a API do tabnews retorna em todas as requisições.
No meu caso, utilizei o endpoint /api/v1/contents
, pra pegar apenas as postagens, sem os comentários.
$ curl -I "https://www.tabnews.com.br/api/v1/contents?strategy=new"
HTTP/2 200
...
..
link: <https://www.tabnews.com.br/api/v1/contents?strategy=new&page=1&per_page=1>; rel="first", <https://www.tabnews.com.br/api/v1/contents?strategy=new&page=2&per_page=1>; rel="next", <https://www.tabnews.com.br/api/v1/contents?strategy=new&page=8153&per_page=1>; rel="last"
x-pagination-total-rows: 8153
O total de resultados (no dia que eu rodei esse script) eram 8153!
Esse cabeçalho sempre retorna o total de resultados, dado que os endpoints da API são todos paginados, com um máximo de 100 resultados por página.
Combinei essa quantidade de resultados com o retorno do endpoint de analytics, que dá a quantidade de posts feitos por dia num intervalo de dois meses.
$ curl https://www.tabnews.com.br/api/v1/analytics/root-content-published
[
{"date":"13/01","conteudos":38},
{"date":"14/01","conteudos":29},
{"date":"15/01","conteudos":30},
{"date":"16/01","conteudos":50},
{"date":"17/01","conteudos":37},
{"date":"18/01","conteudos":44},
..
]
Com isso, selecionei apenas os posts do início de fevereiro até o dia atual, e fiz descobri "a quantos posts atrás" foi feito o primeiro post de fevereiro.
$ echo '[{"date":"01/02","conteudos":46},{"date":"02/02","conteudos":66},{"date":"03/02","conteudos":43},{"date":"04/02","conteudos":43},{"date":"05/02","conteudos":31},{"date":"06/02","conteudos":55},{"date":"07/02","conteudos":51},{"date":"08/02","conteudos":46},{"date":"09/02","conteudos":47},{"date":"10/02","conteudos":39},{"date":"11/02","conteudos":18},{"date":"12/02","conteudos":19},{"date":"13/02","conteudos":40},{"date":"14/02","conteudos":29},{"date":"15/02","conteudos":45},{"date":"16/02","conteudos":54},{"date":"17/02","conteudos":48},{"date":"18/02","conteudos":21},{"date":"19/02","conteudos":18},{"date":"20/02","conteudos":27},{"date":"21/02","conteudos":27},{"date":"22/02","conteudos":37},{"date":"23/02","conteudos":38},{"date":"24/02","conteudos":41},{"date":"25/02","conteudos":10},{"date":"26/02","conteudos":13},{"date":"27/02","conteudos":51},{"date":"28/02","conteudos":38},{"date":"01/03","conteudos":21},{"date":"02/03","conteudos":35},{"date":"03/03","conteudos":35},{"date":"04/03","conteudos":21},{"date":"05/03","conteudos":15},{"date":"06/03","conteudos":28},{"date":"07/03","conteudos":27},{"date":"08/03","conteudos":40},{"date":"09/03","conteudos":37},{"date":"10/03","conteudos":31},{"date":"11/03","conteudos":29},{"date":"12/03","conteudos":23},{"date":"13/03","conteudos":34}]' \
| jq "[.[].conteudos] | add"
1417
1417! Esse foi o número de postagens criadas des do início de fevereiro, até o dia atual. Isso dá mais ou menos 15 páginas (1417 / 100 ≈ 14).
Como o total de resultados do endpoint eram 8153, eles estavam distribuidos em 82 páginas (8153 / 100 ≈ 82).
Com isso, sei que precisava começar minhas requisições ali pela página 68 (82 - 14).
Automatizando as requisições com `requests` do python
A lógica pra recuperar as postagens dentro do intervalo é simples:
- Começo buscando na página 68, e iterando sobre os resultados de cada página;
- Se um resultado for anterior a fevereiro, eu pulo pro próximo;
- Se o resultado for de fevereiro, salvo em uma lista;
- Vou passando de página em página, sempre adicionando +1 à página atual;
- Quando eu chegar no primeiro post de março, posso parar o algoritmo;
A implementação dessa lógica é extremamente simples se usarmos as libs requests
e json
do python.
Clique aqui para visualizar o script inteiro (40 linhas, ❤️ python)
import json
import requests
from datetime import datetime as dt
from time import sleep
def published_before_february(request_body):
"""Returns whether the given post was `published_at` before 01/02/2023"""
published_at = dt.strptime(request_body["published_at"], "%Y-%m-%dT%H:%M:%S.%fZ")
return dt.strptime("2023-02-01", "%Y-%m-%d") > published_at
def published_after_february(request_body):
"""Returns whether the given post was `published_at` after 28/02/2023"""
published_at = dt.strptime(request_body["published_at"], "%Y-%m-%dT%H:%M:%S.%fZ")
return published_at > dt.strptime("2023-02-28T23:59:59", "%Y-%m-%dT%H:%M:%S")
base_url = "https://www.tabnews.com.br/api/v1"
current_page = 68
request_loop = True
february_posts = []
while request_loop:
request_url = f"{base_url}/contents?strategy=old&page={current_page}&per_page=100"
r = requests.get(request_url)
posts = r.json()
for post in posts:
if published_before_february(post):
continue
if published_after_february(post):
request_loop = False
break
february_posts.append(post)
current_page = current_page + 1
sleep(1) # prevent abuse :')
with open("february.json", "w") as outfile:
json.dump(february_posts, outfile)
Análise inicial dos dados
Foram criados 935 posts em fevereiro.
Eu optei por remover os posts da newsletter do conjunto de dados, o que me deixou com 795 postagens criadas por usuários do fórum.
tabcoins | comments | |
---|---|---|
count | 795.000000 | 795.000000 |
mean | 3.373585 | 5.596226 |
std | 7.522933 | 13.093704 |
min | -12.000000 | 0.000000 |
25% | 1.000000 | 1.000000 |
50% | 1.000000 | 3.000000 |
75% | 3.000000 | 6.000000 |
max | 82.000000 | 301.000000 |
- A metade das postagens (50%) não atinge mais de três comentários.
- Três quartos das postagens (75%) não atinge 3 tabcoins!
- Total de tabcoins recebidos: 2682
- Total de comentários realizados: 4449
Distribuição dos comentários
80% das postagens receberam ao menos um comentário.
Eu queria entender como ocorre essa distribuição, porque a média de comentários é relativamente alta (~5.6 por post), mas o desvio padrão é bem alto (~13).
Como não podemos ter posts negativos, isso indica que poucos posts recebem muitos comentários.
A forma que eu achei de entender essa relação foi agrupando os posts por número de comentários. Depois, multiplicando o número de posts agrupado pelo número de comentários, conseguimos ver quantos porcento do total esses posts representam.
Exemplo:
comments | num_posts | total_comments | percent_posts | percent_comments |
---|---|---|---|---|
301 | 1 | 301 | 0.13 | 6.77 |
84 | 1 | 84 | 0.13 | 1.89 |
76 | 1 | 76 | 0.13 | 1.71 |
71 | 1 | 71 | 0.13 | 1.60 |
44 | 1 | 44 | 0.13 | 0.99 |
43 | 1 | 43 | 0.13 | 0.97 |
Aqui podemos ver que posts com 301 comentários ocorreram 1 única vez, e representam 6.7% do total de comentários realizados no mês todo.
Posts com muitos comentários ocorrem poucas vezes, por exemplo, apenas 1 post teve 84, 76, 71, 44, 43 comentários.
Vamos agrupar pela ocorrência pra entender como os posts populares impactam o conteúdo do site.
num_posts | total_posts | percent_posts | percent_comments | comments | total_comments |
---|---|---|---|---|---|
1 | 12 | 1.56 | 18.20 | 809 | 809 |
2 | 16 | 2.00 | 9.61 | 214 | 428 |
3 | 6 | 0.76 | 3.37 | 50 | 150 |
4 | 8 | 1.00 | 3.60 | 40 | 160 |
5 | 10 | 1.26 | 4.27 | 38 | 190 |
18 | 36 | 4.52 | 7.69 | 19 | 342 |
6 | 6 | 0.75 | 2.16 | 16 | 96 |
Podemos interpretar essa tabela da seguinte forma: um total de doze posts teve um numero de comentários que ocorreu uma única vez (301 comentários, 84 comentários, 76 comentários..), o que representa 1.56% de todos os posts criados no mês.
Esses posts juntos somaram 18.20% dos comentários do mês, num total de 809 comentários.
Ou seja, 18.2% dos comentários ficaram acumulados em menos de 2% das postagens.
Esse acumulo de comentários pode ser visualizado plotando o percentual de posts pelo percentual de comentários.
Encontrando tags nas postagens
O Tabnews utiliza um sistema onde o pessoal taggeia seus posts manualmente, usando colchetes ou categoria: título do post
.
Quais foram as tags mais utilizadas?
Pra responder essa pergunta, usei dois parâmetros.
-
Tags com até duas palavras.
Nesse caso, achei melhor usar um gráfico de barras para a visualização.
-
Tags com apenas uma palavra
Aqui foi interessante usar uma nuvem de palavras.
Distribuição temporal das postagens
Primeiro, podemos visualizar a distribuição dos posts em função do dia da semana.
Temos um aumento nas postagens no meio da semana, e uma acalmada no fds... é como se a galera gostasse de usar o tabnews em horário de trabalho. Será?
Distribuição por dias da semana ao longo do mês
O user rafael comentou que uma boa forma de visualizar a interação na plataforma ao longo dos dias da semana é pela página de status.
Como ficaria esse gráfico sem as contribuições da Newsletter?
Distribuição por horário do dia
No gráfico abaixo, eu separei apenas posts realizados em dia de semana, e fiz a distribuição em função da hora da postagem.
Conclusão
O post acabou ficando meio comprido :P Mas eu queria mostrar como uma quantidade limitada de dados (apenas um mês, contendo só títulos e horários) pode nos dar bastante informação sobre o perfil de postagem de uma comunidade.
Uma análise mais profunda pode estimar valores como:
- o melhor horário para postagens (em termos de comentários recebidos)
- as tags com maior receptividade pelos usuários
- os assuntos com maior engajamento na plataforma
que são métricas poderosas e podem qualificar a audiência pra quem busca expandir seu público através do fórum.
Muito obrigado!