Compare commits

...

2 Commits

Author SHA1 Message Date
31d8fd53c8 scrap one list of books 2023-08-17 12:40:49 +02:00
757528b952 déménagement du dépot Rangement
Signed-off-by: tykayn <contact@cipherbliss.com>
2023-08-17 10:58:38 +02:00
20 changed files with 186 additions and 593 deletions

8
rangement/.idea/.gitignore generated vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/rangement.iml" filepath="$PROJECT_DIR$/.idea/rangement.iml" />
</modules>
</component>
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@ -1,4 +1,12 @@
# Devine le rangement # Devine le rangement
/!\ Ce dépot a déménagé ici:
https://forge.chapril.org/tykayn/rangement /!\
installez le avec:
```bash
npm i -g rangement
```
----
script qui devine comment renommer des fichiers selon un pattern de date script qui devine comment renommer des fichiers selon un pattern de date
trouve des infos exif et prend la plus ancienne pour renseigner le nom de fichier. trouve des infos exif et prend la plus ancienne pour renseigner le nom de fichier.

View File

@ -1,6 +0,0 @@
export const tagSeparator = ' '
export const tagSectionSeparator = '--'
export const enableTestsLocally = false
export const reportStatistics = false
export const version = '1.0.0'

View File

@ -1,244 +0,0 @@
/**
* la classe qui repère des patterns
*/
import { tagSectionSeparator, tagSeparator } from './configs.mjs'
import exifr from 'exifr'
import moment from 'moment'
import path from 'path'
/**
* finds patterns for file name
*/
export default class finder {
static statistics = {
filesModified: 0,
}
static patternsFiles = {
'downloaded_pic': /^\-\w{15}\.jpg/, // FyB8cZnWIAc21rw.jpg
'telegram_pic': /^\-\d{19}_\d{4}/, // -4900281569878475578_1109.jpg
'open_camera': /^IMG_OC_\d{8}/i, // IMG_OC_20230617_092120_3.jpg
'screenshot': /^Screenshot/i, // Screenshot 2023-06-15 at 15-26-04 Instance Panoramax OSM-FR.png
}
static reportStatistics () {
console.log('statistics',
this.statistics)
}
static findScreenshot (inputString) {
return inputString.match(/screenshot/i) || inputString.match(/capture d'écran/i)
}
static findFormattedDate (filepath) {
let match = filepath.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/ig)
// console.log('match findFormattedDate', match)
let result = ''
if (match && match[0]) {
result = match[0]
}
return result
}
static findFileExtension (inputString) {
let result = inputString.match(/\.\w{3,4}$/i)
return result
}
/**
* find the section of file name which contains the free text to describe the picture
* @param fileName
* @returns {*|string}
*/
static findFileNameFreeTextPart (fileName) {
fileName = fileName.replace(this.findFileExtension(fileName), '')
let boom = fileName.split(tagSectionSeparator)
if (boom.length) {
let freeTextPart = boom[0].trim()
console.log('freeTextPart', freeTextPart)
return freeTextPart
}
return fileName.trim()
}
/**
* find an array of tags
* @param inputString
* @returns {[]}
*/
static findTagSectionInString (inputString) {
let listOfTags = []
// remove extension
let extensionFile = finder.findFileExtension(inputString)
if (extensionFile) {
extensionFile = extensionFile[0]
} else {
console.log('no extensionFile', extensionFile, inputString)
extensionFile = ''
}
inputString = inputString.replace(extensionFile, '')
// console.log('extensionFile', extensionFile)
if (inputString.includes(tagSectionSeparator)) {
// console.log('inputString', inputString)
if (inputString.length) {
let boom = inputString.split(tagSectionSeparator)
// console.log('boom', boom)
if (boom.length) {
let fileSectionsName = boom.splice(tagSeparator)
listOfTags = [...fileSectionsName[1].trim().split(tagSeparator)]
// console.log('listOfTags', listOfTags)
} else {
console.log('no boom', boom)
}
}
}
return listOfTags
}
static cleanSpaces (inputString) {
return inputString.trim().replace(/ *g/, ' ')
}
static searchAndReplaceInFileName (searchString, replaceString, fileName) {
return this.cleanSpaces(fileName.replace(searchString, replaceString))
}
/**
* search screenshot clues and rename
*/
static searchAndRenameScreenshots (fileName) {
if (finder.findScreenshot(fileName)) {
let tags = this.findTagSectionInString(fileName)
console.log('tags', tags)
if (!tags.includes('screenshot')) {
fileName = this.addTagInFileName('screenshot', fileName)
fileName = this.searchAndReplaceInFileName('Screenshot', '', fileName)
console.log('screenShotMockFileName:', fileName)
return this.cleanSpaces(fileName)
}
console.log('is a screenshot, remove screenshot in name, and add tag screenshot')
} else {
return null
}
}
static addTagInFileName (tagName, fileName) {
let tags = this.findTagSectionInString(fileName)
let firstPart = this.findFileNameFreeTextPart(fileName)
tags.push(tagName)
let uniqueArray = [...new Set(tags)]
let newFileName = firstPart + ' ' + tagSectionSeparator + ' ' + tags.join(tagSeparator)
newFileName = newFileName.replace(/ {*}/, '') + this.findFileExtension(fileName)
return this.cleanSpaces(newFileName)
}
/**
* convertit un nom de fichier en une structure décrivant plusieurs parties correspondant au pattern d'archivage
* @param fullPath
* @returns {{extension: *, dateStamp: string, freeText: (*|string), tags: *[]}}
*/
static destructurateFileName (fullPath) {
let [folderPath, fileNameOriginal] = this.findFolderPath(fullPath)
let dateStampInFileNameOriginal = this.findFormattedDate(fileNameOriginal)
return {
fullPath,
folderPath,
fileNameOriginal,
dateStampInFileNameOriginal,
dateStampExif: '',
freeText: this.findFileNameFreeTextPart(fileNameOriginal),
tags: this.findTagSectionInString(fileNameOriginal),
extension: this.findFileExtension(fileNameOriginal),
}
}
/**
* finds the earliest part in several exif date info
* @param exifData
* @returns {string}
*/
static findEarliestDateInExifData (exifData) {
if (exifData) {
let moments = []
// console.log('exif data : ', exifData) // Do something with your data!
if (exifData.DateTimeOriginal) {
// console.log('image créée le : DateTimeOriginal : ', exifData.DateTimeOriginal) // Do something with your data!
moments.push(exifData.DateTimeOriginal)
}
if (exifData.ModificationDateTime) {
// console.log('image créée le : ModificationDateTime : ', exifData.ModificationDateTime) // Do something with your data!
moments.push(exifData.ModificationDateTime)
}
if (exifData.ModifyDate) {
// console.log('image créée le : ModifyDate : ', exifData.ModifyDate) // Do something with your data!
moments.push(exifData.ModifyDate)
}
if (exifData.FileAccessDateTime) {
moments.push(exifData.FileAccessDateTime)
}
if (exifData.FileInodeChangeDateTime) {
moments.push(exifData.FileInodeChangeDateTime)
}
if (exifData.FileModificationDateTime) {
// console.log('image créée le : FileModificationDateTime : ', exifData.FileModificationDateTime) // Do something with your data!
moments.push(exifData.FileModificationDateTime)
}
if (exifData.CreateDate) {
// console.log('image créée le : CreateDate : ', exifData.CreateDate) // Do something with your data!
moments.push(exifData.CreateDate)
}
moments = moments.map(d => {
let newdate = moment(d)
return newdate
})
let minDate = moment.min(moments)
// console.log('minDate :::::::::', minDate)
console.log('minDate :::::::::', minDate.format('yyyy-MM-DDTHH:mm:ss'))
return minDate.format('yyyy-MM-DDTHH:mm:ss')
} else {
console.log('pas de exif data')
return ''
}
}
/**
* examine plusieurs propriétés exif de date et retourne la plus ancienne
* @param filepath
*/
static async findExifCreationDate (filepath) {
console.log('filepath', filepath)
let dateAlreadyInFileName = finder.findFormattedDate(filepath)
if (dateAlreadyInFileName) {
console.log('------ dateAlreadyInFileName', dateAlreadyInFileName)
}
return await exifr.parse(filepath)
}
static findFolderPath (filePath) {
let folders = filePath.split('/')
let fileName = folders.pop()
folders = filePath.replace(fileName, '')
console.log('\n - folders', folders)
console.log(' - fileName', fileName, '\n')
return [folders, fileName]
}
}

View File

@ -1,134 +0,0 @@
/**---------------------
* @name tykayn Rangement
* @description Rangement sorts and rename files depending on their exif data
* @contact contact@cipherbliss.com
--------------------- */
/** ---------------------
libs
--------------------- */
import fs from 'node-fs'
import minimist from 'minimist'
/** ---------------------
custom utilities and configuration
--------------------- */
import { enableTestsLocally, reportStatistics,tagSectionSeparator, tagSeparator } from './configs.mjs'
import {
TestFindFormattedDate,
TestScreenShotIsFoundAndRenamed,
TestTagsAreDetectedInFileName
} from './testFunctions.mjs'
import finder from './finder.mjs'
let mini_arguments
console.log(' ')
function parseArguments () {
mini_arguments = minimist(process.argv.slice(2))
console.log('arguments', mini_arguments)
}
parseArguments()
function renameFile (originalFileName, fileMixedNewName) {
fs.rename(originalFileName, fileMixedNewName, function (err) {
if (err) console.log('rename ERROR: ' + err)
})
}
function appendFileName (fileProperties, newText) {
fileProperties.freeText = finder.cleanSpaces(fileProperties.freeText + ' ' + newText)
return fileProperties
}
function prependFileName (fileProperties, newText) {
fileProperties.freeText = finder.cleanSpaces(newText + ' ' + fileProperties.freeText)
return fileProperties
}
function makeFileNameFromProperties(fileProperties) {
let tagPlace = ''
if (fileProperties.tags.length) {
tagPlace = ' ' + tagSectionSeparator + ' '
}
// return finder.cleanSpaces(fileProperties.dateStamp + ' ' + fileProperties.freeText + tagPlace + fileProperties.tags.join(tagSeparator) + fileProperties.extension).replace(+' ' + tagSectionSeparator + ' ' + '.', '.')
return ''+fileProperties.dateStampExif + ' ' + fileProperties.freeText + tagPlace + fileProperties.tags.join(tagSeparator) + fileProperties.extension
}
function shouldWeChangeName (structureForFile) {
console.log(' ______ allez hop fini la recherche on fait un nouveau nom')
console.log('structureForFile', structureForFile)
let newName = makeFileNameFromProperties(structureForFile)
if (structureForFile.fileNameOriginal !== newName) {
console.log('\n ancien nom :', structureForFile.fileNameOriginal)
// console.log(' nouveau nom:', foundDate +structureForFile.freeText + structureForFile.tags.join(tagSeparator) + structureForFile.extension )
console.log(' nouveau nom:', newName)
} else {
console.log(' rien à changer')
}
}
async function guessFileNameOnAllFilesFromArguments () {
// parcourir les dossiers
// parcourir les fichiers
console.log('liste des fichiers', mini_arguments._)
let fileList = mini_arguments._
fileList.forEach(fullPath => {
let structureForFile = finder.destructurateFileName(fullPath)
// examiner les infos exif de chaque fichier pour proposer un nouveau nom
if (!structureForFile.dateStampInFileNameOriginal) {
console.log(' le nom de fichier ne contient pas de date formatée au début')
finder.findExifCreationDate(structureForFile.fullPath)
.then(data => {
console.log(' ... chercher la date de création')
let foundDate = finder.findEarliestDateInExifData(data)
console.log(' =>>>>>>> foundDate : ', foundDate)
if (foundDate) {
// finder.findEarliestDateInExifData(fullPath).then(response => {
// console.log(' ... trouvée')
// if (response) {
structureForFile.dateStampExif = foundDate
shouldWeChangeName(structureForFile)
// }
// })
} else {
console.log('pas de date trouvée dans le nom')
}
}
,
(error) => {
console.log('/////////// Error in reading exif of file: ' + error.message)
return ''
})
}
}
)
}
guessFileNameOnAllFilesFromArguments()
// run tests
if (enableTestsLocally) {
TestTagsAreDetectedInFileName()
TestFindFormattedDate()
TestScreenShotIsFoundAndRenamed()
}
if (reportStatistics || mini_arguments.stats) {
finder.reportStatistics()
}

View File

@ -1,22 +0,0 @@
import finder from "./finders.mjs";
// const finders = require('./finders.mjs')
describe('rangement file name', () => {
test('detects date in file name', () => {
expect(finder.findFormattedDate('2023-06-23T18.36.47 -- machin bidule.jpg')).toBe('2023-06-23T18.36.47');
});
test('detects file extension in file name', () => {
expect(finder.findFileExtension()('2023-06-23T18.36.47 -- machin bidule.jpg')).toBe('jpg');
});
})
console.log('finders', finder)
test('adding positive numbers is not zero', () => {
for (let a = 1; a < 10; a++) {
for (let b = 1; b < 10; b++) {
expect(a + b).not.toBe(0);
}
}
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

View File

@ -1,48 +0,0 @@
import finder from './finder.mjs'
const pathFolder = '/home/poule/encrypted/stockage-syncable/photos/a_dispatcher/tout'
const sortingFolder = '/home/poule/encrypted/stockage-syncable/photos/a_dispatcher'
const fileDefinition = {
dateStamp: '',
freeText: '',
tags: [],
extension: '',
}
export function TestScreenShotIsFoundAndRenamed() {
let screenShotMockFileName = 'Screenshot 2023-06-15 at 15-28-21 Instance Panoramax OSM-FR.png'
let screenShotMockFileNameExpected = '2023-06-15 at 15-28-21 Instance Panoramax OSM-FR -- screenshot.png'
let found = finder.searchAndRenameScreenshots(screenShotMockFileName)
console.log('found', found)
if (found === screenShotMockFileNameExpected) {
console.log('TestScreenShotIsFoundAndRenamed : test succès')
} else {
console.log('TestScreenShotIsFoundAndRenamed : FAIL:')
console.log(found)
console.log(screenShotMockFileNameExpected)
}
}
export function TestTagsAreDetectedInFileName() {
let mockFileName = '2023-06-15T10:11:12 -- screeenshot festival.png'
let expectedResult = ['screeenshot', 'festival']
let found = finder.findTagSectionInString(mockFileName)
if (found === expectedResult) {
console.info('Succès')
}
}
export function TestFindFormattedDate() {
let mockFileName = 'Capture d\'écran 2023-06-15T10:11:12.png'
let expectedResult = '2023-06-15T10:11:12'
let found = finder.findFormattedDate(mockFileName)
console.log('foundDate', found, expectedResult)
console.log('foundDate', found)
if (found === expectedResult) {
console.info('Succès')
}
}

View File

@ -1,103 +0,0 @@
import finder from './finders'
import fs from 'node-fs'
/**
----------------------- parties non réalisées -----------------------
* work in progress
// TODO
---------------------------------------------------------------------
**/
function TestDownloadedTelegramPictureRename (fileName) {
let fileProperties = destructurateFileName(fileName)
}
function hasDifferentDateInNameThanExif (fileName) {
let foundDate = finder.findFormattedDate(fileName)
if (foundDate && foundDate != getExifCreationDate(fileName)) {
return true
}
return false
}
function moveToArchive (targetDirectory, fileFullPath) {
// find current directory,
// rename file to move it
}
function getStatisticsOnArchiveFolder (fileFullPath) {
return {
foldersCount: 'TODO',
filesWithoutSemanticName: 'TODO'
}
}
function getControlledVocabularyFromFiles (fileFullPath) {
let controlledVocabulary = ['TODO']
// find all tags
return controlledVocabulary
}
function moveToSortingFolder (fileFullPath) {
return 'TODO'
}
/**
* écrit un nouveau nom de fichier formatté
* @param convertedToName
* @param originalFileName
* @returns {*}
*/
function mixDateNameWithFileName (convertedToName, originalFileName) {
// enlever l'ancien timestamp si il existe
// ajouter en début de nom le nouveau timestamp avec un espace et conserver le reste du nom
return originalFileName
}
function TestMixingName () {
let fileMixedNewName = mixDateNameWithFileName(convertedToName, originalFileName)
console.log('new name', fileMixedNewName)
if (fileMixedNewName !== originalFileName) {
console.log('renommage =>', fileMixedNewName)
// renameFile(originalFileName, fileMixedNewName)
}
}
/**
* obtenir une liste des dossiers uniquement dans le dossier courant
* @param path
* @returns {*}
*/
function getDirectories (path) {
return fs.readdirSync(path).filter(function (file) {
return fs.statSync(path + '/' + file).isDirectory()
})
}
function convertDateToTimeInFileName (inputDate) {
return inputDate.replace(' ', 'T')
}
function testthings(){
// let list = getDirectories(pathFolder)
// console.log('list', list)
let originalFileName = '2015-04-30T09.09.02 -- scan papier.jpg'
let formattedDatePIMBefore = finder.findFormattedDate(originalFileName)
console.log('formattedDatePIMBefore', formattedDatePIMBefore)
let creationDateFound = finder.getExifCreationDate(pathFolder + '/' + originalFileName)
let convertedToName = ''
if (creationDateFound) {
convertedToName = convertDateToTimeInFileName(creationDateFound)
}
console.log('convertedToName', convertedToName)
}

101
scraping/main.ts Normal file
View File

@ -0,0 +1,101 @@
/**
scrapping des livres de la médiathèque de briis
**/
// @ts-ignore
import https from 'https';
import WriteFile from "./utils";
const axios = require('axios');
const cheerio = require('cheerio');
const url: string = "www.mediatheque-de-briis-sous-forges.net";
const fetching_path: string = "/mediatheque-de-briis-sous-forges.net/opac/recherche/catalogue?node=0&value=0&page=2";
interface Book {
author: string
title: string
description: string
img: string
}
const books: Book[] = [];
// autres pages:
// http://www.mediatheque-de-briis-sous-forges.net/mediatheque-de-briis-sous-forges.net/opac/recherche/catalogue?node=0&value=0&page=2
const page_max = 1927
const getTables = (html: string): any => {
const $ = cheerio.load(html);
const tableElements = $(
"table.notice"
);
return tableElements;
};
const getHtml = async (hostname: string, path: string): Promise<string> =>
new Promise((resolve, reject) => {
https
.get(
{
hostname,
path,
// port:80,
method: "GET",
},
(res) => {
let html = "";
res.on("data", function (chunk) {
html += chunk;
});
res.on("end", function () {
resolve(html);
});
}
)
.on("error", (error) => {
console.error(error);
reject(error);
});
});
// fetchData(url).then((res: any) => {
// const html = res.data;
// const $ = cheerio.load(html);
// const statsTable :any = $('table.notice');
// console.log('statsTable', statsTable)
// statsTable.each(function(){
// let elem:any = this;
// let author = $(elem).find('td').eq(2).text();
// // let img = $(this).find('img').attr('src');
// // console.log(elem);
// console.log(author);
// });
// })
function writeBookScrapping() {
WriteFile('books.json', JSON.stringify(books, null , 2))
}
getHtml(url, fetching_path)
.then(getTables)
.then(
(tables: any) => tables.each(
(_: any, table: any) => {
const $ = cheerio.load(table);
// console.log('une table')
// let author = $().find('a.notice').text();
let text_description = $(table).find('td').eq(1).text();
let boom = text_description.split('\n');
let splitting = boom[1].split('/')
let img_src = $(table).find('td img').attr('src');
console.log(img_src);
books.push({
author: boom[0],
title: splitting[0],
description: splitting[1],
img: img_src
})
// console.log(cheerio.load(table).html())
}
)
)
.then(writeBookScrapping)
.catch((error) => console.log(error));

View File

@ -0,0 +1,62 @@
[
{
"author": "ALLENDE, Isabel",
"title": "Portrait sépia ",
"description": " Isabel Allende ; Trad. de l'espagnol par Claude de Frayssinet. - Paris : Grasset et Fasquelle, 2001. - 1 vol. , 391 p. : couv. ill. ; 24 x 15 cm.",
"img": "http://images-eu.amazon.com/images/P/2246617715.08.MZZZZZZZ.jpg"
},
{
"author": "AMETTE, Jacques-Pierre",
"title": "La Maîtresse de Brecht : roman ",
"description": " Jacques-Pierre Amette. - Paris : Albin Michel, 2003. - 300 p. : jaquette ill. ; 20 cm.",
"img": "http://images-eu.amazon.com/images/P/2226141634.08.MZZZZZZZ.jpg"
},
{
"author": "ANDRIÂC, Ivo",
"title": "Mara la courtisane : et autres nouvelles ",
"description": " Ivo Andriâc ; Trad. du serbo-croate par Pascale Delpech. - Paris : Belfond, 1999. - 234 p. : couv. ill. en coul. ; 23 cm. - (Littérature étrangère).",
"img": "http://images-eu.amazon.com/images/P/2714435572.08.MZZZZZZZ.jpg"
},
{
"author": "ANGLADE, Jean",
"title": "Un Lit d'aubépine : roman ",
"description": " Jean Anglade. - Paris : Presses de la Cité, 1995. - 325 p. : couv. ill. en coul. ; 23 cm. - (Production Jeannine Balland).",
"img": "http://images-eu.amazon.com/images/P/2258039568.08.MZZZZZZZ.jpg"
},
{
"author": "ARNOTHY, Christine",
"title": "J'ai quinze ans et je ne veux pas mourir ; (suivi de) Il n'est pas si facile de vivre ",
"description": " Christine Arnothy. - Paris : France loisirs, 1981. - 330 p ; 23 cm.",
"img": "http://images-eu.amazon.com/images/P/2724211065.08.MZZZZZZZ.jpg"
},
{
"author": "CHAUVIN, Rémy",
"title": "Le Monde animal et ses comportements complexes ",
"description": " Rémy Chauvin, Bernadette Chauvin. - Paris : Plon, 1977. - 282 p : ill ; 21 cm.",
"img": "http://images-eu.amazon.com/images/P/2259002331.08.MZZZZZZZ.jpg"
},
{
"author": "D'ARZO, Silvio",
"title": "Maison des autres , (Contient) Un moment comme ça ",
"description": " texte de Silvio D'Arzo ; Trad. de l'italien par Bernard Simeone, Philippe Renard ; Préf. Attilio Bertolucci. - Lagrasse : Verdier, 1997. - 1 vol. , 86 p. : - ; 22 x 14 cm. - (Terra d'altri, ISSN 0989-4160).",
"img": "http://images-eu.amazon.com/images/P/2864322838.08.MZZZZZZZ.jpg"
},
{
"author": "ASSOULINE, Pierre",
"title": "La Cliente : roman ",
"description": " Pierre Assouline. - Paris : Gallimard, 1998. - 191 p. ; 21 cm.",
"img": "http://images-eu.amazon.com/images/P/207075278X.08.MZZZZZZZ.jpg"
},
{
"author": "ATKINSON, Kate",
"title": "Dans les replis du temps ",
"description": " texte de Kate Atkinson ; Trad. de l'anglais par Jean Bourdier. - Paris : Librairie générale française, 1999. - 1 vol. , 403 p. : ill., couv. ill. en coul. ; 18 x 11 cm. - (Le livre de poche ; 14687).",
"img": "http://images-eu.amazon.com/images/P/2253146870.08.MZZZZZZZ.jpg"
},
{
"author": "ATKINSON, Kate",
"title": "Dans les coulisses du musée : roman ",
"description": " Kate Atkinson ; Trad. de l'anglais par Jean Bourdier. - Paris : Bernard de Fallois, 1996. - 348 p. : couv. ill. en coul. ; 23 cm.",
"img": "http://images-eu.amazon.com/images/P/2877062775.08.MZZZZZZZ.jpg"
}
]

15
scraping/utils.ts Normal file
View File

@ -0,0 +1,15 @@
import * as fs from "node:fs";
export default function WriteFile(fileName: string, fileContent: any) {
console.log('write file', fileName)
return fs.writeFile(
`./output/${fileName}`,
fileContent,
'utf8',
(err) => {
if (err) {
console.log(`Error writing file: ${err}`)
}
}
)
}