Lección 24 de 29Módulo 5: Web Scraping y APIs

24. Scraping de Redes Sociales

Twitter API, LinkedIn, Facebook Graph API y ética del scraping

30 minutos

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

  1. USA SIEMPRE APIs oficiales cuando estén disponibles
  2. Respeta términos de servicio de cada plataforma
  3. No scrapes datos personales sin consentimiento
  4. No automatices acciones (likes, follows, DMs) sin permiso
  5. Rate limiting estricto

APIs oficiales vs. Scraping

Plataforma API Oficial Scraping Directo
Twitter/X ✅ API v2 (gratis limitado, de pago completo) ❌ Prohibido en ToS
LinkedIn ✅ API (requiere aprobación) ❌ Altamente prohibido
Reddit ✅ API (gratis, generosa) ⚠️ Permitido con limitaciones
Facebook ✅ Graph API ❌ Prohibido
Instagram ✅ 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.