O item de referência de examples/ (4,8 MB, com a imagem de preservação
inteira) duplicava o que já está no bucket em itens/20.500.12156.1_10007/.
robots.mp4 não tem relação com a coleta.
Ambos continuam recuperáveis em 290c4cc.
317 lines
14 KiB
Markdown
317 lines
14 KiB
Markdown
# 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.985 |
|
||
| 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.444** |
|
||
| Peças no mural 3D | 13.059 |
|
||
|
||
Os **75 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)
|
||
TODO.md lista de trabalho da sessão corrente
|
||
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
|
||
|
||
Lista completa e com contexto em [`tasks/TODO.md`](tasks/TODO.md). As principais:
|
||
|
||
1. **75 itens sem preservação** ([`errors.md`](errors.md)) — depende da origem
|
||
voltar a servir esses bitstreams; retomar com `FALTA3.retomar()` na aba.
|
||
2. **30 bitstreams `extra_seqN`** apagados pelo `subidor.py` antigo (que
|
||
confundia TIFF com texto de licença) precisam ser enumerados item a item
|
||
com `reparo-multibitstream.js` antes de rebaixar.
|
||
3. **30 objetos `(N)` com tamanho diferente do gêmeo** — não são duplicata,
|
||
são versões distintas; precisam de conferência individual.
|
||
4. **Reconciliar 13.058 × 13.060** — o MANIFEST fala em 13.058 itens reais e o
|
||
catálogo tem 13.060 handles distintos; a diferença nunca foi explicada.
|
||
5. **PDFs de álbum** no objdigital são objetos distintos e precisam de decisão
|
||
própria — o de 71,9 MB do item 14582 provavelmente não é caso isolado.
|
||
|
||
## 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.
|