경기 결과를 가져와야 하는데 그 사이트에는 API 가 없었다. HTML 을 읽어서 값을 뽑는 방법밖에 남지 않았다. 다만 그것을 바로 시작하지는 않았다.
Table of contents
Open Table of contents
사전 준비 — 다른 길부터 물어봤다
화면을 읽는 것은 마지막 수단이라 먼저 세 가지를 확인했다.
- 제공하는 API 가 있는가 → 없다
- 데이터를 파일로 받을 수 있는가 → 협의 필요, 시간이 걸린다
- 이용 약관에 수집 금지가 있는가 → 확인함
세 번째를 확인하지 않고 시작하면 안 되는 일이었다. 약관과 robots.txt 의 Crawl-delay 까지 읽었다. 두 번째는 요청해 두었다. 파일로 받게 되면 화면 읽기를 그만둘 수 있기 때문이다.
화면 구조를 봤다
읽어야 할 화면은 이렇게 생겼다.
<table class="result">
<tr><td class="team">A팀</td><td class="score">3</td>...
class 이름이 붙어 있어서 그것으로 잡았다.
$doc = new DOMDocument();
@$doc->loadHTML($html);
$xpath = new DOMXPath($doc);
$rows = $xpath->query("//table[@class='result']//tr");
정규식으로 잡을 수도 있지만 HTML 은 구조가 복잡해서 안 맞는 경우가 많다. DOMXPath 로 구조를 따라가는 쪽이 덜 깨졌다.
깨질 것을 전제하고 만들었다
화면은 언제든 바뀐다. 바뀌면 XPath 가 아무것도 못 잡고 조용히 빈 값이 들어온다.
if ($rows->length === 0) {
error_log('[crawl] 결과 표를 못 찾았다. 화면 구조가 바뀌었을 수 있다.');
return false;
}
if ($rows->length < 5) {
error_log('[crawl] 행이 ' . $rows->length . '개뿐이다. 평소 20개 안팎.');
}
0건이면 확실히 문제이고 다섯 개 미만이면 적어도 이상하다. 평소 스무 개 안팎이라는 것을 알고 있어야 이상한 것을 알아본다.
값이 예상한 모양인지도 봤다.
$score = trim($cells->item(1)->textContent);
if (!preg_match('/^\d+$/', $score)) {
error_log('[crawl] 점수가 숫자가 아니다: ' . $score);
continue;
}
경기 취소 같은 글자가 들어오는 경우가 있었다. 숫자를 기대하고 그대로 넣으면 값이 0으로 들어간다.
받은 것도 그대로 남겼다.
file_put_contents("/data/crawl/raw/" . date('Ymd_His') . ".html", $html);
며칠치만 두고 오래된 것은 지웠다. 안 남기면 그때 무엇이 왔느냐는 물음에 답할 수가 없다.
실제로 이걸로 한 번 찾았다. 사이트가 점검 중이라 안내 화면이 오고 있었는데 그 화면에도 table 이 있어서 이상한 값이 들어갔다. HTTP 응답이 200 이라 받는 쪽에서는 정상으로 보였다. 503 이었으면 거기서 걸렀을 것이다.
상대 서버와 그만둘 조건
요청 사이에 간격을 뒀다.
foreach ($dates as $d) {
$html = fetch($d);
sleep(2);
}
한 번에 몰아서 부르면 막히고 상대에게도 부담이 된다. 시간대도 골라 cron 으로 새벽에 돌게 했다.
0 4 * * * /usr/bin/php /app/batch/crawl.php
어디서 오는 요청인지도 밝혔다.
$opts = ['http' => ['header' => "User-Agent: ...\r\n"]];
User-Agent 를 상대가 보고 판단할 수 있게 하는 것이 맞다고 봤다.
이 방식은 임시라서 언제 그만둘지를 코드 옆에 적어 뒀다.
데이터 제공을 요청해 두었다. 파일이나 API 로 받을 수 있게 되면 이 배치를 없앤다.
그때까지 화면 구조가 크게 바뀌면 다시 만들지 말고 먼저 협의 상태를 확인한다.
두 달 뒤에 파일로 받게 되어 배치를 지웠다. 임시로 만든 것을 언제 없앨지 적어 두지 않으면 그것이 영구가 된다.
정리
- 화면을 읽는 것은 마지막 수단이다
API와 파일 제공과 약관과robots.txt를 먼저 확인한다- 정규식보다
DOMXPath로 구조를 잡는다.HTML은 정규식으로 안 맞는 경우가 많다 - 화면은 언제든 바뀌고 바뀌면 조용히 빈 값이 들어온다
- 0건과 평소보다 적은 것을 둘 다 본다
- 평소 건수를 알아야 이상한 것을 알아본다
- 값이 예상한 모양인지 본다. 글자가 들어오면 조용히 0이 된다
- 받은 원본을 며칠 남긴다. 점검 안내 화면도 200 으로 온다
- 간격을 두고 시간대를 고르고
User-Agent에 출처를 밝힌다 - 언제 그만둘지 적지 않으면 임시가 남는다