2023. 5. 29. 15:46ㆍInternational Sign Lang 프로젝트/백엔드
이전 글을 읽고 와주시면 감사하겠습니다 :)
오늘의 WORK
오늘은 3 가지의 작업을 완료했습니다.
- world-map.html 에 존재하는 나라들의 국가코드(ISO 3166-1 alpha-2)를 모으기
- 해당 국가코드들의 풀네임을 매핑한 json 생성
- 이렇게 생성한 json 파일을 JavaScript로 DB에 넣기
world-map.html 에서 국가코드 긁어오기
저의 world-map.html 의 주된 구조는 다음과 같습니다.
<body>
<!-- 세계 지도 -->
<svg id="worldMap" version="1.1" width="1200" xmlns="http://www.w3.org/2000/svg"
style="overflow: hidden; position: relative; left: -0.5px;"
viewBox="0 0 1400 599.1070366699703" preserveAspectRatio="xMidYMid meet">
<!--원래 xMinYMin 이였음-->
<rect x="-4035.01" y="-1726.15" width="10090" height="4317.85" r="0" rx="0" ry="0"
fill="#ffffff" stroke="none" transform="matrix(0.6938,0,0,0.6938,0,0)"
stroke-width="1.4414285714285715"
style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); fill-opacity: 0;"
fill-opacity="0" />
<path fill="#88a4bc" stroke="#ffffff"
d="M1166.7,673.5H1162.6000000000001L1162.3000000000002,670.6L1161.7000000000003,667.7L1161.3000000000002,665.4000000000001L1162.7000000000003,658.3000000000001L1161.6000000000004,653.7L1159.4000000000003,644.7L1165.6000000000004,637.4000000000001L1167.3000000000004,632.8000000000001L1168.1000000000004,632.2L1169.0000000000005,628.4000000000001L1168.2000000000005,626.5000000000001L1168.6000000000006,621.7000000000002L1169.9000000000005,617.3000000000002L1170.3000000000006,609.1000000000001L1167.5000000000007,607.1000000000001L1164.8000000000006,606.6000000000001L1163.7000000000007,605.0000000000001L1161.1000000000008,603.7000000000002L1156.4000000000008,603.8000000000002L1156.2000000000007,601.4000000000002L1155.8000000000006,596.8000000000002L1173.0000000000007,591.5000000000002L1176.2000000000007,594.6000000000003L1177.7000000000007,594.0000000000002L1179.9000000000008,595.6000000000003L1180.1000000000008,598.2000000000003L1178.8000000000009,601.2000000000003L1179.000000000001,605.7000000000003L1182.500000000001,609.7000000000003L1184.400000000001,605.2000000000003L1186.900000000001,603.9000000000003L1186.800000000001,595.6000000000004L1184.600000000001,591.0000000000003L1182.700000000001,588.9000000000003H1182.3000000000009L1181.700000000001,581.6000000000004L1183.200000000001,575.5000000000003L1185.400000000001,575.3000000000003L1192.100000000001,577.1000000000003L1193.600000000001,576.3000000000003L1197.5000000000011,576.1000000000003L1199.600000000001,574.2000000000003L1203.0000000000011,574.3000000000003L1209.2000000000012,571.8000000000003L1213.800000000001,568.1000000000003L1214.7000000000012,570.9000000000002L1214.2000000000012,577.3000000000002L1214.7000000000012,583.0000000000002L1214.5000000000011,593.0000000000002L1215.300000000001,596.1000000000003L1213.400000000001,600.7000000000003L1211.000000000001,605.2000000000003L1207.3000000000009,609.2000000000003L1202.000000000001,611.6000000000003L1195.500000000001,614.7000000000003L1188.900000000001,621.6000000000003L1186.700000000001,622.8000000000003L1182.500000000001,627.4000000000003L1180.200000000001,628.8000000000003L1179.400000000001,633.4000000000003L1181.800000000001,638.3000000000003L1182.7000000000012,642.0000000000003V644.0000000000003L1183.7000000000012,643.6000000000004L1183.2000000000012,649.9000000000003L1182.1000000000013,652.9000000000003L1183.3000000000013,654.0000000000003L1182.3000000000013,656.7000000000004L1179.9000000000012,659.0000000000003L1175.2000000000012,661.1000000000004L1168.300000000001,664.6000000000004L1165.800000000001,667.0000000000003L1166.100000000001,669.7000000000004L1167.400000000001,670.1000000000004Z"
class="sm_state sm_state_MZ" opacity="1" stroke-opacity="1" stroke-width="3.78375"
stroke-linejoin="round" transform="matrix(0.6938,0,0,0.6938,0,0)"
style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0); opacity: 1; cursor: pointer; stroke-opacity: 1; stroke-linejoin: round; fill-opacity: 1;"
fill-opacity="1" />
수많은 path 태그들의 반복이고
path 태그들의 class 속성의 2 번째에 국가코드가 들어가 있습니다.
위 경우네는 st_state_MZ라고 되어 있고 이는 모잠비크의 국가 코드이죠!
즉, 저의 태스크는 다음으로 정의됩니다.
world-map.html의 모든 path 태그들의 2 번째 클래스를 가져오기
그리고 다음과 같이 함수를 짰습니다.
const { JSDOM } = jsdom;
const fs = require('fs');
function getCountryCodesFromHTML() {
return new Promise((resolve, reject) => {
fs.readFile('../html/world-map.html', 'utf8', (err, data) => {
if (err) {
reject(err);
} else {
const dom = new JSDOM(data, { resources: "usable", runScripts: "outside-only", url: "file://" });
const document = dom.window.document;
var paths = document.getElementsByTagName('path');
var countryCodes = [];
for (var i = 0; i < paths.length; i++) {
var classes = paths[i].className.baseVal.split(' ');
for (var j = 0; j < classes.length; j++) {
if (classes[j].startsWith('sm_state_')) {
var countryCode = classes[j].substring(9);
countryCodes.push(countryCode);
}
}
}
resolve(countryCodes);
}
});
});
}
프로미스 객체를 활용한 이유는
위 함수가 실행된 이후 바로 크롤링을 해서
국가 코드의 풀네임을 매핑하기 위해서입니다!
잠깐 jsdom 이란? 왜 사용하나요
jsdom is a pure-JavaScript implementation of many web standards, notably the WHATWG DOM and HTML Standards, for use with Node.js. In general, the goal of the project is to emulate enough of a subset of a web browser to be useful for testing and scraping real-world web applications.
[해석]
'jsdom'은 JavaScript로 구현된 가상 DOM입니다.
이 라이브러리는 브라우저 환경이 없는 곳에서도 HTML과 DOM API를 사용하게 해 주므로,
브라우저 기반 라이브러리나 프레임워크를 테스트하거나 웹 스크래핑을 수행하는 데 유용합니다.
[출처] : https://github.com/jsdom/jsdom
위의 코드는 크롤링을 수행하는 게 아니라
실행시키지 않은 html 파일을 열어 모든 path 태그의 2 번째 class를 읽어야 한다는 것입니다.
하지만 html 파일도 결국엔 문자로 이루어진 파일이기 때문에 DOM 이 생성되지 않아 클래스의 개념이 없습니다.
즉 class라는 개념을 컴퓨터는 알 수가 없다는 거죠.
이 문제를 해결하기 위해서 가상, 즉 가짜 DOM을 하나 만들어야 합니다.
이를 도와주는 라이브러리가 바로 JSDOM인 거죠!
추출한 국가 코드의 풀네임 매핑
이제 html 파일에서 국가 코드들을 추출했다면
매핑을 할 풀네임을 가져와야겠죠?
이제는 크롤링을 수행해야 할 시간입니다.
이를 위해 cheerio를 사용했습니다!
The fast, flexible, and elegant library for parsing and manipulating HTML and XML.
[해석] : 빠르고, 유연하며, 서버를 위해 특별히 설계된 핵심 jQuery의 경량화된 구현체
[출처] : https://github.com/cheeriojs/cheerio
jQuery 스타일의 문법으로 DOM 조작 및 탐색을 가능하게 하고
브라우저 환경이 없어도 HTML 문서를 파싱하고 조작할 수 있습니다.
이런 cheerio 를 사용해 구현한 코드는 다음과 같습니다!
const axios = require('axios')
const cheerio = require('cheerio')
function getCountryNamesFromWeb(codes) {
return axios.get('https://www.iban.com/country-codes')
.then((response) => {
if (response.status === 200) {
const html = response.data;
const $ = cheerio.load(html);
let countryNames = {};
$('table').first().find('tbody tr').each(function (index, element) {
const code = $(element).find('td').eq(1).text();
let name = $(element).find('td').eq(0).text();
// Remove "(the)" from the country name
name = name.replace(/\(the\)/g, '').trim();
if (codes.includes(code)) {
countryNames[code] = name;
}
});
return countryNames;
}
});
}
파라미터로 받는 codes는 위에 나온 countryCodesFromHTML 함수의 리턴값입니다.
https://www.iban.com/country-codes에 쿼리를 날리고, 이에 대한 결과물을 cheerio로 load 해서
크롤링을 수행합니다.
만약 크롤링한 국가코드가 world-map.html 상에 없는 코드라면 포함시키지 않았습니다!
둘이 합친 코드
getCountryCodesFromHTML()
.then(getCountryNamesFromWeb)
.then(countries => {
fs.writeFile("../json/countries.json", JSON.stringify(countries, null, 2), "utf8", function (err) {
if (err) {
console.error(err)
} else {
console.log("Country codes have been successfully saved to countries.json")
}
})
})
.catch(console.error);
아까서 말했던 것처럼 먼저 world-map.html 에서 국가코드를 추출한 후
크롤링을 수행해서 존재하는 국가코드에 대해서만 풀네임을 저장하고,
마지막에 이 결과물을 coutries.json 에다가 저장했습니다!
아직 끝나지 않았습니다... DB에 넣기
일일이 넣을까...?라는 인간적(?)인 고민에 빠지기도 했지만
모름지기 개발자라면 자동화에 목매야 하는 법.
이 또한 JavaScript로 한번 구현해 봤습니다.
const fs = require('fs');
const mysql = require('mysql2');
// Create MySQL connection
const connection = mysql.createConnection({
host: "localhost",
user: "root",
password: "비밀",
database: "ISL",
})
// Connect to MySQL
connection.connect((err) => {
if (err) throw err
console.log("Connected to the database.")
// Read countries.json
fs.readFile("src/json/countries.json", "utf8", (err, data) => {
if (err) throw err
const countries = JSON.parse(data)
// Insert each country into the database
for (let code in countries) {
let fullName = countries[code]
let query = "INSERT INTO country (code, full_name) VALUES (?, ?)"
connection.query(query, [code, fullName], (err, result) => {
if (err) throw err
console.log(`Inserted ${fullName} into the database.`)
})
}
})
})
먼저 mysql 커넥션을 생성해 주고,
이 커넥션 안에서 JSON.parse를 통한 countries.json을 파싱해
차례대로 데이터베이스 안에다가 넣어줬습니다!
TIL
오랜만에 프로미스 객체를 다루느라 시간이 오래걸렸다...
그리고 JavaScript 로 외부에서 DB에다가 데이터를 넣어본 것도 처음이라
역시 개발의 마스터의 길은 멀구나,,, 싶습니다...
위 글을 보신다면 가서 스타 한번씩만 부탁드립니다!
그리고 언제나 피드백은 환영입니다!
'International Sign Lang 프로젝트 > 백엔드' 카테고리의 다른 글
Day 12-2 : 솔직히 본인 테이블에 더미 데이터 1,000 개 넣어본 사람? 없으면 보자, 프로시저로 더미 데이터 생성하기 (0) | 2023.06.08 |
---|---|
Day 12-1 CSRF 와 CORS 는 매우 연관이 깊다?! (0) | 2023.06.08 |
Day 11 : 싸우자 해커야! CSRF, HttpOnly, CSP 대응하기 (0) | 2023.06.02 |
Day 9 : 에러나면 손모가지 날라가붕께 (2) | 2023.05.30 |
Day 7 : 왜 굳이 웹 서버 프레임워크를 사용해야 하나? (0) | 2023.05.25 |