Skip to content
isdnetworks
Go back

몰아 보내면 막힌다

외부 사이트의 정보를 주기적으로 가져와 저장하는 프로그램을 만들게 됐다. cURLHTML 을 받아 필요한 부분을 잘라내는 것 자체는 어렵지 않았다. 어려운 것은 어떻게 가져올지를 정하는 쪽이었다.

Table of contents

Open Table of contents

쉬지 않고 돌렸더니 막혔다

처음 만든 것은 이랬다.

$urls = getTargetUrls();       // 대상 목록
foreach ($urls as $url) {
    $html = file_get_contents($url);
    $data = parse($html);
    save($data);
}

목록이 수백 개였고 반복문은 쉬지 않고 돈다. 몇 분 만에 응답이 느려지더니 나중에는 아예 오지 않았다. HTTP 상태가 429 로 바뀌어 있었다.

내 쪽 문제가 아니라 상대가 막은 것이었다. 짧은 시간에 요청을 몰아 보내면 막히는 것이 당연한데 그 생각을 못 했다.

foreach ($urls as $url) {
    $html = file_get_contents($url);
    // ...
    sleep(2);
}

요청 사이에 쉬게 하니 다시 됐다. 다만 500개를 가져오면 1000초가 걸린다. 주기적으로 돌려야 하는데 한 회차가 17분이면 곤란했다.

판단 기준 — 빠른 것과 부담 사이

빨리 끝내는 것과 상대에게 부담을 안 주는 것은 반대 방향이었다. 둘 다 만족하는 값은 없어서 어디에 맞출지 정해야 했다.

정한 것
요청 간격1~3초 (고정이 아니라 범위 안에서 변동)
한 번에 가져오는 양100건씩 나눠서
실행 시각새벽 (상대 서버가 한가한 시간)
실패하면즉시 재시도하지 않고 다음 회차로

간격을 mt_rand 로 흔든 것은 같은 간격으로 꼬박꼬박 오는 요청이 사람이 아니라는 것을 너무 잘 보여주기 때문이었다. array_chunk 로 100건씩 나누고 cron 으로 새벽에 돌게 했다.

바뀐 것만 가져오게 했다

간격을 두니 느려졌고 느려지니 가져올 양 자체를 줄여야 했다. 처음에는 매번 전체를 다시 받았는데 500건 중 실제로 바뀌는 것은 몇십 건뿐이었다.

표준 방식은 If-Modified-Since 를 보내고 304 가 오면 건너뛰는 것이다. 그런데 그 사이트는 Last-Modified 를 안 줬다. 그래서 마지막으로 가져온 시각을 우리 쪽에 적어 두고 그 뒤에 바뀐 것만 받게 했다.

목록 페이지에서 날짜 확인
  → 마지막 수집 시각보다 오래된 항목이 나오면 거기서 중단
  → 그 뒤로는 전부 이미 가진 것이므로 볼 필요 없음

목록 화면에 날짜가 있어서 그것으로 걸렀다. 전체 500건이 아니라 새로 바뀐 30~50건만 받으면 됐다. 회차당 요청 수가 크게 줄어서 간격을 두고도 시간이 감당됐다.

상대가 바꾸면 깨진다

한 달쯤 잘 돌다가 어느 날 아무것도 들어오지 않았다. 상대 HTML 구조가 바뀌어 내가 잘라내던 자리에 다른 것이 들어 있었다. 약속된 형식이 아니라 화면을 보고 가져오는 방식의 약점이다. 상대는 나에게 알려줄 이유가 없다.

그래서 두 가지를 넣었다. 가져온 개수가 0이면 알리게 하고 가져온 값이 예상한 모양인지도 검사했다. 뒤의 것이 특히 필요했다. 구조가 조금만 바뀌면 엉뚱한 값이 정상처럼 저장되기 때문이다. 개수만 세면 그 경우를 잡지 못한다.

어디까지 해도 되는지는 기술보다 어려웠다. 로그인해야 보이는 것은 가져오지 않는다. 상대 서버에 부담이 될 만큼 자주 요청하지 않는다. 가져온 것을 그대로 다시 보여주지 않는다. 이 정도로 두고 있다. robots.txt 에 사이트가 수집 여부를 밝혀 둔다는 것도 알게 됐다.

정리


Share this post on:

Previous Post
압축을 풀었더니 파일명이 깨졌다
Next Post
알림을 줄였더니 다음 건이 묻혔다