초급 개발자에게 프로그래밍 학습을 위한 콘텐츠로 크롤링만큼
"안녕~ 세상아!"를 출력하는 프로그램 코드 100번 작성하는 것보다 자신이 애용하는 웹사이트에 있는 내용을 가져와서 출력하는 프로그램 작성하는 게 훨씬 재밌기 때문이다.
언어를 가장 효율적으로 습득하는 방법 두 가지는 "자주 사용"하고 "재밌어야" 한다는 것이다. 프로그램 언어도 컴퓨터와 대화하기 위한 수단이기 때문에 실용성과 재미없이는 러닝 커브를 넘기가 쉽지 않다.
때문에 필자는 초급 개발자가 새로운 언어를 배우거나 스킬업이 필요할 때 크롤링 개발을 추천한다.
그런데,
단순히 학습 목적이 아니라 실제 크롤링 서비스를 개발을 하다 보면 고려해야 할 요소들이 나타난다. 가령 로그인이 필요한 페이지를 크롤링해야 할 경우도 있고, 화면(Client)에서 특정 정보를 암호화해서 넘겨줘야지만 다음 사이트 크롤링이 가능한 경우도 있다.
다행인 건, 우리가 브라우저를 통해서 보는 페이지는 크롤링을 통해 가져올 수 있기 때문에 문제가 발생해도 해결방법은 존재한다. 단, 그 해결방법을 찾는데 생각보다 시간과 노력이 많이 필요하다는 거. 그.. 그래도 해결했을 때의 짜릿함은 꽤나 크다. -_ -;
본 글에서는 필자를 고생시켰던 크롤링 개발 경험 중 Redirect 관련 이야기를 해 볼까 한다.
웹사이트를 크롤링하다가 의도하지 않았던 웹페이지가 크롤링되는 경우가 종종 발생한다. 가령 자신은 A라는 웹페이지를 크롤링했는데 받아본 결과는 B 웹페이지인 경우다.
다양한 이유가 존재하지만, 정상적인 입력값을 사용했음에도 의도하지 않은 페이지가 크롤링 되는 경우는 대부분 HTML의 상태 코드가 30X(page redirection)로 설정된 경우다.
서버에서 HTML 헤더의 location 정보를 설정해서 브라우저에서 해당 페이지를 재요청하게 하는 방법이 보편적으로 사용된다.
재밌는 일은 크롤링할 때 리다이렉션 페이지가 문제없이 넘어가는 경우가 많다. 대부분의 크롤링 대상은 사용자가 브라우저를 통해 보는 최종 모습이기 때문에...(심지어 개발자 조차 리다이렉션이 있는 줄 모르는 경우도 있다.) 그런데 간혹, 크롤링 대상이 POST 작업이고 리다이렉션 되는 페이지가 접근이 불가한 상황이면 404 에러를 발생하며 의도하지 않게 수행될 수 있다.
예를 들어, API 사용을 위해서 엑세스 토큰을 받는 경우를 가정해 보자.
티스토리에서 토큰 제공 API를 호출하면 로그인 페이지로 리다이렉트 되고, 로그인에 성공하면 개발자 사이트에 등록된 리다이렉션 페이지(토큰을 받아서 처리하는 URL)로 이동한다. 보통 리다이렉션 페이지는 개발서버에서 동작하기 때문에 접근에 제한이 있거나 로컬(localhost)에서 동작한다. 그러다 보니, 리다이렉션 페이지에 접근이 불가해서 404 에러가 발생한다.
게다가 토큰을 리다이렉션 페이지에 앵커(#)로 설정하는데, 앵커 정보는 서버에서 핸들링할 수 없기 때문에 평소 하던 대로 크롤링하면 죽었다 깨어나도 엑세스 토큰을 받을 수 없다.
OAuth2 API를 정식 지원하는 포털, SNS의 경우는 크게 문제가 없으나, 지금 사용 중인 티스토리는 겉으론 API를 제공하지만 제대로 되어있지.... 아... 할말하않 =0=.
해서, 이런 경우 크롤링을 위해 사용하고 있는 HTTP Request 라이브러리의 자동 리다이렉트 옵션을 해제하고 사용해야 한다. 프로그램 언어별로 조금씩 다르겠지만, 대부분의 라이브러리는 리다이렉트가 자동으로 동작하는 것이 기본 속성이거나 횟수의 제한으로 되어있다.
필자가 크롤링에 주로 사용하는 NodeJs의 Request 모듈의 경우 아래와 같은 다양한 속성을 제공한다.
followRedirect - follow HTTP 3xx responses as redirects (default: true). This property can also be implemented as function which gets response object as a single argument and should return true if redirects should continue or false otherwise.
followAllRedirects - follow non-GET HTTP 3xx responses as redirects (default: false)
followOriginalHttpMethod - by default we redirect to HTTP method GET. you can enable this property to redirect to the original HTTP method (default: false)
maxRedirects - the maximum number of redirects to follow (default: 10)
removeRefererHeader - removes the referer header when a redirect happens (default: false). Note: if true, referer header set in the initial request is preserved during redirect chain.
followRedirect 옵션 사용한 예는 다음과 같다.
const getAuth = function () {
let options = {
method: 'GET',
url: 'https://www.tistory.com/oauth/authorize',
qs: {
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
response_type: RESPONSE_TYPE
},
jar: cookieJar,
followRedirect: false,
headers: {
'cache-control': 'no-cache'
}
}; return new Promise(function (resolve, reject) {
request(options, function (error, response) {
if (!error) {
resolve(response);
} else {
reject(error);
}
});
});
};
getAuth()
.then(res => {
console.log("###### getAuth Result");
console.log(JSON.stringify(res, null, 2));
console.log(res.headers.location);
})
.catch(err => {
console.error('###### Error 발생');
console.error(err);
});
정리하면,
크롤링에서 요청하지 않은 페이지가 수집되거나 404 에러가 발생할 경우 리다이렉트 여부를 의심하자.
리다이렉트 최종 결과물이 아닌 중간의 페이지 크롤링을 위해서 리다이렉트 옵션을 비활성화 하자.
리다이렉션 URL을 호출해주는 API의 경우, 반드시 리다이렉트 옵션 비활성화 하자.
앵커(#)는 서버에서 처리 불가능하니 리다이렉트 옵션 비활성화 한 뒤 header의 location 정보를 확인하자.