# Brasiliana Fotográfica → acervo arquivístico em S3 > **Retomando o trabalho?** Comece por [`tasks/PLANO.md`](tasks/PLANO.md) — estado atual, achados que não devem ser redescobertos e runbook. Captura integral do acervo da [Brasiliana Fotográfica](https://brasilianafotografica.bn.gov.br/) — **13.060 itens de 16 instituições** — para o bucket `s3://brasiliana` (Hetzner Object Storage, `hel1.your-objectstorage.com`). Mural 3D do acervo no ar: A Biblioteca Nacional foi consultada: confirmou que o material é público e que não tem equipe técnica para fornecer um dump. A coleta por scraping é, portanto, o caminho combinado. Todo o desenho abaixo assume ritmo cortês e identificação. --- ## Estado atual **Coleta concluída em 2026-09-13.** Medido contra catálogo × ledger de upload: | | | |---|--:| | Itens no catálogo | 13.060 | | Com imagem de preservação | 12.991 | | Com thumbnail do DSpace | 13.059 | | Com miniatura derivada (`derivados/thumb256/`) | 13.059 | | Cópia legada (objdigital) | 2.137 | | Posts do blog WordPress | 636 | | **Objetos no bucket (ledger)** | **41.450** | | Peças no mural 3D | 13.059 | Os **69 itens sem imagem de preservação** estão em [`errors.md`](errors.md): a página do item responde `200`, mas a origem serve o bitstream com `HTTP 500`. Nenhum fica fora do mural — todos têm thumbnail. A fila local está zerada: tudo que foi baixado está no bucket. --- ## O acervo DSpace 4.2, handles com prefixo `20.500.12156.1`. **13.036 itens**, conferidos comunidade a comunidade — a soma das 15 fecha exatamente com o total da raiz, sem sobra nem duplicata. | Instituição | Itens | | Instituição | Itens | |---|--:|---|---|--:| | Instituto Moreira Salles | 5.133 | | Fiocruz | 548 | | Biblioteca Nacional | 2.726 | | Marinha (DPHDM) | 456 | | Museu Aeroespacial | 1.143 | | Arquivo Nacional | 383 | | Museu da República | 906 | | Arq. Geral da Cidade do RJ | 248 | | Museu Histórico Nacional | 649 | | Leibniz-Institut, Leipzig | 216 | | IHGB | 206 | | Fund. Joaquim Nabuco | 190 | | Museu Goeldi | 95 | | FGV CPDOC | 79 | | IBGE | 58 | | | | **Espaço projetado: ~116 GB** (média de 9,08 MB em 6 arquivos medidos × 13.036). Mediana 3,99 MB dá ~51 GB; a distribuição tem cauda pesada, então a média é o estimador certo para o total. Amostra pequena — confirmar com `sizes`. --- ## Cinco descobertas que definem o desenho ### 1. O sitemap está sete anos desatualizado `htmlmap`/`sitemap` foram gerados em **abril de 2019** e listam ~5.300 itens. Quem enumerar por eles — o caminho óbvio, e o que ferramentas de mirror usam — perde **59% do acervo** sem erro aparente: o crawl termina limpo, pela metade. Fonte correta: o índice de navegação, construído ao vivo. `browse?type=title&rpp=100&offset=N`, percorrido por comunidade (o que de quebra etiqueta a instituição de origem, que a página do item não expõe de forma confiável). ### 2. O arquivo de preservação está no DSpace, não no objdigital O campo `DC.identifier` aponta para `objdigital.bn.br`, e é natural supor que ali esteja o master. **Medido, é o contrário:** | Item 6066 | Resolução | Peso | |---|---|---| | Bitstream do DSpace | 5533×4065 (22 MP) | 7,39 MB | | Cópia no objdigital | ~2600×2100 (5,5 MP) | 147 KB | O maior bitstream medido chega a **8679×6484 (34,7 MB)**. O EXIF mostra digitalização em Hasselblad H3D II-39MS a 7216×5412 — preservação de verdade. O PDF de 71,9 MB que aparece no objdigital do item 14582 **não** é a mesma imagem em melhor qualidade: é outro objeto, um álbum de várias páginas. > **Regra de captura:** o bitstream do DSpace é o arquivo de preservação e vai > sempre. A cópia do objdigital entra como procedência, em `legado/`, nunca > como substituta. Inverter isso arquiva o acervo a um quarto da resolução, e > nada no site avisa. ### 3. O Cloudflare bloqueia automação de navegador, não IP nem geografia Medido na mesma máquina, mesmo IP, mesmo instante: | Cliente | Páginas HTML | |---|---| | Chrome do usuário | ✅ 200 | | `fetch()` de dentro da página | ✅ 200 (>100 requisições seguidas) | | Playwright *headed* | ❌ 200 na primeira, 403 depois | | Playwright *headless* | ❌ 403 | | curl / httpx | ❌ 403 | | Servidor na Finlândia | ❌ 403 | Não é reputação de IP: o Chrome comum carregava normalmente enquanto o Playwright levava 403 na mesma rede. É detecção de controle remoto (CDP). **Consequência:** proxies não ajudam — o bloqueio dispara na segunda requisição da mesma sessão, não por origem. Testado e descartado. ### 4. Bitstreams passam por curl; páginas HTML não Curioso e muito útil: `bitstream/...` responde 200 a `curl -L` simples, mesmo com UA de curl. Só as páginas HTML são desafiadas. Isso permite baixar os 116 GB fora do navegador — inclusive de um servidor. ### 5. Nomes de arquivo com percent-encoding levam 403 Aqui mora a pendência. Testado com cinco user-agents (Chrome, Safari, Firefox, Googlebot, curl): **todos 403** numa URL com `%28`/`%20`, enquanto `curl/8.7.1` puro dá **200** numa URL de nome simples. O gatilho é o caminho, não o cliente — variar user-agent não resolve. | Arquivo | curl | navegador | |---|---|---| | `imagem.jpg` | ✅ 200 | ✅ 200 | | `LB.001%288%29.dvft.JPG` | ❌ 403 | ✅ 200 | | `GV%20dvft%20...Get%c3%balio...JPG` | ❌ 403 | ✅ 200 | Itens da Biblioteca Nacional usam `imagem.jpg`; os do IMS usam nomes descritivos com espaços e acentos. Como o IMS sozinho são 5.133 itens, a fração afetada é grande — mas **o número exato sai do catálogo, sem nenhuma requisição extra**: basta contar quantos `preservation_url` precisam de encoding. --- ## Arquitetura: duas trilhas ``` páginas HTML (13.036) arquivos (116 GB) │ │ coletor.js ──────────────┘ │ (Chrome real, in-page fetch) │ → catalogo.jsonl │ ┌────────────────┴────────────────┐ │ │ nome simples │ nome com encoding │ │ TRILHA A ▼ TRILHA B ▼ baixador.py na Finlândia navegador na máquina local curl → sha256 → S3 → apaga fetch → download → S3 → apaga 4,7 MB/s, mesmo datacenter (uploader por construir) ``` A cópia legada do `objdigital` **não é alcançável da Finlândia** (conexão morta) — essas rodam na máquina local, com `--somente-legado`. São ~328 KB em média, ~1,5 GB no total. **Nada acumula em disco.** Cada arquivo é baixado, somado, enviado e apagado; pico de uso local é um arquivo. O `baixador.py` aborta se o disco livre cair abaixo de 2 GB (a Finlândia tem só 31 GB). --- ## Runbook Todos os comandos abaixo assumem `cd pipeline/` primeiro — é onde vivem os scripts, o catálogo e o estado (`.sqlite`, `.log`, `.tmp/`). ### Fase 1 — metadados (navegador, ~7 h) 1. Abra 2. DevTools → Console → cole `pipeline/coletor.js` inteiro 3. `BRAS.descobrir()` — ~5 min, enumera os 13.036 handles 4. `BRAS.coletar()` — o passo longo; `BRAS.status()` acompanha, `BRAS.parar()` interrompe 5. `BRAS.exportar()` — baixa `brasiliana-catalogo-AAAA-MM-DD.jsonl` O estado fica em `localStorage`: pode fechar e retomar colando o arquivo de novo. ### Fase 2 — arquivos (trilha A, servidor) ```bash scp pipeline/baixador.py pipeline/catalogo.jsonl finland:/root/brasiliana/ ssh finland set -a; . /root/.s3env; set +a cd /root/brasiliana python3 baixador.py --catalogo catalogo.jsonl --pular-legado python3 baixador.py --status ``` ### Fase 3 — trilha B, arquivos com encoding (máquina local) Na mesma aba do coletor, cole `pipeline/baixador-navegador.js`, depois: ``` BAIXA.quantos() // quantos precisam desta trilha BAIXA.iniciar() // os arquivos caem em ~/Downloads ``` Em outro terminal, em paralelo — sobe cada arquivo e apaga na hora: ```bash cd pipeline set -a; . .s3env; set +a python3 subidor.py --vigiar --catalogo catalogo.jsonl ``` ### Fase 4 — cópias legadas (máquina local) ```bash cd pipeline set -a; . .s3env; set +a python3 baixador.py --catalogo catalogo.jsonl --somente-legado ``` --- ## Estrutura no bucket ``` s3://brasiliana/ MANIFEST.md o que é, como foi coletado, quando catalogo.jsonl uma linha por item wordpress-posts.jsonl uma linha por post do blog falhas-500-2026-09-13.txt itens que a origem não serve itens/20.500.12156.1_6066/ metadata.json Dublin Core completo + procedência preservacao/imagem.jpg 5533×4065, 7,4 MB thumbnail/thumb_2.jpg derivado do DSpace (721–3.709 px) legado/icon843660.jpg 147 KB, referência de origem manifest-sha256.txt checksum de cada arquivo do item derivados/thumb256/ 20.500.12156.1_6066.webp miniatura gerada por nós, 256 px ``` `itens/` é material de origem; `derivados/` é o que geramos e pode ser reconstruído do zero. A separação é proposital — misturar apagaria a procedência que o resto do manifesto garante. Dois pontos não negociáveis: **SHA-256 de tudo** (prova de integridade) e **URL de origem + data/hora UTC de captura** em cada `metadata.json` (procedência). Sem os dois é uma pasta de imagens, não um arquivo. O formato é compatível com BagIt se um dia for depositado em outro repositório. --- ## Estrutura do repositório ``` brasiliano/ README.md este arquivo tasks/ retomada de sessão e plano de trabalho PLANO.md estado atual, achados, runbook (comece por aqui) tasks.md todas as tarefas abertas, numa lista só TODO.md notas técnicas para retomar o trabalho PROMPT_TERMINAL.md runbook original (parcialmente superado, ver PLANO.md) pipeline/ scripts ativos + estado em disco (catálogo, .sqlite, .log, .tmp/, credenciais) coletor.js Fase 1 — metadados, console do navegador coletor-resume.js idem, com resume + reparo juntos coletor-reparo.js reprocessa itens marcados com erro coletor-chrome.js trilha paralela (2ª aba/navegador) baixador-navegador.js Fase 3 — trilha B, baixa no navegador p/ ~/Downloads baixador-thumb.js baixa miniaturas baixador-extras.js baixa bitstreams extras de itens com múltiplas imagens reparo-multibitstream.js detecta itens com mais de um bitstream real baixador.py Fases 2 e 4 — curl → sha256 → S3, um arquivo por vez subidor.py Fase 3 — Downloads → S3, um por vez (APOSENTADO, ver TODO) subidor-paralelo.py substituto: boto3 + threads, ~19 GB/h; valida por assinatura (JPEG/PNG/TIFF/BMP/GIF/JP2/WebP) e nunca apaga o que recusa — manda pra rejeitados/ rodar-paralelo.sh drena a fila, limpa duplicatas, regrava manifests vigia-30min.sh a cada 30 min sobe o que chegar e apaga o que subiu baixar-faltantes-v3.js console do navegador: baixa os itens que faltam falhas-500-2026-09-13.txt os 75 que a origem não serve (ver errors.md) catalogo.jsonl catálogo corrente, uma linha por item catalogo-teste.jsonl dois itens, teste de fumaça .env, .s3env, .s3env.exemplo credenciais AWS (reais e modelo) *.sqlite, *.log, .tmp/ estado e logs dos scripts acima — não versionar web/ visualizações do acervo wall.html mural 3D wall2.html linha do tempo build-atlas.js gera atlas/; use --thumbs=DIR com derivados/thumb256/ atlas/ atlas-0.webp + atlas-map.json (saída do build) archive/ censo.py versão anterior (Playwright) — não funciona, mantida pelo parsing ``` ## Arquivos deste projeto | Arquivo | O quê | |---|---| | `pipeline/coletor.js` | Coletor de metadados; roda no console do Chrome | | `pipeline/baixador.py` | Baixa arquivos e deposita no S3, um por vez | | `pipeline/baixador-navegador.js` | Trilha B: baixa no navegador os arquivos que o curl não alcança | | `pipeline/subidor.py` | Trilha B: leva da pasta Downloads para o S3 e apaga | | `archive/censo.py` | Versão anterior, transporte Playwright — **não funciona**, mantida pelo parsing e pelo modelo de dados | | `pipeline/.s3env.exemplo` | Modelo de credenciais; preencher as chaves AWS | | `pipeline/catalogo-teste.jsonl` | Dois itens para teste de fumaça | --- ## Pendências Todas as tarefas abertas, organizadas por área (descrição NOBRADE, mural, acervo no bucket) e por quem destrava cada uma, estão em [`tasks/tasks.md`](tasks/tasks.md). ## Direitos Acervo majoritariamente em domínio público por antiguidade (fotografia do séc. XIX e início do XX), mas as 16 instituições têm políticas distintas. O campo `dc.rights` e a instituição de origem são preservados **por item** — a decisão de redistribuir é item a item, não do acervo em bloco. Arquivar para si é uma coisa; republicar é outra.