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.
94 lines
4.0 KiB
JavaScript
94 lines
4.0 KiB
JavaScript
/* ===================================================================
|
|
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}`);
|
|
})();
|