안녕하세요. 오늘은 자바스크립트로 크롤링 하는 법을 알아보려고 해요. 먼저 많은 분들이 궁금해하시는 웹 스크래핑과의 차이를 설명드릴게요. 이 둘은 서로 관련되어 있지만, 각각 다른 목적과 과정을 가지고 있는 개념이라고 이해하시면 되는데요.
크롤링과 웹 스크래핑의 차이
크롤링이란 인터넷상의 웹 페이지들을 방문하고 정보를 수집하는 과정이에요. 크롤러(또는 스파이더)라 불리는 프로그램이 웹 페이지를 방문하고, 그 페이지에 있는 링크들을 따라가며 다른 페이지로 이동하면서 정보를 수집해요.
웹 스크래핑은 그 수집한 정보를 추출하고 원하는 형태로 가공하는 과정까지 포함한 개념입니다. 웹 스크래핑은 웹 크롤링을 기반으로 하지만, 더 많은 데이터 처리 작업이 포함되어 있어요.
간단한 예를 들어볼게요. 웹 크롤링을 통해 온라인 쇼핑몰의 상품 페이지들을 방문하고, 각 페이지의 상품 정보를 수집할 수 있어요. 그리고 웹 스크래핑을 사용하여 수집한 상품 정보들 중에서 필요한 부분만 추출하고, 원하는 형식으로 가공하여 텍스트 파일이나 데이터베이스에 저장하는 작업을 진행할 수 있죠.
주의사항
크롤링을 하기 전에 주의할 사항이 있습니다. 대상 웹사이트의 'robots.txt' 파일을 꼭 확인해 보세요. 이 파일은 웹 사이트가 크롤러에게 어떤 페이지를 수집할 수 있는지, 어떤 페이지는 금지되어 있는지에 대한 정보를 담고 있습니다.
/*
*파일위치: 도메인주소/robots.txt (예: naver.com/robots.txt)
*아래와 같은 형식으로 되어있음
*/
User-agent: * //모든 크롤러에 적용
Disallow: /new //new경로로 시작하는 페이지들은 허용안함
Disallow: /* //모든페이지 허용안함
Allow: /wiki //wiki경로로 시작하는 페이지들은 허용
또한 너무 빈번한 요청으로 웹 사이트의 서버에 부하를 주지 않도록 주의하세요. 요청 간격을 적절하게 조절해서 서버에 문제를 일으키지 않도록 해야 합니다. 대규모 웹사이트의 경우, 크롤링 요청에 제한을 두기도 하니 이 점도 참고해 주세요.
사전준비
자 이제 시작하겠습니다. 브라우저의 콘솔 창에서 실행하기 위해 이번에도 CORS 우회 프록시 서버를 이용할 거예요. 처음이신 분들은 아랫글을 참고해 주세요. 뭘 설치하거나 환경설정을 하는 건 아니고, 웹사이트에 들어가서 활성화만 해주면 끝이에요.
그리고 사용하시는 브라우저에서 F12 키를 눌러 개발자 도구를 열고, 콘솔 탭을 선택합니다. 웹 스크래핑 할 때와 마찬가지로 요청 헤더 정보인 User-Agent를 추가해야 해요. 혹시 추가하는 이유가 궁금하신 분들은 아랫글을 참고해 주세요.
크롤링 코드 작성
자바스크립트 코드는 웹 스크래핑 할 때와 크게 다르지 않아요. fetch를 사용해 웹 서버로부터 데이터를 요청하고 응답을 받습니다. 먼저 fetch가 잘 되는지 확인을 위해 링크는 따라 들어가지 않고 지정한 페이지에 대해서만 데이터를 수집하도록 할게요.
테스트는 위키백과 홈페이지로 하겠습니다. '/wiki' 경로로 시작하는 페이지들은 크롤러를 허용하고 있기 때문이에요!
//cors 우회 프록시 서버 URL
const proxyUrl = 'https://cors-anywhere.herokuapp.com/';
// 수집 대상 URL
const url = 'https://ko.wikipedia.org/wiki/%ED%8F%AC%ED%84%B8:%EC%9A%94%EC%A6%98_%ED%99%94%EC%A0%9C';
// 크롤링 함수
async function crawl(url) {
// 수집중인 URL
const decodedUrl = decodeURI(url);
console.log(`크롤링 ${decodedUrl} ...`);
// URL에서 데이터를 가져옴
const response = await fetch(proxyUrl + url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7'
}
});
// 응답 데이터를 문자열로 변환
const htmlString = await response.text();
// HTML 문자열을 파싱하여 DOM 객체 생성
const parser = new DOMParser();
const htmlDOM = parser.parseFromString(htmlString, 'text/html');
// 데이터 추출
const title = htmlDOM.querySelector('title').textContent;
// 페이지내의 텍스트만 가져오기
const content = htmlDOM.querySelector('#mw-content-text').textContent;
// 추출한 데이터에서 연속된 개행 문자를 하나의 공백으로 대체
const contentWithSingleNewlines = content.replace(/\n{2,}/g, '\n');
console.log(`Title: ${title}\nContent: ${contentWithSingleNewlines}\n`);
}
// 실행
crawl(url)
.then(() => {
console.log('데이터 수집 완료');
})
.catch(err => console.error(err));
실행 화면 ▼
해당 페이지 내 모든 텍스트를 수집한 결과입니다! 가져온 데이터를 분석하고 가공하면 유의미한 정보를 얻을 수 있는 거죠. 이번에는 해당 페이지의 링크들을 따라가며 그 페이지들까지 데이터를 수집해 볼게요.
재귀 호출을 사용하여 링크를 따라 들어가도록 합니다. 그리고 링크가 깊으면 시간이 너무 오래 걸리니 깊이를 지정해서 첫 번째 링크만 들어가도록 할게요.
//cors 우회 프록시 서버 URL
const proxyUrl = 'https://cors-anywhere.herokuapp.com/';
// 수집 대상 URL
const url = 'https://ko.wikipedia.org/wiki/%ED%8F%AC%ED%84%B8:%EC%9A%94%EC%A6%98_%ED%99%94%EC%A0%9C';
// 링크 깊이
const depth = 1;
// 딜레이 시간
const delayTime = 100;
// 수집한 데이터를 저장할 배열
const data = [];
// 크롤링 함수
async function crawl(url, depth) {
// 수집중인 URL
const decodedUrl = decodeURI(url);
console.log(`수집중 ${decodedUrl} ...`);
// URL에서 데이터를 가져옴
const response = await fetch(proxyUrl + url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7'
}
});
// 응답 데이터를 문자열로 변환
const htmlString = await response.text();
// HTML 문자열을 파싱하여 DOM 객체 생성
const parser = new DOMParser();
const htmlDOM = parser.parseFromString(htmlString, 'text/html');
try {
// 데이터 추출
const title = htmlDOM.querySelector('title').textContent;
const content = htmlDOM.querySelector('#mw-content-text').textContent;
// 추출한 데이터를 배열에 저장
data.push({
title,
content
});
} catch (e) {
console.log(e);
return;
}
// 링크 깊이가 0이면 함수 종료
if (depth === 0) {
return;
}
// 링크 추출
const links = htmlDOM.querySelectorAll('a');
for (const link of links) {
// href 속성이 없으면 건너뜀
if (!link.hasAttribute('href')) {
continue;
}
// href 속성값 추출
const href = link.getAttribute('href');
// 동일 도메인에 있는 URL만 수집
if (href.startsWith('https://ko.wikipedia.org') || href.startsWith('/wiki')) {
// 링크 추출 후 딜레이 시간만큼 대기한 후 재귀적으로 호출
await new Promise(resolve => setTimeout(resolve, delayTime));
const nextUrl = new URL(href, url).href;
// 링크 깊이가 0 이상인 경우에만 수집
if (depth > 0) {
await crawl(nextUrl, depth - 1);
}
}
}
}
// 실행
crawl(url, depth).then(() => {
console.log('크롤링 완료');
console.log(JSON.stringify(data, null, 2));
}).catch(err => console.error(err));
실행 화면 ▼
약 470개 정도의 링크 페이지가 있었지만, 테스트 예제에서는 10개의 페이지만 데이터를 수집해 오도록 했습니다. 또한 서버의 부담을 줄이기 위한 페이지당 수집 대기시간을 0.5초로 해놓았는데, 실제로 사용하실 땐 더 짧게 줄이셔도 됩니다. 10개 페이지인데도 수집된 데이터양 엄청 많네요! 위 화면에 보이는 데이터의 10배 정도가 더 있답니다. 데이터를 분석 후 필요한 텍스트만 추출하도록 해야 할 거 같아요.
오늘은 이렇게 자바스크립트로 크롤링 하는 코드를 만들어봤는데요. 다음 시간에는 크롤링으로 수집한 데이터를 분석하고 텍스트 파일로 저장하는 코드를 만들어볼게요! 오늘 알아본 주의사항들을 잘 기억하시고, 항상 웹사이트의 규칙을 존중하며 작업을 진행해 주세요. - 끝 -