feat: acervo Brasiliana Fotográfica coletado e mural 3D no ar
Captura integral do acervo da Brasiliana Fotográfica (Biblioteca Nacional) para s3://brasiliana: 13.060 itens, 12.985 com imagem de preservação, 13.059 com miniatura derivada, 2.137 cópias legadas e 636 posts do blog. 41.444 objetos no ledger. Os 75 itens sem 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, porque todos têm thumbnail. Não versionado, por viver no bucket ou ser reconstruível: catálogo, posts .jsonl, atlas .webp, sqlite de estado e logs. Credenciais ficam de fora pelo .gitignore.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
/* ===================================================================
|
||||
Reparo do coletor.js -- reprocessa itens que ficaram marcados com
|
||||
erro (ex.: queda de rede no meio da colheita).
|
||||
|
||||
POR QUE PRECISA: coletar() grava {handle, erro} em 'itens' quando
|
||||
uma requisicao falha. Isso conta como "feito" pro loop principal --
|
||||
ele nao tenta de novo sozinho. Falhas de rede (TypeError: Failed to
|
||||
fetch) tambem nao disparam a pausa de 60s (so HTTP 4xx/5xx disparam),
|
||||
entao uma queda pode gerar uma rajada de erros em vez de so alguns.
|
||||
|
||||
COMO USAR
|
||||
1. Rode DEPOIS que await BRAS.status() mostrar fase 'concluido'
|
||||
(pra nao competir por rede com o loop principal).
|
||||
2. Cole este arquivo inteiro no mesmo console (mesma aba/origem).
|
||||
3. Acompanhe pelo proprio console.log -- ele avisa quantos achou
|
||||
e reporta ok/erro no final.
|
||||
4. Se ainda sobrar erro (rede caiu nesse meio-tempo tambem), rode
|
||||
de novo -- e idempotente.
|
||||
=================================================================== */
|
||||
(async () => {
|
||||
const BASE = 'https://brasilianafotografica.bn.gov.br/brasiliana';
|
||||
const PREFIX= '20.500.12156.1';
|
||||
const PAUSA = 2000;
|
||||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
const jit = () => PAUSA + Math.floor(Math.random() * 600);
|
||||
|
||||
const db = await new Promise((res, rej) => {
|
||||
const q = indexedDB.open('bras', 1);
|
||||
q.onsuccess = () => res(q.result); q.onerror = () => rej(q.error);
|
||||
});
|
||||
const get = (st, k) => new Promise(res => {
|
||||
const r = db.transaction(st).objectStore(st).get(k);
|
||||
r.onsuccess = () => res(r.result); r.onerror = () => res(undefined); });
|
||||
const put = (st, k, v) => new Promise((res, rej) => {
|
||||
const t = db.transaction(st, 'readwrite'); t.objectStore(st).put(v, k);
|
||||
t.oncomplete = res; t.onerror = () => rej(t.error); });
|
||||
const getAll = st => new Promise((res, rej) => {
|
||||
const r = db.transaction(st).objectStore(st).getAll();
|
||||
r.onsuccess = () => res(r.result); r.onerror = () => rej(r.error); });
|
||||
|
||||
async function pegar(u) {
|
||||
const r = await fetch(u, {credentials: 'same-origin'});
|
||||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||||
return r.text();
|
||||
}
|
||||
|
||||
function extrair(html, h, com) {
|
||||
const d = new DOMParser().parseFromString(html, 'text/html');
|
||||
const dc = [...d.querySelectorAll('meta[name]')]
|
||||
.filter(m => /^(DC|DCTERMS|citation)/.test(m.name))
|
||||
.map(m => ({name: m.name, value: m.content}));
|
||||
const a = n => (dc.find(x => x.name === n) || {}).value || null;
|
||||
const tb = [...d.querySelectorAll('table.detailtable tr, table.ds-includeSet-table tr')]
|
||||
.map(tr => { const td = [...tr.querySelectorAll('td')].map(c => c.textContent.trim());
|
||||
return td.length >= 2 ? {field: td[0], value: td[1]} : null; })
|
||||
.filter(Boolean);
|
||||
const leg = dc.filter(x => x.name === 'DC.identifier' && /objdigital/.test(x.value || ''))
|
||||
.map(x => x.value)[0] || null;
|
||||
return {
|
||||
handle: `${PREFIX}/${h}`,
|
||||
source_url: `${BASE}/handle/${PREFIX}/${h}?show=full`,
|
||||
fetched_at: new Date().toISOString(),
|
||||
title: a('DC.title'),
|
||||
community: com,
|
||||
preservation_url: a('citation_pdf_url'),
|
||||
legacy_url: leg,
|
||||
rights: (tb.find(t => /^dc\.rights/i.test(t.field)) || {}).value || null,
|
||||
dublin_core: dc,
|
||||
full_table: tb,
|
||||
};
|
||||
}
|
||||
|
||||
const todos = await getAll('itens');
|
||||
const falhados = todos.filter(x => x.erro);
|
||||
console.log(`${falhados.length} itens com erro -- reprocessando`);
|
||||
|
||||
let ok = 0, erro = 0;
|
||||
for (const item of falhados) {
|
||||
const h = item.handle.split('/').pop();
|
||||
const com = await get('fila', h); // recupera o nome da comunidade
|
||||
try {
|
||||
const html = await pegar(`${BASE}/handle/${PREFIX}/${h}?show=full`);
|
||||
await put('itens', h, extrair(html, h, com));
|
||||
ok++;
|
||||
} catch (e) {
|
||||
await put('itens', h, {handle: item.handle, erro: String(e).slice(0, 80)});
|
||||
erro++;
|
||||
if (/HTTP 4|HTTP 5/.test(String(e))) await sleep(60000);
|
||||
}
|
||||
await sleep(jit());
|
||||
}
|
||||
console.log(`\nReparo concluido. ok=${ok} ainda-com-erro=${erro}`);
|
||||
})();
|
||||
Reference in New Issue
Block a user