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.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: 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)

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)
    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.

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.

S
Description
No description provided
Readme
13 MiB
Languages
HTML 35.4%
Python 34%
JavaScript 29.9%
Shell 0.7%