Files
brasiliano/README.md
T

308 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: <https://xn--2dk.xyz/brasiliana/>
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 <https://brasilianafotografica.bn.gov.br/brasiliana/>
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.