Skip to content
isdnetworks
Go back

API가 없어서 화면을 읽었다

경기 결과를 가져와야 하는데 그 사이트에는 API 가 없었다. HTML 을 읽어서 값을 뽑는 방법밖에 남지 않았다. 다만 그것을 바로 시작하지는 않았다.

Table of contents

Open Table of contents

사전 준비 — 다른 길부터 물어봤다

화면을 읽는 것은 마지막 수단이라 먼저 세 가지를 확인했다.

- 제공하는 API 가 있는가        → 없다
- 데이터를 파일로 받을 수 있는가 → 협의 필요, 시간이 걸린다
- 이용 약관에 수집 금지가 있는가 → 확인함

세 번째를 확인하지 않고 시작하면 안 되는 일이었다. 약관과 robots.txtCrawl-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 로 받을 수 있게 되면 이 배치를 없앤다.
그때까지 화면 구조가 크게 바뀌면 다시 만들지 말고 먼저 협의 상태를 확인한다.

두 달 뒤에 파일로 받게 되어 배치를 지웠다. 임시로 만든 것을 언제 없앨지 적어 두지 않으면 그것이 영구가 된다.

정리


Share this post on:

Previous Post
내가 안 건드렸는데 값이 바뀌어 있었다
Next Post
세션에 있던 언어