https://opendata.hira.or.kr/op/opc/selectColumnCodeList.do?tblId=TBOMA270&pageIndex=1
고객에게 어디사는지 정보를 받으면 그 위치의 병원만 보여주고 싶다.
그럴려면 위치 정보를 고유 code로 바꿔야하는데, redux toolkit에 무작정 하드코딩하려다가 너무 많아서 "그래! 크롤링 나도 해보는거야!"에서 시작해서 도전하게 되었다.
결과는 꼬박 하루가 걸렸지만, 성공했다...^^
나처럼 node와 React로 정보를 어떻게 주고받을지 고민하는 분들을 위하여 작성 go!
step1. 내가 원하는 정보 크롤링 해보자
크롤링을 해보기 위해 참고한 영상은 이것이다.
https://www.youtube.com/watch?v=xbehh8lWy_A
- 우선 axios와 cheerio(치리오)를 다운받아준다.
npm install axios
npm install cheerio
이 2개를 다운받았다면, 당신은 이미 반 정도 크롤링을 완료한 것이나 다름없다 ^^
내가 원하는 페이지의 전체 html을 크롤링하려면 이렇게 쓰면 된다.
const getHTML = async() => {
try{
return await axios.get("https://opendata.hira.or.kr/op/opc/selectColumnCodeList.do?tblId=TBOMA270&pageIndex=1")
}catch(err){
console.log(err);
}
}
get부분에 내가 크롤링하고 싶은 링크를 적으면 된다.
정말 쉽죠?...^^
저 링크의 페이지가 총 6개가 있어서 6번의 크롤링을 진행하는 함수를 만들어주었다.
const axios = require("axios");
const cheerio = require("cheerio");
const getHTML = async() => {
try{
return await axios.get("https://opendata.hira.or.kr/op/opc/selectColumnCodeList.do?tblId=TBOMA270&pageIndex=1")
}catch(err){
console.log(err);
}
}
const getHTML2 = async() => {
try{
return await axios.get("https://opendata.hira.or.kr/op/opc/selectColumnCodeList.do?tblId=TBOMA270&pageIndex=2")
}catch(err){
console.log(err);
}
}
const getHTML3 = async() => {
try{
return await axios.get("https://opendata.hira.or.kr/op/opc/selectColumnCodeList.do?tblId=TBOMA270&pageIndex=3")
}catch(err){
console.log(err);
}
}
const getHTML4 = async() => {
try{
return await axios.get("https://opendata.hira.or.kr/op/opc/selectColumnCodeList.do?tblId=TBOMA270&pageIndex=4")
}catch(err){
console.log(err);
}
}
const getHTML5 = async() => {
try{
return await axios.get("https://opendata.hira.or.kr/op/opc/selectColumnCodeList.do?tblId=TBOMA270&pageIndex=5")
}catch(err){
console.log(err);
}
}
const getHTML6 = async() => {
try{
return await axios.get("https://opendata.hira.or.kr/op/opc/selectColumnCodeList.do?tblId=TBOMA270&pageIndex=6")
}catch(err){
console.log(err);
}
}
근데 잘보면 링크 뒤에 페이지 숫자만 바껴서 요령껏 반복문 쓰면 코드를 비약적으로 줄일 수 있을 것 같기도 하다...^^
(나는 패스)
- 6 페이지 크롤링 결과를 한 리스트에 모두 담아준다.
let codes_info = [];
const parsing = async () => {
let html = await getHTML();
let $ = cheerio.load(html.data);
let $codeList = $("#codeTbl>tbody>tr");
$codeList.each((idx, node)=>{
codes_info.push({
code: $(node).find("td:nth-child(3)").text(),
region : $(node).find("td:nth-child(4)").text(),
});
})
console.log(codes_info);
return codes_info;
}
codes_info라는 리스트에 모든 정보를 담아줄 것이다.
우선 1개 페이지의 크롤링 결과를 어떻게 담아내는지 확인하자!
getHTML()이라는 함수에는 1페이지의 크롤링 결과가 담겨져있다.
우리는 전체 html에서 우리가 필요한 code나 지역이름을 가져와야한다. 그렇기에 cheerio를 써주는 것이다.
일단 위 페이지 구조를 보면 table(id = codeTbl)아래 tbody > tr> td로 이루어져 있다.
우리가 필요한 것은 tr 아래 3~4번 td이기 때문에
[부분 코드]
let $ = cheerio.load(html.data);
let $codeList = $("#codeTbl>tbody>tr");
$codeList.each((idx, node)=>{
codes_info.push({
code: $(node).find("td:nth-child(3)").text(),
region : $(node).find("td:nth-child(4)").text(),
});
})
이런식으로 가져온 것이다.
잘 모르겠지만, $codeList에 #codeTbl만 쓰고, find 부분에 tbody > tr > td:nth-child(3) 이런식으로 써주기도 해봤는데,
하나의 코드, 하나의 지역이름으로 이뤄진 object가 아닌 전체 코드, 전체 지역이름이 띄어쓰기 없이 하나의 object만 가져다줘서 삽질 했던 경험이 있다. 여러분도 주의하시길...
let codes_info = [];
const parsing = async () => {
let html = await getHTML();
let $ = cheerio.load(html.data);
let $codeList = $("#codeTbl>tbody>tr");
$codeList.each((idx, node)=>{
codes_info.push({
code: $(node).find("td:nth-child(3)").text(),
region : $(node).find("td:nth-child(4)").text(),
});
})
html = await getHTML2();
$ = cheerio.load(html.data);
$codeList = $("#codeTbl>tbody>tr"); //코드 같다고 안치면 안되고 꼭 현페이지에서 위치 다시 잡아줘야함
$codeList.each((idx, node)=>{
codes_info.push({
code: $(node).find("tr>td:nth-child(3)").text(),
region : $(node).find("tr>td:nth-child(4)").text(),
});
})
html = await getHTML3();
$ = cheerio.load(html.data);
$codeList = $("#codeTbl>tbody>tr"); //코드 같다고 안치면 안되고 꼭 현페이지에서 위치 다시 잡아줘야함
$codeList.each((idx, node)=>{
codes_info.push({
code: $(node).find("tr>td:nth-child(3)").text(),
region : $(node).find("tr>td:nth-child(4)").text(),
});
})
html = await getHTML4();
$ = cheerio.load(html.data);
$codeList = $("#codeTbl>tbody>tr"); //코드 같다고 안치면 안되고 꼭 현페이지에서 위치 다시 잡아줘야함
$codeList.each((idx, node)=>{
codes_info.push({
code: $(node).find("tr>td:nth-child(3)").text(),
region : $(node).find("tr>td:nth-child(4)").text(),
});
})
html = await getHTML5();
$ = cheerio.load(html.data);
$codeList = $("#codeTbl>tbody>tr"); //코드 같다고 안치면 안되고 꼭 현페이지에서 위치 다시 잡아줘야함
$codeList.each((idx, node)=>{
codes_info.push({
code: $(node).find("tr>td:nth-child(3)").text(),
region : $(node).find("tr>td:nth-child(4)").text(),
});
})
html = await getHTML6();
$ = cheerio.load(html.data);
$codeList = $("#codeTbl>tbody>tr"); //코드 같다고 안치면 안되고 꼭 현페이지에서 위치 다시 잡아줘야함
$codeList.each((idx, node)=>{
codes_info.push({
code: $(node).find("tr>td:nth-child(3)").text(),
region : $(node).find("tr>td:nth-child(4)").text(),
});
})
console.log(codes_info);
return codes_info;
}
parsing();
6개 크롤링 정보는 html변수만 바꿔주며 불러오고, codes_info 리스트에 전부 합쳐줬다.
parsing() 함수를 호출해 데이터 파싱을 진행한다.
콘솔에 합친 리스트를 출력해보면, 이렇게 잘 가져오는 것을 확인할 수 있다.
step2. 크롤링 결과를 React에서 써보자!
크롤링 결과를 다짜고짜 바로 리액트에 적용할 순 없다.
서버로 만들어주고, 리액트가 서버 주소로 요청하게 해야한다. 마치 스프링 부트에서 만든 api에 정보를 요청할 때처럼 말이다.
- 우선 express를 다운받아준다.
크롤링 결과를 내보내주는 서버로 만들기 위해 express를 다운한다.
npm install express
- 상단에 express, path를 설정해준다.
const express = require('express');
const path = require('path');
const app = express();
- 데이터를 호출할 서버를 설정한다.
app.listen(9000, function(){
console.log('listening on 9000')
})
app.get("/api/crwal", async(req, res)=>{
res.send([{
codes_info,
}]);
});
9000번 포트를 사용해 파싱 데이터를 호출할 수 있도록 했다.
http://localhost:9000/api/crwal
그리하여 위 주소를 호출하면, 파싱 데이터가 올 수 있도록 했다.
파싱 데이터를 우리에게 전달해줄 수 있게 res.send를 이용한다.
이렇게 말이다 ㅎㅎ
부분 부분 코드만 보고 이해를 했다면 이젠 전체 코드를 보며 이해해보시길!
step3. 전체 코드
[scraper.js]
const axios = require("axios");
const cheerio = require("cheerio");
const express = require('express');
const path = require('path');
const app = express();
const getHTML = async() => {
try{
return await axios.get("https://opendata.hira.or.kr/op/opc/selectColumnCodeList.do?tblId=TBOMA270&pageIndex=1")
}catch(err){
console.log(err);
}
}
const getHTML2 = async() => {
try{
return await axios.get("https://opendata.hira.or.kr/op/opc/selectColumnCodeList.do?tblId=TBOMA270&pageIndex=2")
}catch(err){
console.log(err);
}
}
const getHTML3 = async() => {
try{
return await axios.get("https://opendata.hira.or.kr/op/opc/selectColumnCodeList.do?tblId=TBOMA270&pageIndex=3")
}catch(err){
console.log(err);
}
}
const getHTML4 = async() => {
try{
return await axios.get("https://opendata.hira.or.kr/op/opc/selectColumnCodeList.do?tblId=TBOMA270&pageIndex=4")
}catch(err){
console.log(err);
}
}
const getHTML5 = async() => {
try{
return await axios.get("https://opendata.hira.or.kr/op/opc/selectColumnCodeList.do?tblId=TBOMA270&pageIndex=5")
}catch(err){
console.log(err);
}
}
const getHTML6 = async() => {
try{
return await axios.get("https://opendata.hira.or.kr/op/opc/selectColumnCodeList.do?tblId=TBOMA270&pageIndex=6")
}catch(err){
console.log(err);
}
}
let codes_info = [];
const parsing = async () => {
let html = await getHTML();
let $ = cheerio.load(html.data);
let $codeList = $("#codeTbl>tbody>tr");
$codeList.each((idx, node)=>{
codes_info.push({
code: $(node).find("td:nth-child(3)").text(),
region : $(node).find("td:nth-child(4)").text(),
});
})
html = await getHTML2();
$ = cheerio.load(html.data);
$codeList = $("#codeTbl>tbody>tr");
$codeList.each((idx, node)=>{
codes_info.push({
code: $(node).find("tr>td:nth-child(3)").text(),
region : $(node).find("tr>td:nth-child(4)").text(),
});
})
html = await getHTML3();
$ = cheerio.load(html.data);
$codeList = $("#codeTbl>tbody>tr");
$codeList.each((idx, node)=>{
codes_info.push({
code: $(node).find("tr>td:nth-child(3)").text(),
region : $(node).find("tr>td:nth-child(4)").text(),
});
})
html = await getHTML4();
$ = cheerio.load(html.data);
$codeList = $("#codeTbl>tbody>tr");
$codeList.each((idx, node)=>{
codes_info.push({
code: $(node).find("tr>td:nth-child(3)").text(),
region : $(node).find("tr>td:nth-child(4)").text(),
});
})
html = await getHTML5();
$ = cheerio.load(html.data);
$codeList = $("#codeTbl>tbody>tr");
$codeList.each((idx, node)=>{
codes_info.push({
code: $(node).find("tr>td:nth-child(3)").text(),
region : $(node).find("tr>td:nth-child(4)").text(),
});
})
html = await getHTML6();
$ = cheerio.load(html.data);
$codeList = $("#codeTbl>tbody>tr");
$codeList.each((idx, node)=>{
codes_info.push({
code: $(node).find("tr>td:nth-child(3)").text(),
region : $(node).find("tr>td:nth-child(4)").text(),
});
})
console.log(codes_info);
return codes_info;
}
parsing();
app.listen(9000, function(){
console.log('listening on 9000')
})
app.get("/api/crwal", async(req, res)=>{
res.send([{
codes_info,
}]);
});
실행은 위 파일이 있는 경로까지 이동한다.
나같은 경우는 src > pages > health 아래에 scraper.js (서버 구축한 거)가 있어서 일단 그쪽으로 이동했다.
cd src
cd pages
cd health
그리고 서버를 실행해줬다.
node scraper.js
그 후 크롬 등 주소창에 http://localhost:9000/api/crwal 이 주소를 치면, 데이터가 잘 뜰 것이다.
React에선 axios.get("http://localhost:9000/api/crwal").then().catch()
구문으로 불러오면 잘 불러와질 것이다!
내 생의 첫 크롤링, 너무 재밌다.
20000!!!
'BE > node.js' 카테고리의 다른 글
[node.js] 정말 간단한 서버 구축 방법 (0) | 2023.05.04 |
---|