Never give up

Node - XLSX example (feat. scraping) 본문

WEB

Node - XLSX example (feat. scraping)

대기만성 개발자 2022. 5. 11. 16:00
반응형

요즘 엑셀 작업할 일들이 조금 있어서 최대한 자동화를 해보려고 이런저런 삽질(?)을 하다보니..

 

이런 포스트도 있으면 좋겠다 싶어서 만들어봤습니다

 

server.js

const express = require('express')

const app = express()

const port = process.env.PORT || 8080

const excel = require('./excel/index')

app.use(express.json())
app.use(express.urlencoded({ extended: true }))

app.use('/excel', excel)

app.listen(port, () => {
    console.log(`Started! express server on port ${port}`)
})

기본틀은 그대로 가져가고..

 

lib/excel/index.js

const express = require('express')

const router = express.Router()

const makeExcel = require('./xlsx/make_excel')

const editExcel = require('./xlsx/edit_excel')

const scrap = require('../scrap/index')

router.get('/make', async (_, res) => {
    let blogData = {}

    blogData.list = await scrap()

    blogData.callback = (fileName) => {
        res.download(fileName)
    }

    makeExcel(blogData)
})

router.get('/edit', async (_, res) => {
    try {
        await editExcel((fileName) => res.download(fileName))
    } catch (e) {
        res.send({ e })
    }
})

module.exports = router

사용할 api는 2개로 get으로 가져옵니다

 

/excel/make : scraping 및 엑셀로 만들기


/excel/edit : 이미지 다운로드 및 엑셀 편집(가로, 세로 픽셀 크기 값 추가)

 

lib/scrap/index.js

const axios = require('axios').default

const cheerio = require('cheerio')

module.exports = async () => {
    let list = []

    const html = await axios.get('https://devmemory.tistory.com/')

    const $ = cheerio.load(html.data)

    const $articleList = $('div#mArticle').children('div.list_content')

    $articleList.each((_, e) => {
        const img = $(e).find('a.thumbnail_post img').attr('src')

        const title = $(e).find('a.link_post strong.tit_post').text()

        list = [...list, { img: `https:${img}`, title }]
    })

    return list
}

필자는 cheerio라는 라이브러리를 이용해서 스크래핑을 해봤습니다

(링크 : https://www.npmjs.com/package/cheerio)

 

사용법은 처음할 때는 조금 삽질했는데 익숙해지면 나름 괜찮아보입니다

 

cheerio.load('html파일') 형식으로 가져와서 원하는 태그를 선택하려면

 

$(태그명 + #태그id 혹은 .태그class)로 원하는 태그를 선택하고

 

children, attr, text등 필요한 정보를 가져옵니다

 

저는 제 개인블로그로 한번 해봤는데 mArticle의 div.list_content로 일단 포스팅 리스트를 가져와서

 

이미지와 타이틀을 가져오는데 이미지 같은경우 scheme가 빠져있어서 따로 추가해줬습니다

 

여기서 return 값이 json으로 변환하기 좋은 형태로 만들어 놓고

 

lib/excel/xlsx/make_excel.js

const xlsx = require('xlsx')

module.exports = (data) => {
    const workBook = xlsx.utils.book_new()

    const sheetData = xlsx.utils.json_to_sheet(data.list)

    const sheetName = '데이터'

    // sheet에 추가
    xlsx.utils.book_append_sheet(workBook, sheetData, sheetName)

    const excelFileName = 'test.xls'

    // excel 파일 생성
    xlsx.writeFileAsync(excelFileName, workBook, null, () => {
        console.log('Finished writing')
        data.callback(excelFileName)
    })
}

이후 가져온 데이터를 json_to_sheet로 그대로 붙여놓고

 

원하는 시트와 파일 명을 만들고 write를 해줍니다

 

이 부분까지 적용 후 콜을 해보면

 

< 이미지 링크와 타이틀 >

만들어진 엑셀파일을 보면 잘 작동된것을 확인할 수 있고

 

lib/excel/xlsx/edit_excel.js

const axios = require('axios').default

const fs = require('fs')

const sizeUtil = require('image-size')

const xlsx = require('xlsx')

module.exports = async (callback) => {
    const excelFile = xlsx.readFile(handler.getExcelFilePath)

    const sheetName = excelFile.SheetNames[0]

    const sheet = excelFile.Sheets[sheetName]

    const jsonData = xlsx.utils.sheet_to_json(sheet)

    let imgSizeList = []

    for (let i = 0; i < jsonData.length; i++) {
        const imageData = await handler.downloadImage(jsonData[i].img)

        const imgSize = handler.getImageSize(imageData.filePath)

        imgSizeList = [...imgSizeList, [imgSize.width, imgSize.height]]
    }
    xlsx.utils.sheet_add_aoa(sheet, imgSizeList, { origin: 'C2' })

    try {
        // xlsx.writeFile(excelFile, handler.getExcelFilePath)
        xlsx.writeFileAsync(handler.getExcelFilePath, excelFile, null, () => {
            console.log('Finished writing')
            callback(handler.getExcelFilePath)
        })
    } catch (e) {
        return { result: e }
    }
}

const handler = {
    downloadImage: async (url) => {
        const urlString = url.split('/')

        const filePath = `image/${urlString[urlString.length - 2]}-${urlString[urlString.length - 1]}`

        // 파일이 있는지 체크 없으면 다운로드
        if (fs.existsSync(filePath)) {
            console.log(`[image] existing path : ${filePath}`)
            return { filePath, msg: 'already exist', code: -1 }
        }

        const res = await axios.get(url, { responseType: 'stream' })

        const writeStream = fs.createWriteStream(filePath)

        res.data.pipe(writeStream)

        // 다운로드 완료 blocking용 콜백
        async function isFinished() {
            return new Promise((res, rej) => {
                writeStream.on('finish', () => {
                    console.log(`[image] download path : ${filePath}`)
                    res('finished')
                })
                writeStream.on('error', rej)
            })
        }

        await isFinished()

        return { filePath, msg: 'downloaded', code: 1 }
    },
    getExcelFilePath: 'test.xls',
    getImageSize: (filePath) => {
        let dimensions
        try {
            dimensions = sizeUtil(filePath)
        } catch (e) {
            console.log({ e })
            dimensions = { width: 'N/A', height: 'N/A' }
        }

        return {
            width: dimensions.width,
            height: dimensions.height
        }
    }
}

먼저 엑셀파일을 불러오고 sheet이름으로 원하는 sheet를 가져와서

 

데이터를 json형태로 변환해줍니다

 

그 후 image들을 특정 폴더에 저장합니다

(필자의 경우 해당 폴더의 image)

 

< 다운받은 이미지들 >

 

그 후 image-size 라이브러리를 이용해서 저장된 이미지 크기를 추출합니다

(링크 : https://www.npmjs.com/package/image-size)

 

(url로도 가능한데 필자는 다운로드까지 필요한 상황이어서 일단 다운로드 했습니다)

 

이미지 사이즈들을 다시 엑셀에 저장할 때 sheet_add_aoa로 배열형태로 원하는 위치에 넣어줍니다

 

필자의 경우 C2부터 밑으로 차례대로 넣어주었습니다

 

그 후 저장해서 확인해보면

 

< 출력된 사이즈 >

잘 출력된것을 확인할 수 있습니다

 

(전체 소스 코드 링크 : https://github.com/devmemory/node_xlsx_example)

 

사실 다른 페이지 스크래핑 및 데이터 추출용으로 만든건데 따로 기록 안해놓으면 까먹을거 같아서 포스팅 해봤습니다

반응형

'WEB' 카테고리의 다른 글

React - Without CRA  (0) 2022.05.23
React - redux toolkit example(createStore is deprecated)  (0) 2022.05.18
Node - Nodemailer example  (2) 2022.04.26
Node - Google analytics (feat. Chart.js, Excel)  (0) 2022.04.22
React, Node - Image upload example  (0) 2022.02.19
Comments