24. Scraping de Redes Sociales
Twitter API, LinkedIn, Facebook Graph API y ética del scraping
Aprende a extraer datos de redes sociales para social listening, análisis de competencia y sentiment analysis. Usarás APIs oficiales y técnicas éticas para obtener datos valiosos de Twitter, LinkedIn, Reddit y más.
¿Por qué social listening?
Social listening: Monitorear menciones de tu marca, competidores y tendencias en redes sociales.
Casos de uso reales:
- Detectar crisis de marca en tiempo real
- Analizar sentiment de lanzamientos de productos
- Identificar influencers clave
- Monitorear competidores
- Descubrir pain points de clientes
Ejemplo: Una startup SaaS usa social listening para detectar cuando usuarios mencionan a competidores con quejas, y se acerca con soluciones.
Ética y legal en social media
Reglas de oro
- USA SIEMPRE APIs oficiales cuando estén disponibles
- Respeta términos de servicio de cada plataforma
- No scrapes datos personales sin consentimiento
- No automatices acciones (likes, follows, DMs) sin permiso
- Rate limiting estricto
APIs oficiales vs. Scraping
| Plataforma | API Oficial | Scraping Directo |
|---|---|---|
| Twitter/X | ✅ API v2 (gratis limitado, de pago completo) | ❌ Prohibido en ToS |
| ✅ API (requiere aprobación) | ❌ Altamente prohibido | |
| ✅ API (gratis, generosa) | ⚠️ Permitido con limitaciones | |
| ✅ Graph API | ❌ Prohibido | |
| ✅ Graph API (solo cuentas Business) | ❌ Prohibido |
Advertencia: Scraping directo de LinkedIn y Facebook puede resultar en ban permanente de la plataforma y acciones legales. Siempre usa APIs oficiales.
Reddit API (PRAW)
Reddit es perfecto para aprender: API generosa, bien documentada y comunidades activas.
Setup de Reddit API
# Instalar: pip install praw pandas
import praw
import pandas as pd
from datetime import datetime
# 1. Crear app en: https://www.reddit.com/prefs/apps
# 2. Obtener credenciales
reddit = praw.Reddit(
client_id='TU_CLIENT_ID',
client_secret='TU_CLIENT_SECRET',
user_agent='MyBot/1.0 by /u/tu_usuario' # Descripción del bot
)
# Verificar conexión
print(f"Conectado como: {reddit.read_only}") # True = solo lectura
Scraping de subreddits
def scrape_subreddit(subreddit_name, limit=100, time_filter='week'):
"""
Extrae posts de un subreddit
Args:
subreddit_name: Nombre del subreddit (sin r/)
limit: Número de posts
time_filter: 'hour', 'day', 'week', 'month', 'year', 'all'
"""
subreddit = reddit.subreddit(subreddit_name)
posts = []
# Obtener top posts
for post in subreddit.top(time_filter=time_filter, limit=limit):
posts.append({
'title': post.title,
'score': post.score, # Upvotes - downvotes
'id': post.id,
'url': post.url,
'num_comments': post.num_comments,
'created': datetime.fromtimestamp(post.created_utc),
'author': str(post.author),
'selftext': post.selftext[:500] if post.selftext else '', # Primeros 500 chars
'link_flair_text': post.link_flair_text
})
return pd.DataFrame(posts)
# Scraping de r/datascience
df_posts = scrape_subreddit('datascience', limit=50, time_filter='week')
print(f"Posts obtenidos: {len(df_posts)}")
print(df_posts.head())
# Análisis
print(f"\nPost más popular:")
top = df_posts.loc[df_posts['score'].idxmax()]
print(f" Título: {top['title']}")
print(f" Score: {top['score']}, Comentarios: {top['num_comments']}")
Extracción de comentarios
def get_post_comments(post_id, limit=100):
"""Extrae comentarios de un post"""
submission = reddit.submission(id=post_id)
submission.comments.replace_more(limit=0) # Expandir todos los comentarios
comments = []
for comment in submission.comments.list()[:limit]:
if isinstance(comment, praw.models.Comment):
comments.append({
'author': str(comment.author),
'body': comment.body,
'score': comment.score,
'created': datetime.fromtimestamp(comment.created_utc)
})
return pd.DataFrame(comments)
# Obtener comentarios del post más popular
top_post_id = df_posts.loc[df_posts['score'].idxmax(), 'id']
df_comments = get_post_comments(top_post_id, limit=50)
print(f"\nComentarios del post más popular: {len(df_comments)}")
print(df_comments.head())
Búsqueda y sentiment analysis
def search_keyword(subreddit_name, keyword, limit=50):
"""Busca posts con palabra clave"""
subreddit = reddit.subreddit(subreddit_name)
posts = []
for post in subreddit.search(keyword, limit=limit, sort='relevance'):
posts.append({
'title': post.title,
'score': post.score,
'num_comments': post.num_comments,
'created': datetime.fromtimestamp(post.created_utc)
})
return pd.DataFrame(posts)
# Buscar menciones de "Python" vs "R"
df_python = search_keyword('datascience', 'Python', limit=30)
df_r = search_keyword('datascience', 'R programming', limit=30)
print(f"Menciones Python: {len(df_python)} posts, avg score: {df_python['score'].mean():.1f}")
print(f"Menciones R: {len(df_r)} posts, avg score: {df_r['score'].mean():.1f}")
Twitter/X API (versión básica)
Twitter API v2 requiere cuenta de desarrollador (gratuita para uso básico).
Setup (conceptual - requiere API key real)
# Instalación: pip install tweepy
import tweepy
# Autenticación con Bearer Token
client = tweepy.Client(bearer_token='TU_BEARER_TOKEN')
# Buscar tweets recientes (últimos 7 días con plan gratuito)
query = 'data science -is:retweet lang:es' # Tweets en español, sin retweets
tweets = client.search_recent_tweets(
query=query,
max_results=10, # Gratuito: máx 10-100 por request
tweet_fields=['created_at', 'public_metrics', 'author_id']
)
# Procesar resultados
for tweet in tweets.data:
print(f"Tweet: {tweet.text[:100]}")
print(f"Likes: {tweet.public_metrics['like_count']}")
print(f"Retweets: {tweet.public_metrics['retweet_count']}\n")
Análisis de hashtags
def analyze_hashtags(keyword, count=100):
"""
Analiza hashtags relacionados con keyword
(Pseudocódigo - requiere API key real)
"""
# Buscar tweets
query = f'{keyword} -is:retweet has:hashtags lang:en'
tweets = client.search_recent_tweets(query=query, max_results=count)
# Extraer hashtags
all_hashtags = []
for tweet in tweets.data:
# Parsear hashtags del texto
hashtags = [word for word in tweet.text.split() if word.startswith('#')]
all_hashtags.extend(hashtags)
# Contar frecuencia
hashtag_counts = pd.Series(all_hashtags).value_counts()
return hashtag_counts.head(20)
# Hashtags relacionados con "machine learning"
# top_hashtags = analyze_hashtags('machine learning')
# print(top_hashtags)
Proyecto práctico: Monitor de marca
import praw
import pandas as pd
from datetime import datetime, timedelta
import time
class SocialListeningBot:
"""
Bot de social listening para Reddit
Monitorea menciones de keywords en subreddits específicos
"""
def __init__(self, reddit_client):
self.reddit = reddit_client
self.mentions = []
def monitor_subreddits(self, subreddits, keywords, hours_back=24):
"""
Monitorea múltiples subreddits buscando keywords
Args:
subreddits: Lista de nombres de subreddits
keywords: Lista de keywords a buscar
hours_back: Cuántas horas hacia atrás buscar
"""
cutoff_time = datetime.now() - timedelta(hours=hours_back)
all_mentions = []
for sub_name in subreddits:
print(f"Monitoreando r/{sub_name}...")
subreddit = self.reddit.subreddit(sub_name)
# Buscar posts recientes
for post in subreddit.new(limit=100):
post_time = datetime.fromtimestamp(post.created_utc)
if post_time < cutoff_time:
continue # Demasiado antiguo
# Buscar keywords en título y texto
text = f"{post.title} {post.selftext}".lower()
for keyword in keywords:
if keyword.lower() in text:
all_mentions.append({
'subreddit': sub_name,
'keyword': keyword,
'title': post.title,
'score': post.score,
'num_comments': post.num_comments,
'url': f"https://reddit.com{post.permalink}",
'created': post_time,
'sentiment': self._simple_sentiment(text)
})
break # Solo registrar una vez por post
time.sleep(1) # Rate limiting
return pd.DataFrame(all_mentions)
def _simple_sentiment(self, text):
"""Análisis de sentiment básico con palabras clave"""
positive_words = ['excelente', 'increíble', 'genial', 'recomiendo', 'love', 'great', 'amazing']
negative_words = ['malo', 'terrible', 'odio', 'decepcionante', 'hate', 'awful', 'worst']
text = text.lower()
pos_count = sum(1 for word in positive_words if word in text)
neg_count = sum(1 for word in negative_words if word in text)
if pos_count > neg_count:
return 'positive'
elif neg_count > pos_count:
return 'negative'
else:
return 'neutral'
def generate_report(self, mentions_df):
"""Genera reporte de menciones"""
if len(mentions_df) == 0:
print("No se encontraron menciones")
return
print(f"\n{'='*60}")
print(f"REPORTE DE SOCIAL LISTENING")
print(f"{'='*60}\n")
print(f"Total de menciones: {len(mentions_df)}")
print(f"Periodo: últimas 24 horas\n")
# Por keyword
print("Menciones por keyword:")
keyword_counts = mentions_df['keyword'].value_counts()
for keyword, count in keyword_counts.items():
print(f" {keyword}: {count}")
# Por sentiment
print("\nSentiment general:")
sentiment_counts = mentions_df['sentiment'].value_counts()
for sentiment, count in sentiment_counts.items():
pct = (count / len(mentions_df)) * 100
print(f" {sentiment.capitalize()}: {count} ({pct:.1f}%)")
# Top menciones
print("\nTop 5 menciones por engagement:")
mentions_df['engagement'] = mentions_df['score'] + mentions_df['num_comments']
top = mentions_df.nlargest(5, 'engagement')[['title', 'subreddit', 'score', 'num_comments', 'sentiment']]
print(top.to_string(index=False))
# Alertas (menciones negativas con alto engagement)
alerts = mentions_df[
(mentions_df['sentiment'] == 'negative') &
(mentions_df['engagement'] > 50)
]
if len(alerts) > 0:
print(f"\n⚠️ ALERTAS: {len(alerts)} menciones negativas con alto engagement")
print(alerts[['title', 'url', 'engagement']].to_string(index=False))
print(f"\n{'='*60}\n")
# Uso del bot
# Configurar
reddit = praw.Reddit(
client_id='TU_CLIENT_ID',
client_secret='TU_CLIENT_SECRET',
user_agent='SocialListeningBot/1.0'
)
bot = SocialListeningBot(reddit)
# Monitorear marca ficticia "DataAnalyzer"
subreddits_to_monitor = ['datascience', 'analytics', 'MachineLearning', 'Python']
keywords_to_track = ['DataAnalyzer', 'data analysis tool', 'analytics platform']
# Ejecutar monitoreo
df_mentions = bot.monitor_subreddits(
subreddits=subreddits_to_monitor,
keywords=keywords_to_track,
hours_back=24
)
# Generar reporte
bot.generate_report(df_mentions)
# Guardar resultados
df_mentions.to_csv('social_listening_report.csv', index=False)
print("Reporte guardado en social_listening_report.csv")
Best practices
Rate limiting agresivo
import time
from functools import wraps
def rate_limit(calls_per_minute=30):
"""Decorator para limitar calls por minuto"""
min_interval = 60.0 / calls_per_minute
last_called = [0.0]
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
elapsed = time.time() - last_called[0]
left_to_wait = min_interval - elapsed
if left_to_wait > 0:
time.sleep(left_to_wait)
result = func(*args, **kwargs)
last_called[0] = time.time()
return result
return wrapper
return decorator
# Uso
@rate_limit(calls_per_minute=20)
def fetch_reddit_data(subreddit):
# Tu código aquí
pass
Caching de resultados
import json
from pathlib import Path
def cache_results(cache_file='cache.json', expiry_hours=24):
"""Cachea resultados para no re-fetch innecesariamente"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
cache_path = Path(cache_file)
now = datetime.now()
# Leer cache si existe
if cache_path.exists():
with open(cache_path, 'r') as f:
cache = json.load(f)
cached_time = datetime.fromisoformat(cache['timestamp'])
age_hours = (now - cached_time).total_seconds() / 3600
if age_hours < expiry_hours:
print(f"Usando cache ({age_hours:.1f}h antiguo)")
return cache['data']
# Fetch fresh data
data = func(*args, **kwargs)
# Guardar en cache
with open(cache_path, 'w') as f:
json.dump({
'timestamp': now.isoformat(),
'data': data
}, f)
return data
return wrapper
return decorator
Resumen de la lección
✅ APIs oficiales son la única forma ética y legal de obtener datos de redes sociales ✅ Reddit es excelente para aprender: API generosa, bien documentada ✅ Twitter requiere aprobación pero ofrece API gratuita limitada ✅ LinkedIn/Facebook scraping directo = ban permanente ✅ Social listening detecta crisis, sentiment y oportunidades de negocio ✅ Rate limiting, caching y respeto a ToS son críticos
Checklist de social media scraping
- Usa siempre API oficial (nunca scraping directo)
- Lee y respeta Terms of Service
- Implementa rate limiting estricto
- Cachea resultados para minimizar requests
- No automatices acciones (likes, follows)
- No extraigas datos personales sin consentimiento
- Monitorea sentiment para detectar crisis
- Configura alertas para menciones negativas
🎯 Próxima lección: 25. Proyecto: Extraer Datos de Competidores
Aplicarás todo lo aprendido en un proyecto completo: monitor de precios y disponibilidad de competidores. ¡Nos vemos! 🚀
¿Completaste esta lección?
Marca esta lección como completada. Tu progreso se guardará en tu navegador.