diff --git a/Makefile b/Makefile index a7f7ce2..f899524 100644 --- a/Makefile +++ b/Makefile @@ -91,6 +91,9 @@ scrape_folha: scrape_jornaldaparaiba: docker compose exec scraper python scrape_no_openai.py --platform jornaldaparaiba.com.br +scrape_imirante: + docker compose exec scraper python scrape_no_openai.py --platform imirante.com + # Crawler para todos os portais ou específicos crawl: @@ -124,6 +127,9 @@ crawl_folha: crawl_jornaldaparaiba: docker compose run scraper python crawl.py jornaldaparaiba +crawl_imirante: + docker compose run scraper python crawl.py imirantespider + # Workflow completo de coleta de URLs crawl_all_working: @echo "Executando crawl de todos os portais funcionais..." @@ -136,6 +142,7 @@ crawl_all_working: @make crawl_ig @make crawl_folha @make crawl_jornaldaparaiba + @make crawl_imirante @echo "Crawl de todos os portais concluído!" # Workflow completo de scraping @@ -150,6 +157,7 @@ scrape_all_working: @make scrape_uol @make scrape_folha @make scrape_jornaldaparaiba + @make scrape_imirante @echo "Scraping de todos os portais concluído!" # Pipeline completo: crawl + scrape diff --git a/README.md b/README.md index 1c50248..aca93d7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Check-up +  O Check-up é um projeto de **extração de conteúdo de notícias** de portais brasileiros para análise de desinformação. Este repositório contém ferramentas para coletar e extrair o texto completo de notícias de diversos portais de notícia do Brasil. @@ -15,6 +16,7 @@ Este projeto foi adaptado a partir de uma ferramenta original do [**Aos Fatos**] ### 🏗️ Arquitetura A ferramenta possui dois módulos principais: + - **Crawler (Spiders)**: Coleta URLs de notícias das páginas iniciais dos portais - **Extrator (Plays)**: Acessa cada notícia e extrai o conteúdo completo @@ -25,16 +27,20 @@ O código pode ser facilmente adaptado para novos portais de notícia, seguindo ## 🚀 Como Rodar o Projeto ### 📋 Pré-requisitos + - Docker e Docker Compose instalados - Make (disponível por padrão no macOS e Linux) ### 🔧 Configuração Inicial **1. Setup completo do ambiente:** + ```bash make setup ``` + Este comando faz toda a configuração inicial: + - Cria o arquivo `.env` a partir do `env.example` - Inicia os serviços Docker (PostgreSQL, MinIO, scraper) - Aguarda o banco de dados ficar pronto @@ -48,14 +54,16 @@ O processo de extração acontece em duas etapas principais: **🕷️ Etapa 1: Crawling (Coleta de URLs de Notícias)** Colete URLs de notícias de todos os portais funcionais: + ```bash make crawl_all_working ``` Ou execute individualmente: + ```bash make crawl_metropoles # Portal Metrópoles -make crawl_veja # Portal Veja +make crawl_veja # Portal Veja make crawl_r7 # Portal R7 make crawl_uol # Portal UOL make crawl_maisgoias # Portal MaisGoiás @@ -67,11 +75,13 @@ make crawl_folha # Portal Folha **📰 Etapa 2: Scraping (Extração de Conteúdo das Notícias)** Execute a extração de conteúdo de todos os portais: + ```bash make scrape_all_working ``` Ou execute por portal específico: + ```bash make scrape_metropoles # Extração do Metrópoles make scrape_maisgoias # Extração do MaisGoiás @@ -86,6 +96,7 @@ make scrape_folha # Extração da Folha ### 📋 Conteúdo Extraído Para cada notícia, o sistema extrai: + - **Título**: Título principal da notícia - **Descrição**: Subtítulo ou resumo da notícia - **Corpo**: Texto completo da matéria @@ -94,11 +105,13 @@ Para cada notícia, o sistema extrai: ### ⚡ Workflows Automatizados **Pipeline completo (crawl + scraping):** + ```bash make pipeline_complete ``` **Coleta otimizada:** + ```bash make collect_working ``` @@ -106,21 +119,25 @@ make collect_working ### 🛠️ Comandos Úteis **Acessar o container:** + ```bash make bash ``` **Ver logs dos serviços:** + ```bash docker compose logs -f ``` **Parar serviços:** + ```bash make stop ``` **Ver todos os comandos disponíveis:** + ```bash make help ``` @@ -128,13 +145,14 @@ make help ### � Onde os Dados São Salvos - **URLs coletadas**: Salvos no banco PostgreSQL (tabela `URLQueue`) -- **Notícias extraídas**: Salvos no banco PostgreSQL (tabela `Entry`) com título, descrição, corpo e tags +- **Notícias extraídas**: Salvos no banco PostgreSQL (tabela `Entry`) com título, descrição, corpo e tags - **Arquivos temporários**: Salvos no MinIO (acessível em `http://localhost:9001`) - **Logs**: Disponíveis via `docker compose logs` ### � Configurações Importantes **Variáveis de ambiente (arquivo `.env`):** + - Credenciais dos portais (se necessário) - Configurações do banco PostgreSQL - Configurações do MinIO para armazenamento @@ -147,16 +165,19 @@ make help Este projeto coleta dados dos seguintes portais brasileiros: ### ✅ **Portais Funcionais** (Totalmente operacionais) + - **[Metrópoles](https://www.metropoles.com)** - `make crawl_metropoles` / `make scrape_metropoles` -- **[IG](https://www.ig.com.br)** - `make crawl_ig` / `make scrape_ig` +- **[IG](https://www.ig.com.br)** - `make crawl_ig` / `make scrape_ig` - **[MaisGoiás](https://www.maisgoias.com.br)** - `make crawl_maisgoias` / `make scrape_maisgoias` - **[AliadosBrasil](https://www.aliadosbrasiloficial.com.br)** - `make crawl_aliadosBrasil` / `make scrape_aliadosbrasil` - **[Veja](https://veja.abril.com.br)** - `make crawl_veja` / `make scrape_veja` - **[R7](https://www.r7.com)** - `make crawl_r7` / `make scrape_r7` - **[UOL](https://www.uol.com.br)** - `make crawl_uol` / `make scrape_uol` - **[Folha](https://www.folha.uol.com.br)** - `make crawl_folha` / `make scrape_folha` +- **[Imirante](https://imirante.com)** - `make crawl_imirante` / `make scrape_imirante` ### 🔧 **Em Desenvolvimento** + - **[Estadão](https://www.estadao.com.br)** - Necessita ajustes nos seletores CSS - **[Globo](https://oglobo.globo.com/)** - Requer configurações específicas de autenticação - **[RBS](https://www.clicrbs.com.br)** - Spider em desenvolvimento @@ -188,6 +209,7 @@ Quer contribuir para o projeto? Consulte nosso **[Guia de Contribuição](./.git ### Problemas Comuns **1. Erro "Playwright browsers not installed"** + ```bash # Se o IG spider falhar, reconstrua a imagem Docker: docker compose down @@ -196,6 +218,7 @@ make start ``` **2. Container não conecta ao banco** + ```bash # Aguarde o banco ficar pronto: make wait-for-db @@ -204,6 +227,7 @@ make stop && make start ``` **3. MinIO não acessível** + ```bash # Verifique se o MinIO está rodando: docker compose ps @@ -211,6 +235,7 @@ docker compose ps ``` **4. Containers órfãos** + ```bash # Limpe containers órfãos: docker compose down --remove-orphans @@ -220,6 +245,7 @@ make prune ### 💡 Exemplos Práticos **Coleta rápida de um portal específico:** + ```bash # Setup inicial (só uma vez) make setup @@ -230,21 +256,24 @@ make scrape_metropoles ``` **Pipeline completo para produção:** + ```bash # Coleta de todos os portais funcionais make pipeline_complete ``` **Monitoramento em tempo real:** + ```bash # Terminal 1 - Execute a coleta make crawl_all_working -# Terminal 2 - Monitore os logs +# Terminal 2 - Monitore os logs docker compose logs -f scraper ``` **Acesso aos dados coletados:** + ```bash # Via container make bash @@ -259,6 +288,7 @@ open http://localhost:9001 ``` ### 2- Executar as migrações do banco de dados + Para executar as migrações do banco de dados, utilize: `make migrate_db` @@ -266,6 +296,7 @@ Para executar as migrações do banco de dados, utilize: Este comando irá aplicar todas as migrações pendentes no banco de dados. ### 3- Coletar URL de notícias + O primeiro passo é coletar URLs de notícias nas páginas iniciais dos portais. Cada portal possui um "spider" implementado com a biblioteca Scrapy, localizado no diretório `spiders/`. Exemplo de script para a [Folha]("https://www.folha.uol.com.br"): `spiders/folha.py`. @@ -285,6 +316,7 @@ Para executar a extração de todas as notícias: ### 5- Adicionar um Novo Portal Para adicionar um novo portal, consulte o **[TUTORIAL_SPIDERS_PLAYS.md](./TUTORIAL_SPIDERS_PLAYS.md)** que contém instruções detalhadas sobre: + - Como criar spiders para coleta de URLs - Como criar plays para extração de conteúdo - Exemplos práticos e boas práticas @@ -295,15 +327,15 @@ Para adicionar um novo portal, consulte o **[TUTORIAL_SPIDERS_PLAYS.md](./TUTORI Os dados extraídos são armazenados no banco PostgreSQL e arquivos temporários no MinIO. Para configurações específicas de armazenamento, consulte o arquivo `.env`. ## Importante -Os scripts dependem da estrutura HTML dos portais e podem precisar de ajustes após atualizações nos sites. +Os scripts dependem da estrutura HTML dos portais e podem precisar de ajustes após atualizações nos sites. ## Executando um spider específico Para executar apenas um spider específico, você pode passar o nome do spider como argumento: ``` - docker compose run scraper python crawl.py metropolesspider + docker compose run scraper python crawl.py metropolesspider ``` Para verificar o banco de dados, você pode executar o seguinte comando: @@ -335,4 +367,4 @@ make clean # Ver todos os comandos disponíveis make help -``` \ No newline at end of file +``` diff --git a/plays/__init__.py b/plays/__init__.py index 8189816..af1f1b0 100644 --- a/plays/__init__.py +++ b/plays/__init__.py @@ -2,6 +2,7 @@ from plays.folha import FolhaPlay from plays.globo import GloboPlay from plays.ig import IGPlay +from plays.imirante import ImirantePlay from plays.metropoles import MetropolesPlay from plays.r7 import R7Play from plays.rbs import ClicRBSPlay @@ -19,6 +20,7 @@ FolhaPlay, GloboPlay, IGPlay, + ImirantePlay, MetropolesPlay, R7Play, TerraPlay, diff --git a/plays/imirante.py b/plays/imirante.py new file mode 100644 index 0000000..dcdfb3d --- /dev/null +++ b/plays/imirante.py @@ -0,0 +1,204 @@ +import time +from playwright.sync_api import sync_playwright +from plays.base import BasePlay +from plays.items import EntryItem +from plog import logger + +class ImirantePlay(BasePlay): + name = "imirante" + + @classmethod + def match(cls, url): + return "imirante.com" in url + + def pre_run(self): + pass + + def run(self) -> EntryItem: + with sync_playwright() as p: + browser = self.launch_browser(p, viewport={"width": 1920, "height": 1080}) + page = browser.new_page() + logger.info(f"[{self.name}] Opening URL '{self.url}'...") + page.goto(self.url, timeout=180_000) + page.wait_for_selector("h1", timeout=30000) + title = self._extract_title(page) + description = self._extract_description(page) + body = self._extract_body(page) + tags = self._extract_tags(page) + + logger.info(f"[{self.name}] Extração concluída:") + logger.info(f" - Título: {'✓' if title else '✗'}") + logger.info(f" - Descrição: {'✓' if description else '✗'}") + logger.info(f" - Corpo: {'✓' if body else '✗'} ({len(body) if body else 0} chars)") + logger.info(f" - Tags: {'✓' if tags else '✗'} ({len(tags) if tags else 0} tags)") + + return EntryItem( + title=title, + url=self.url, + description=description, + body=body, + tags=tags, + ) + + def _extract_title(self, page): + title_selectors = [ + "h1", + ".article-title h1", + ".news-title h1", + ".post-title h1", + "header h1" + ] + + for selector in title_selectors: + try: + element = page.locator(selector) + if element.count() > 0: + title = element.first.inner_text().strip() + if title: + logger.info(f"[{self.name}] Título extraído com seletor: {selector}") + return title + except Exception as e: + logger.warning(f"[{self.name}] Erro ao extrair título com {selector}: {str(e)}") + continue + + try: + title = page.title().strip() + if " - Imirante" in title: + title = title.replace(" - Imirante", "").strip() + if title: + logger.info(f"[{self.name}] Título extraído do