Pular para o conteúdo
🧠Inteligência em Código
📚 Tutoriais10 min de leitura

Web Scraping Inteligente com Python e IA

Aprenda a construir scrapers inteligentes que usam IA para extrair, classificar e estruturar dados da web automaticamente.

#web-scraping#python#ia#beautifulsoup#selenium

Introdução

Web scraping tradicional depende de seletores CSS e XPath frágeis que quebram com qualquer mudança no layout de um site. Com a integração de Inteligência Artificial, podemos criar scrapers mais robustos e inteligentes — capazes de entender o conteúdo das páginas, extrair informações semanticamente e se adaptar a mudanças de estrutura.

Neste tutorial, vamos construir um sistema completo de web scraping inteligente que combina técnicas tradicionais (BeautifulSoup, requests) com modelos de IA para classificação, extração de entidades e sumarização do conteúdo coletado.

Pré-requisitos

pip install requests beautifulsoup4 lxml selenium
pip install openai transformers spacy
python -m spacy download pt_core_news_sm

1. Scraper Básico com BeautifulSoup

Primeiro, vamos criar a base do nosso scraper:

"""
Scraper base com BeautifulSoup.
Coleta artigos de sites de notícias de tecnologia.
"""

import requests
from bs4 import BeautifulSoup
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
import time
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


@dataclass
class Artigo:
    """Representa um artigo extraído."""
    titulo: str
    url: str
    texto: str
    data_publicacao: Optional[str] = None
    autor: Optional[str] = None
    tags: list[str] = field(default_factory=list)
    # Campos preenchidos pela IA
    resumo: Optional[str] = None
    sentimento: Optional[str] = None
    categoria: Optional[str] = None
    entidades: list[str] = field(default_factory=list)


class WebScraper:
    """Scraper inteligente com suporte a IA."""
    
    def __init__(self, delay: float = 1.0):
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                          "AppleWebKit/537.36 (KHTML, like Gecko) "
                          "Chrome/120.0.0.0 Safari/537.36",
            "Accept-Language": "pt-BR,pt;q=0.9,en;q=0.8"
        })
        self.delay = delay
    
    def fetch(self, url: str) -> BeautifulSoup:
        """Faz requisição HTTP e retorna BeautifulSoup."""
        logger.info(f"Acessando: {url}")
        
        response = self.session.get(url, timeout=30)
        response.raise_for_status()
        response.encoding = response.apparent_encoding
        
        time.sleep(self.delay)  # Respeitar o servidor
        
        return BeautifulSoup(response.text, "lxml")
    
    def extract_text(self, soup: BeautifulSoup, selector: str) -> str:
        """Extrai e limpa texto de um seletor CSS."""
        element = soup.select_one(selector)
        if element:
            # Remover scripts e estilos
            for tag in element.find_all(["script", "style", "nav", "footer"]):
                tag.decompose()
            return element.get_text(separator="\n", strip=True)
        return ""
    
    def extract_articles_from_listing(
        self, url: str, article_selector: str,
        title_selector: str, link_selector: str
    ) -> list[dict]:
        """Extrai lista de artigos de uma página de listagem."""
        soup = self.fetch(url)
        articles = []
        
        for item in soup.select(article_selector):
            title_el = item.select_one(title_selector)
            link_el = item.select_one(link_selector)
            
            if title_el and link_el:
                href = link_el.get("href", "")
                if href and not href.startswith("http"):
                    href = requests.compat.urljoin(url, href)
                
                articles.append({
                    "titulo": title_el.get_text(strip=True),
                    "url": href
                })
        
        logger.info(f"Encontrados {len(articles)} artigos")
        return articles


# Exemplo de uso
scraper = WebScraper(delay=1.5)

2. Extração Inteligente de Entidades com spaCy

Agora adicionamos IA para extrair entidades nomeadas (pessoas, empresas, locais):

"""
Extração de Entidades Nomeadas (NER) usando spaCy.
"""

import spacy


class EntityExtractor:
    """Extrai entidades nomeadas de textos."""
    
    def __init__(self, model_name: str = "pt_core_news_sm"):
        self.nlp = spacy.load(model_name)
    
    def extract(self, texto: str) -> dict:
        """
        Extrai entidades nomeadas do texto.
        
        Returns:
            Dicionário com entidades agrupadas por tipo
        """
        doc = self.nlp(texto[:100000])  # Limite de segurança
        
        entidades = {}
        for ent in doc.ents:
            tipo = ent.label_
            if tipo not in entidades:
                entidades[tipo] = []
            if ent.text not in entidades[tipo]:
                entidades[tipo].append(ent.text)
        
        return entidades
    
    def extract_key_phrases(self, texto: str, top_n: int = 10) -> list[str]:
        """Extrai as frases-chave mais relevantes."""
        doc = self.nlp(texto)
        
        # Extrair chunks nominais relevantes
        chunks = []
        for chunk in doc.noun_chunks:
            # Filtrar chunks muito curtos ou com stopwords apenas
            if len(chunk.text.split()) >= 2 and len(chunk.text) > 5:
                chunks.append(chunk.text.strip())
        
        # Contar frequência e retornar mais comuns
        from collections import Counter
        freq = Counter(chunks)
        return [phrase for phrase, _ in freq.most_common(top_n)]


# Exemplo de uso
extractor = EntityExtractor()

texto_exemplo = """
A OpenAI anunciou hoje em San Francisco o lançamento do GPT-5,
seu mais novo modelo de linguagem. Sam Altman, CEO da empresa,
apresentou as novidades em uma conferência transmitida ao vivo.
O Google e a Microsoft também prepararam suas respostas com
modelos concorrentes.
"""

entidades = extractor.extract(texto_exemplo)
print("Entidades encontradas:")
for tipo, lista in entidades.items():
    print(f"  {tipo}: {', '.join(lista)}")

frases = extractor.extract_key_phrases(texto_exemplo)
print(f"\nFrases-chave: {frases}")

3. Classificação Automática de Conteúdo

Usamos um modelo de classificação zero-shot para categorizar o conteúdo extraído:

"""
Classificador automático de conteúdo usando zero-shot.
"""

from transformers import pipeline


class ContentClassifier:
    """Classifica textos em categorias sem treinamento prévio."""
    
    def __init__(self):
        self.classifier = pipeline(
            "zero-shot-classification",
            model="facebook/bart-large-mnli",
            device=-1
        )
        
        self.categorias_padrao = [
            "inteligência artificial",
            "programação",
            "segurança cibernética",
            "hardware",
            "startups",
            "ciência de dados",
            "cloud computing",
            "redes e infraestrutura",
            "games",
            "dispositivos móveis"
        ]
    
    def classify(
        self, texto: str,
        categorias: list[str] | None = None,
        top_n: int = 3
    ) -> list[dict]:
        """
        Classifica um texto nas categorias fornecidas.
        
        Args:
            texto: Texto para classificar
            categorias: Lista de categorias (usa padrão se None)
            top_n: Número de categorias no resultado
            
        Returns:
            Lista de dicts com categoria e score
        """
        cats = categorias or self.categorias_padrao
        
        # Limitar texto para performance
        texto_truncado = texto[:1000]
        
        resultado = self.classifier(
            texto_truncado,
            candidate_labels=cats,
            hypothesis_template="Este texto é sobre {}."
        )
        
        return [
            {"categoria": label, "score": round(score, 4)}
            for label, score in zip(
                resultado["labels"][:top_n],
                resultado["scores"][:top_n]
            )
        ]


classifier = ContentClassifier()

resultado = classifier.classify(
    "A NVIDIA lançou sua nova GPU com foco em treinamento de "
    "modelos de deep learning e inferência de redes neurais."
)
print("Classificação:")
for r in resultado:
    print(f"  {r['categoria']}: {r['score']:.2%}")

4. Sumarização com IA

Para condensar artigos longos em resumos objetivos:

"""
Sumarização de textos longos usando OpenAI.
"""

from openai import OpenAI
import os


class Summarizer:
    """Sumariza textos usando modelos de linguagem."""
    
    def __init__(self):
        self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
    
    def summarize(
        self, texto: str,
        max_sentences: int = 3,
        estilo: str = "informativo"
    ) -> str:
        """
        Gera um resumo do texto fornecido.
        
        Args:
            texto: Texto original para resumir
            max_sentences: Número máximo de frases no resumo
            estilo: Estilo do resumo (informativo, técnico, casual)
            
        Returns:
            Texto resumido
        """
        prompt = f"""Resuma o seguinte texto em no máximo {max_sentences} frases.
Estilo: {estilo}
Idioma: Português brasileiro

Texto:
{texto[:4000]}

Resumo:"""
        
        response = self.client.chat.completions.create(
            model="gpt-4",
            messages=[
                {
                    "role": "system",
                    "content": "Você é um especialista em resumir textos "
                               "de tecnologia de forma precisa e concisa."
                },
                {"role": "user", "content": prompt}
            ],
            max_tokens=300,
            temperature=0.3
        )
        
        return response.choices[0].message.content.strip()
    
    def extract_key_points(self, texto: str) -> list[str]:
        """Extrai pontos-chave de um artigo."""
        prompt = f"""Extraia os 5 pontos-chave mais importantes do texto abaixo.
Retorne como lista numerada.

Texto:
{texto[:4000]}"""
        
        response = self.client.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}],
            max_tokens=400,
            temperature=0.2
        )
        
        content = response.choices[0].message.content.strip()
        return [line.strip() for line in content.split("\n") if line.strip()]

5. Pipeline Completo: Scraper + IA

Agora vamos juntar tudo em um pipeline completo:

"""
Pipeline completo de web scraping inteligente.
Coleta → Extração → Classificação → Sumarização → Armazenamento
"""

import json
from pathlib import Path
from datetime import datetime


class IntelligentScrapingPipeline:
    """Pipeline que combina scraping com análise de IA."""
    
    def __init__(self):
        self.scraper = WebScraper(delay=2.0)
        self.extractor = EntityExtractor()
        self.classifier = ContentClassifier()
        self.summarizer = Summarizer()
        self.output_dir = Path("dados_coletados")
        self.output_dir.mkdir(exist_ok=True)
    
    def process_article(self, url: str, content_selector: str = "article") -> Artigo:
        """
        Processa um artigo completo: scraping + análise de IA.
        
        Args:
            url: URL do artigo
            content_selector: Seletor CSS para o conteúdo principal
            
        Returns:
            Objeto Artigo com todos os campos preenchidos
        """
        # 1. Scraping
        soup = self.scraper.fetch(url)
        
        titulo = soup.find("h1")
        titulo_texto = titulo.get_text(strip=True) if titulo else "Sem título"
        
        texto = self.scraper.extract_text(soup, content_selector)
        
        if not texto:
            logger.warning(f"Nenhum conteúdo encontrado em {url}")
            return Artigo(titulo=titulo_texto, url=url, texto="")
        
        # 2. Extração de entidades
        entidades = self.extractor.extract(texto)
        todas_entidades = []
        for tipo_entidades in entidades.values():
            todas_entidades.extend(tipo_entidades)
        
        # 3. Classificação
        classificacao = self.classifier.classify(texto)
        categoria = classificacao[0]["categoria"] if classificacao else "outros"
        
        # 4. Sumarização
        resumo = self.summarizer.summarize(texto)
        
        artigo = Artigo(
            titulo=titulo_texto,
            url=url,
            texto=texto,
            resumo=resumo,
            categoria=categoria,
            entidades=todas_entidades[:20]
        )
        
        return artigo
    
    def process_and_save(self, url: str, content_selector: str = "article"):
        """Processa artigo e salva resultado em JSON."""
        artigo = self.process_article(url, content_selector)
        
        # Salvar como JSON
        filename = f"{datetime.now():%Y%m%d_%H%M%S}.json"
        filepath = self.output_dir / filename
        
        data = {
            "titulo": artigo.titulo,
            "url": artigo.url,
            "resumo": artigo.resumo,
            "categoria": artigo.categoria,
            "entidades": artigo.entidades,
            "coletado_em": datetime.now().isoformat(),
            "texto_completo": artigo.texto[:5000]
        }
        
        filepath.write_text(
            json.dumps(data, ensure_ascii=False, indent=2),
            encoding="utf-8"
        )
        
        logger.info(f"Artigo salvo em: {filepath}")
        return artigo


# Executar o pipeline
pipeline = IntelligentScrapingPipeline()

6. Scraping com Selenium para Sites Dinâmicos

Para sites que carregam conteúdo via JavaScript:

"""
Scraper para sites com conteúdo dinâmico usando Selenium.
"""

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


class DynamicScraper:
    """Scraper para sites que exigem JavaScript."""
    
    def __init__(self, headless: bool = True):
        options = Options()
        if headless:
            options.add_argument("--headless")
        options.add_argument("--no-sandbox")
        options.add_argument("--disable-dev-shm-usage")
        options.add_argument(
            "user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36"
        )
        
        self.driver = webdriver.Chrome(options=options)
        self.wait = WebDriverWait(self.driver, 15)
    
    def fetch_dynamic(self, url: str, wait_selector: str) -> str:
        """
        Carrega página e espera conteúdo dinâmico renderizar.
        
        Args:
            url: URL para acessar
            wait_selector: Seletor CSS do elemento para aguardar
            
        Returns:
            HTML completo da página após renderização
        """
        self.driver.get(url)
        
        # Esperar o conteúdo aparecer
        self.wait.until(
            EC.presence_of_element_located((By.CSS_SELECTOR, wait_selector))
        )
        
        # Scroll para carregar conteúdo lazy-loaded
        self._scroll_to_bottom()
        
        return self.driver.page_source
    
    def _scroll_to_bottom(self, pause: float = 1.5):
        """Faz scroll até o final da página para carregar conteúdo lazy."""
        import time
        
        last_height = self.driver.execute_script(
            "return document.body.scrollHeight"
        )
        
        while True:
            self.driver.execute_script(
                "window.scrollTo(0, document.body.scrollHeight);"
            )
            time.sleep(pause)
            
            new_height = self.driver.execute_script(
                "return document.body.scrollHeight"
            )
            if new_height == last_height:
                break
            last_height = new_height
    
    def close(self):
        """Fecha o navegador."""
        self.driver.quit()
    
    def __enter__(self):
        return self
    
    def __exit__(self, *args):
        self.close()


# Exemplo de uso
with DynamicScraper(headless=True) as scraper:
    html = scraper.fetch_dynamic(
        "https://example.com/articles",
        wait_selector=".article-card"
    )
    soup = BeautifulSoup(html, "lxml")
    # Processar normalmente com BeautifulSoup...

7. Armazenamento Estruturado com SQLite

"""
Armazenamento dos dados coletados em SQLite.
"""

import sqlite3
from contextlib import contextmanager


class ArticleDatabase:
    """Banco de dados para artigos coletados."""
    
    def __init__(self, db_path: str = "artigos.db"):
        self.db_path = db_path
        self._create_tables()
    
    @contextmanager
    def _get_conn(self):
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        try:
            yield conn
            conn.commit()
        finally:
            conn.close()
    
    def _create_tables(self):
        with self._get_conn() as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS artigos (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    titulo TEXT NOT NULL,
                    url TEXT UNIQUE NOT NULL,
                    texto TEXT,
                    resumo TEXT,
                    categoria TEXT,
                    sentimento TEXT,
                    entidades TEXT,
                    coletado_em TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                )
            """)
    
    def insert(self, artigo: Artigo) -> int:
        with self._get_conn() as conn:
            cursor = conn.execute(
                """INSERT OR REPLACE INTO artigos 
                   (titulo, url, texto, resumo, categoria, entidades)
                   VALUES (?, ?, ?, ?, ?, ?)""",
                (
                    artigo.titulo,
                    artigo.url,
                    artigo.texto,
                    artigo.resumo,
                    artigo.categoria,
                    json.dumps(artigo.entidades, ensure_ascii=False)
                )
            )
            return cursor.lastrowid
    
    def search(self, query: str, limit: int = 10) -> list[dict]:
        with self._get_conn() as conn:
            rows = conn.execute(
                """SELECT * FROM artigos 
                   WHERE titulo LIKE ? OR texto LIKE ?
                   ORDER BY coletado_em DESC LIMIT ?""",
                (f"%{query}%", f"%{query}%", limit)
            ).fetchall()
            return [dict(row) for row in rows]


db = ArticleDatabase()

Conclusão

Neste tutorial, construímos um sistema completo de web scraping inteligente que vai além da extração mecânica de dados. A integração com IA nos permite:

  • Entender o conteúdo extraído usando NER e classificação automática
  • Resumir artigos longos em pontos-chave
  • Categorizar automaticamente o conteúdo sem treinamento manual
  • Adaptar a extração usando compreensão semântica ao invés de seletores frágeis
  • Armazenar dados estruturados prontos para análise

Lembre-se sempre de respeitar os termos de uso dos sites, o arquivo robots.txt e de incluir delays adequados entre as requisições.

Próximos Passos

  • Adicionar sistema de agendamento com APScheduler ou Celery
  • Implementar detecção de duplicatas usando hashing de conteúdo
  • Criar dashboard com Streamlit para visualizar dados coletados
  • Adicionar alertas automáticos para notícias relevantes
  • Implementar proxy rotation para scraping em escala
  • Usar embeddings para busca semântica nos artigos coletados