mirror of
https://github.com/catdevnull/transicion-desordenada-diablo
synced 2024-11-26 19:26:18 +00:00
Compare commits
No commits in common. "b7499e8106b8b73b5a7a708441fed834e8cceba9" and "14b94d53dfddcca306ae0327080dbc7763e49c56" have entirely different histories.
b7499e8106
...
14b94d53df
7 changed files with 32 additions and 177 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,4 +4,3 @@ log
|
||||||
prueba
|
prueba
|
||||||
datos.gob.ar*
|
datos.gob.ar*
|
||||||
data/
|
data/
|
||||||
*.zip
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
FROM docker.io/alpine:3.18 as build
|
FROM docker.io/alpine:3.18 as build
|
||||||
RUN apk add --no-cache npm
|
RUN apk add --no-cache npm esbuild
|
||||||
RUN npm install -g esbuild
|
RUN npm install -g esbuild
|
||||||
WORKDIR /tmp/build
|
WORKDIR /tmp/build
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ COPY package.json .
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
COPY download_json.js .
|
COPY download_json.js .
|
||||||
RUN esbuild --bundle --format=cjs --platform=node --outfile=build.js --sourcemap=inline download_json.js
|
RUN esbuild --bundle --format=cjs --platform=node --outfile=build.js download_json.js
|
||||||
|
|
||||||
FROM docker.io/alpine:3.18
|
FROM docker.io/alpine:3.18
|
||||||
RUN apk add --no-cache nodejs-current tini
|
RUN apk add --no-cache nodejs-current tini
|
||||||
|
@ -16,4 +16,4 @@ COPY pki/ca_intermediate_root_bundle.pem /usr/lib/ca_intermediate_root_bundle.pe
|
||||||
COPY --from=build /tmp/build/build.js /usr/local/bin/download_json.js
|
COPY --from=build /tmp/build/build.js /usr/local/bin/download_json.js
|
||||||
ENV NODE_EXTRA_CA_CERTS=/usr/lib/ca_intermediate_root_bundle.pem
|
ENV NODE_EXTRA_CA_CERTS=/usr/lib/ca_intermediate_root_bundle.pem
|
||||||
WORKDIR /data
|
WORKDIR /data
|
||||||
CMD ["/sbin/tini", "node", "--enable-source-maps", "/usr/local/bin/download_json.js"]
|
CMD ["/sbin/tini", "node", "/usr/local/bin/download_json.js", "https://datos.gob.ar/data.json", "http://datos.energia.gob.ar/data.json", "https://datos.magyp.gob.ar/data.json", "https://datos.acumar.gov.ar/data.json", "https://datasets.datos.mincyt.gob.ar/data.json", "https://datos.arsat.com.ar/data.json", "https://datos.cultura.gob.ar/data.json", "https://datos.mininterior.gob.ar/data.json", "https://datos.produccion.gob.ar/data.json", "https://datos.salud.gob.ar/data.json", "https://datos.transporte.gob.ar/data.json", "https://ckan.ciudaddemendoza.gov.ar/data.json", "https://datos.santafe.gob.ar/data.json", "https://datosabiertos.chaco.gob.ar/data.json", "https://datosabiertos.gualeguaychu.gov.ar/data.json", "https://datosabiertos.mercedes.gob.ar/data.json", "http://luj-bue-datos.paisdigital.innovacion.gob.ar/data.json", "https://datosabiertos.desarrollosocial.gob.ar", "http://datos.mindef.gov.ar/data.json"]
|
||||||
|
|
140
download_json.js
140
download_json.js
|
@ -4,66 +4,13 @@ import { Agent, fetch, request, setGlobalDispatcher } from "undici";
|
||||||
import { join, normalize } from "node:path";
|
import { join, normalize } from "node:path";
|
||||||
import pLimit from "p-limit";
|
import pLimit from "p-limit";
|
||||||
|
|
||||||
const sitiosPorDefecto = [
|
|
||||||
"https://datos.gob.ar/data.json",
|
|
||||||
"http://datos.energia.gob.ar/data.json",
|
|
||||||
"https://datos.magyp.gob.ar/data.json",
|
|
||||||
"https://datos.acumar.gov.ar/data.json",
|
|
||||||
"https://datasets.datos.mincyt.gob.ar/data.json",
|
|
||||||
"https://datos.arsat.com.ar/data.json",
|
|
||||||
"https://datos.cultura.gob.ar/data.json",
|
|
||||||
"https://datos.mininterior.gob.ar/data.json",
|
|
||||||
"https://datos.produccion.gob.ar/data.json",
|
|
||||||
"https://datos.salud.gob.ar/data.json",
|
|
||||||
"https://datos.transporte.gob.ar/data.json",
|
|
||||||
"https://ckan.ciudaddemendoza.gov.ar/data.json",
|
|
||||||
"https://datos.santafe.gob.ar/data.json",
|
|
||||||
"https://datosabiertos.chaco.gob.ar/data.json",
|
|
||||||
"https://datosabiertos.mercedes.gob.ar/data.json",
|
|
||||||
"http://luj-bue-datos.paisdigital.innovacion.gob.ar/data.json",
|
|
||||||
"https://datosabiertos.desarrollosocial.gob.ar/data.json",
|
|
||||||
"http://datos.mindef.gov.ar/data.json",
|
|
||||||
|
|
||||||
"https://monitoreo.datos.gob.ar/catalog/jgm/data.json",
|
|
||||||
// 'https://datosabiertos.enacom.gob.ar/data.json',
|
|
||||||
"https://monitoreo.datos.gob.ar/catalog/otros/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/catalog/aaip/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/media/catalog/sedronar/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/catalog/modernizacion/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/media/catalog/shn/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/catalog/smn/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/catalog/ign/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/catalog/justicia/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/catalog/seguridad/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/media/catalog/ambiente/data.json",
|
|
||||||
// "http://andino.siu.edu.ar/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/catalog/educacion/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/media/catalog/inti/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/catalog/ssprys/data.json",
|
|
||||||
"https://www.presupuestoabierto.gob.ar/sici/rest-api/catalog/public",
|
|
||||||
"https://transparencia.enargas.gob.ar/data.json",
|
|
||||||
"https://infra.datos.gob.ar/catalog/sspm/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/catalog/ssprys/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/catalog/siep/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/catalog/exterior/data.json",
|
|
||||||
"http://datos.pami.org.ar/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/media/catalog/trabajo/data.json",
|
|
||||||
"https://datos.yvera.gob.ar/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/media/catalog/renaper/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/media/catalog/dine/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/media/catalog/obras/data.json",
|
|
||||||
"https://monitoreo.datos.gob.ar/media/catalog/generos/data.json",
|
|
||||||
];
|
|
||||||
|
|
||||||
// desactivado porque va MUY lento: datosabiertos.gualeguaychu.gov.ar
|
|
||||||
|
|
||||||
// FYI: al menos los siguientes dominios no tienen la cadena completa de certificados en HTTPS. tenemos que usar un hack (node_extra_ca_certs_mozilla_bundle) para conectarnos a estos sitios. (se puede ver con ssllabs.com) ojalá lxs administradorxs de estos servidores lo arreglen.
|
// FYI: al menos los siguientes dominios no tienen la cadena completa de certificados en HTTPS. tenemos que usar un hack (node_extra_ca_certs_mozilla_bundle) para conectarnos a estos sitios. (se puede ver con ssllabs.com) ojalá lxs administradorxs de estos servidores lo arreglen.
|
||||||
// www.enargas.gov.ar, transparencia.enargas.gov.ar, www.energia.gob.ar, www.economia.gob.ar, datos.yvera.gob.ar
|
// www.enargas.gov.ar, transparencia.enargas.gov.ar, www.energia.gob.ar, www.economia.gob.ar, datos.yvera.gob.ar
|
||||||
|
|
||||||
setGlobalDispatcher(
|
setGlobalDispatcher(
|
||||||
new Agent({
|
new Agent({
|
||||||
pipelining: 0,
|
pipelining: 0,
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
/** key es host
|
/** key es host
|
||||||
|
@ -81,14 +28,14 @@ class StatusCodeError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class TooManyRedirectsError extends Error {}
|
class TooManyRedirectsError extends Error {}
|
||||||
let jsonUrls = process.argv.slice(2);
|
const jsonUrls = process.argv.slice(2);
|
||||||
if (jsonUrls.length < 1) {
|
if (jsonUrls.length < 1) {
|
||||||
jsonUrls = sitiosPorDefecto;
|
console.error("Especificamente el url al json porfa");
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
await writeFile("readme.txt", generateReadme(jsonUrls));
|
|
||||||
for (const url of jsonUrls)
|
for (const url of jsonUrls)
|
||||||
downloadFromData(url).catch((error) =>
|
downloadFromData(url).catch((error) =>
|
||||||
console.error(`${url} FALLÓ CON`, error),
|
console.error(`${url} FALLÓ CON`, error)
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -96,7 +43,7 @@ for (const url of jsonUrls)
|
||||||
*/
|
*/
|
||||||
async function downloadFromData(jsonUrlString) {
|
async function downloadFromData(jsonUrlString) {
|
||||||
const jsonUrl = new URL(jsonUrlString);
|
const jsonUrl = new URL(jsonUrlString);
|
||||||
const outputPath = `${jsonUrl.host}${jsonUrl.pathname}`.replaceAll("/", "_");
|
const outputPath = jsonUrl.host;
|
||||||
await mkdir(outputPath, { recursive: true });
|
await mkdir(outputPath, { recursive: true });
|
||||||
const errorFile = (
|
const errorFile = (
|
||||||
await open(join(outputPath, "errors.jsonl"), "w")
|
await open(join(outputPath, "errors.jsonl"), "w")
|
||||||
|
@ -117,7 +64,7 @@ async function downloadFromData(jsonUrlString) {
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorFile.write(
|
errorFile.write(
|
||||||
JSON.stringify(encodeError({ dataset, dist }, error)) + "\n",
|
JSON.stringify(encodeError({ dataset, dist }, error)) + "\n"
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -128,14 +75,14 @@ async function downloadFromData(jsonUrlString) {
|
||||||
url: patchUrl(new URL(dist.downloadURL)),
|
url: patchUrl(new URL(dist.downloadURL)),
|
||||||
outputPath,
|
outputPath,
|
||||||
attempts: 0,
|
attempts: 0,
|
||||||
})),
|
}))
|
||||||
);
|
);
|
||||||
const totalJobs = jobs.length;
|
const totalJobs = jobs.length;
|
||||||
let nFinished = 0;
|
let nFinished = 0;
|
||||||
let nErrors = 0;
|
let nErrors = 0;
|
||||||
|
|
||||||
// por las dudas verificar que no hayan archivos duplicados
|
// por las dudas verificar que no hayan archivos duplicados
|
||||||
chequearIdsDuplicados(jobs, outputPath);
|
chequearIdsDuplicados(jobs);
|
||||||
|
|
||||||
shuffleArray(jobs);
|
shuffleArray(jobs);
|
||||||
|
|
||||||
|
@ -149,7 +96,7 @@ async function downloadFromData(jsonUrlString) {
|
||||||
try {
|
try {
|
||||||
await downloadDistWithRetries(job);
|
await downloadDistWithRetries(job);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorFile.write(JSON.stringify(encodeError(job, error)) + "\n");
|
await errorFile.write(JSON.stringify(job, encodeError(error)) + "\n");
|
||||||
nErrors++;
|
nErrors++;
|
||||||
} finally {
|
} finally {
|
||||||
nFinished++;
|
nFinished++;
|
||||||
|
@ -157,16 +104,16 @@ async function downloadFromData(jsonUrlString) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
process.stderr.write(`info[${outputPath}]: 0/${totalJobs} done\n`);
|
process.stderr.write(`info[${jsonUrl.host}]: 0/${totalJobs} done\n`);
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
process.stderr.write(
|
process.stderr.write(
|
||||||
`info[${outputPath}]: ${nFinished}/${totalJobs} done\n`,
|
`info[${jsonUrl.host}]: ${nFinished}/${totalJobs} done\n`
|
||||||
);
|
);
|
||||||
}, 30000);
|
}, 30000);
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
if (nErrors > 0)
|
if (nErrors > 0)
|
||||||
console.error(`${outputPath}: Finished with ${nErrors} errors`);
|
console.error(`${jsonUrl.host}: Finished with ${nErrors} errors`);
|
||||||
} finally {
|
} finally {
|
||||||
errorFile.close();
|
errorFile.close();
|
||||||
}
|
}
|
||||||
|
@ -192,13 +139,13 @@ async function downloadDistWithRetries(job, attempts = 0) {
|
||||||
await wait(15000);
|
await wait(15000);
|
||||||
return await downloadDistWithRetries(job, attempts + 1);
|
return await downloadDistWithRetries(job, attempts + 1);
|
||||||
}
|
}
|
||||||
// si no fue un error de http, reintentar hasta 3 veces con 5 segundos de por medio
|
// si no fue un error de http, reintentar hasta 5 veces con 5 segundos de por medio
|
||||||
else if (
|
else if (
|
||||||
!(error instanceof StatusCodeError) &&
|
!(error instanceof StatusCodeError) &&
|
||||||
!(error instanceof TooManyRedirectsError) &&
|
!(error instanceof TooManyRedirectsError) &&
|
||||||
attempts < 3
|
attempts < 10
|
||||||
) {
|
) {
|
||||||
await wait(5000 + Math.random() * 10000);
|
await wait(5000);
|
||||||
return await downloadDistWithRetries(job, attempts + 1);
|
return await downloadDistWithRetries(job, attempts + 1);
|
||||||
} else throw error;
|
} else throw error;
|
||||||
}
|
}
|
||||||
|
@ -228,12 +175,12 @@ async function downloadDist({ dist, dataset, url, outputPath }) {
|
||||||
const fileDirPath = join(
|
const fileDirPath = join(
|
||||||
outputPath,
|
outputPath,
|
||||||
sanitizeSuffix(dataset.identifier),
|
sanitizeSuffix(dataset.identifier),
|
||||||
sanitizeSuffix(dist.identifier),
|
sanitizeSuffix(dist.identifier)
|
||||||
);
|
);
|
||||||
await mkdir(fileDirPath, { recursive: true });
|
await mkdir(fileDirPath, { recursive: true });
|
||||||
const filePath = join(
|
const filePath = join(
|
||||||
fileDirPath,
|
fileDirPath,
|
||||||
sanitizeSuffix(dist.fileName || dist.identifier),
|
sanitizeSuffix(dist.fileName || dist.identifier)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!res.body) throw new Error("no body");
|
if (!res.body) throw new Error("no body");
|
||||||
|
@ -268,15 +215,14 @@ function sanitizeSuffix(path) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {DownloadJob[]} jobs
|
* @param {DownloadJob[]} jobs
|
||||||
* @param {string} id
|
|
||||||
*/
|
*/
|
||||||
function chequearIdsDuplicados(jobs, id) {
|
function chequearIdsDuplicados(jobs) {
|
||||||
const duplicated = hasDuplicates(
|
const duplicated = hasDuplicates(
|
||||||
jobs.map((j) => `${j.dataset.identifier}/${j.dist.identifier}`),
|
jobs.map((j) => `${j.dataset.identifier}/${j.dist.identifier}`)
|
||||||
);
|
);
|
||||||
if (duplicated) {
|
if (duplicated) {
|
||||||
console.error(
|
console.error(
|
||||||
`ADVERTENCIA[${id}]: ¡encontré duplicados! es posible que se pisen archivos entre si`,
|
"ADVERTENCIA: ¡encontré duplicados! es posible que se pisen archivos entre si"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -297,7 +243,7 @@ function wait(ms) {
|
||||||
*/
|
*/
|
||||||
function encodeError(job, error) {
|
function encodeError(job, error) {
|
||||||
const always = {
|
const always = {
|
||||||
url: job.url?.toString() || job.dist.downloadURL,
|
url: job.url?.toString || job.dist.downloadURL,
|
||||||
datasetIdentifier: job.dataset.identifier,
|
datasetIdentifier: job.dataset.identifier,
|
||||||
distributionIdentifier: job.dist.identifier,
|
distributionIdentifier: job.dist.identifier,
|
||||||
};
|
};
|
||||||
|
@ -334,45 +280,3 @@ function shuffleArray(array) {
|
||||||
[array[i], array[j]] = [array[j], array[i]];
|
[array[i], array[j]] = [array[j], array[i]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string[]} portales
|
|
||||||
*/
|
|
||||||
function generateReadme(portales) {
|
|
||||||
// basado en el readme de Patricio
|
|
||||||
return `Dumps de Portales de Datos Abiertos de la República Argentina
|
|
||||||
=============================================================
|
|
||||||
|
|
||||||
El zip contiene todo lo que se pudo descargar de los portales seleccionados, que fueron:
|
|
||||||
${portales.map((p) => `- ${p}`).join("\n")}
|
|
||||||
|
|
||||||
La carpeta está ordenada en subcarpetas cuyo nombre corresponde al ID del dataset/distribución del portal. De esta forma,
|
|
||||||
leyendo el data.json se puede programaticamente y de manera simple volver a mapear qué archivo le corresponde a cada
|
|
||||||
distribución.
|
|
||||||
|
|
||||||
Formato:
|
|
||||||
|
|
||||||
- {url de data.json sin protocolo y con / reemplazado por _}/
|
|
||||||
- data.json
|
|
||||||
- errors.jsonl: archivo con todos los errores que se obtuvieron al intentar descargar todo.
|
|
||||||
- {identifier de dataset}/
|
|
||||||
- {identifier de distribution}/
|
|
||||||
- {fileName (o, si no existe, identifier de distribution)}
|
|
||||||
|
|
||||||
Ejemplo:
|
|
||||||
|
|
||||||
- datos.gob.ar_data.json/
|
|
||||||
- data.json
|
|
||||||
- errors.jsonl
|
|
||||||
- turismo_fbc269ea-5f71-45b6-b70c-8eb38a03b8db/
|
|
||||||
- turismo_0774a0bb-71c2-44d7-9ea6-780e6bd06d50/
|
|
||||||
- cruceristas-por-puerto-residencia-desagregado-por-pais-mes.csv
|
|
||||||
- ...
|
|
||||||
- energia_0d4a18ee-9371-439a-8a94-4f53a9822664/
|
|
||||||
- energia_9f602b6e-2bef-4ac4-895d-f6ecd6bb1866/
|
|
||||||
- energia_9f602b6e-2bef-4ac4-895d-f6ecd6bb1866 (este archivo no tiene fileName en el data.json, entonces se reutiliza el identifier)
|
|
||||||
- ...
|
|
||||||
|
|
||||||
Este dump fue generado con transicion-desordenada-diablo: https://gitea.nulo.in/Nulo/transicion-desordenada-diablo
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"run": "mkdir -p data && cd data && env NODE_EXTRA_CA_CERTS=../pki/ca_intermediate_root_bundle.pem node ../download_json.js"
|
"run": "env NODE_EXTRA_CA_CERTS=pki/ca_intermediate_root_bundle.pem node download_json.js"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
@ -15,7 +15,6 @@
|
||||||
"undici": "^5.28.0"
|
"undici": "^5.28.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/node20": "^20.1.2",
|
|
||||||
"@types/node": "^20.10.0"
|
"@types/node": "^20.10.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,6 @@ dependencies:
|
||||||
version: 5.28.0
|
version: 5.28.0
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@tsconfig/node20':
|
|
||||||
specifier: ^20.1.2
|
|
||||||
version: 20.1.2
|
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.10.0
|
specifier: ^20.10.0
|
||||||
version: 20.10.0
|
version: 20.10.0
|
||||||
|
@ -27,10 +24,6 @@ packages:
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@tsconfig/node20@20.1.2:
|
|
||||||
resolution: {integrity: sha512-madaWq2k+LYMEhmcp0fs+OGaLFk0OenpHa4gmI4VEmCKX4PJntQ6fnnGADVFrVkBj0wIdAlQnK/MrlYTHsa1gQ==}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/node@20.10.0:
|
/@types/node@20.10.0:
|
||||||
resolution: {integrity: sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==}
|
resolution: {integrity: sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
33
readme.md
33
readme.md
|
@ -1,9 +1,5 @@
|
||||||
# WIP: descargador masivo de datos públicos
|
# WIP: descargador masivo de datos públicos
|
||||||
|
|
||||||
descarga masivamente archivos de la mayoría de los portales de datos argentinos que tengan un archivo "data.json" ([DCAT](https://www.w3.org/TR/vocab-dcat-2/)). la idea es tener un espejo (mirror) lo más perfecto posible en el caso de que cualquiera de las fuentes se caiga.
|
|
||||||
|
|
||||||
## setup
|
|
||||||
|
|
||||||
require [Node.js](https://nodejs.org) y [pnpm](https://pnpm.io/)
|
require [Node.js](https://nodejs.org) y [pnpm](https://pnpm.io/)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -13,13 +9,8 @@ pnpm install
|
||||||
## correr
|
## correr
|
||||||
|
|
||||||
```
|
```
|
||||||
# descargar portal datos.gob.ar
|
pnpm run run download_json.js https://datos.gob.ar/data.json
|
||||||
pnpm run run https://datos.gob.ar/data.json
|
# guarda en ./datos.gob.ar
|
||||||
# guarda en data/datos.gob.ar_data.json
|
|
||||||
|
|
||||||
# descargar todos los portales conocidos
|
|
||||||
pnpm run run
|
|
||||||
# guarda en data/*
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## contenedor
|
## contenedor
|
||||||
|
@ -31,23 +22,9 @@ docker run --rm -it -v ./data:/data gitea.nulo.in/nulo/transicion-desordenada-di
|
||||||
|
|
||||||
## formato de repo guardado
|
## formato de repo guardado
|
||||||
|
|
||||||
- `{url de data.json sin protocolo y con / reemplazado por _}/`
|
- `{dominio de repo}`
|
||||||
- `data.json`
|
- `data.json`
|
||||||
- `errors.jsonl`: archivo con todos los errores que se obtuvieron al intentar descargar todo.
|
- `errors.jsonl`: archivo con todos los errores que se obtuvieron al intentar descargar todo.
|
||||||
- `{identifier de dataset}/`
|
- `{identifier de dataset}`
|
||||||
- `{identifier de distribution}/`
|
- `{identifier de distribution}`
|
||||||
- `{fileName (o, si no existe, identifier de distribution)}`
|
- `{fileName (o, si no existe, identifier de distribution)}`
|
||||||
|
|
||||||
### ejemplo
|
|
||||||
|
|
||||||
- `datos.gob.ar_data.json/`
|
|
||||||
- `data.json`
|
|
||||||
- `errors.jsonl`
|
|
||||||
- `turismo_fbc269ea-5f71-45b6-b70c-8eb38a03b8db/`
|
|
||||||
- `turismo_0774a0bb-71c2-44d7-9ea6-780e6bd06d50/`
|
|
||||||
- `cruceristas-por-puerto-residencia-desagregado-por-pais-mes.csv`
|
|
||||||
- ...
|
|
||||||
- `energia_0d4a18ee-9371-439a-8a94-4f53a9822664/`
|
|
||||||
- `energia_9f602b6e-2bef-4ac4-895d-f6ecd6bb1866/`
|
|
||||||
- `energia_9f602b6e-2bef-4ac4-895d-f6ecd6bb1866` (este archivo no tiene fileName en el data.json, entonces se reutiliza el `identifier`)
|
|
||||||
- ...
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
|
||||||
"display": "Node 20",
|
|
||||||
"_version": "20.1.0",
|
|
||||||
|
|
||||||
"compilerOptions": {
|
|
||||||
"lib": ["es2023"],
|
|
||||||
"module": "node16",
|
|
||||||
"target": "es2022",
|
|
||||||
|
|
||||||
"strict": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"moduleResolution": "node16"
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue