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.
Brasiliana Fotográfica → acervo arquivístico em S3
Retomando o trabalho? Comece por
tasks/PLANO.md— estado atual, achados que não devem ser redescobertos e runbook.
Captura integral do acervo da Brasiliana Fotográfica
— 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:
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)
- Abra https://brasilianafotografica.bn.gov.br/brasiliana/
- DevTools → Console → cole
pipeline/coletor.jsinteiro BRAS.descobrir()— ~5 min, enumera os 13.036 handlesBRAS.coletar()— o passo longo;BRAS.status()acompanha,BRAS.parar()interrompeBRAS.exportar()— baixabrasiliana-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)
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:
cd pipeline
set -a; . .s3env; set +a
python3 subidor.py --vigiar --catalogo catalogo.jsonl
Fase 4 — cópias legadas (máquina local)
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. As principais:
- 75 itens sem preservação (
errors.md) — depende da origem voltar a servir esses bitstreams; retomar comFALTA3.retomar()na aba. - 30 bitstreams
extra_seqNapagados pelosubidor.pyantigo (que confundia TIFF com texto de licença) precisam ser enumerados item a item comreparo-multibitstream.jsantes de rebaixar. - 30 objetos
(N)com tamanho diferente do gêmeo — não são duplicata, são versões distintas; precisam de conferência individual. - 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.
- 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.