외부 사이트의 정보를 주기적으로 가져와 저장하는 프로그램을 만들게 됐다. cURL 로 HTML 을 받아 필요한 부분을 잘라내는 것 자체는 어렵지 않았다. 어려운 것은 어떻게 가져올지를 정하는 쪽이었다.
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 에 사이트가 수집 여부를 밝혀 둔다는 것도 알게 됐다.
정리
- 요청을 몰아 보내면 상대가 막는다.
HTTP429 가 그 신호다 - 빨리 끝내는 것과 부담을 안 주는 것은 반대 방향이다
- 간격은
mt_rand로 범위 안에서 흔든다. 정확히 같은 간격은 사람이 아니라는 표시다 array_chunk로 나누고 한가한 시간대에 돈다If-Modified-Since를 받아 주면 그것으로, 아니면 우리 쪽 기록으로 증분만 받는다- 목록의 날짜로 멈출 지점을 잡으면 500건이 30~50건이 된다
- 화면을 보고 가져오면 상대가 바꿀 때 깨진다
- 0건 알림과 형식 확인을 함께 둔다
- 개수만 세면 엉뚱한 값이 저장되는 것을 못 잡는다
robots.txt는 강제력은 없지만 상대가 밝힌 의사다