Laravel 웹 크롤러

spiderman-crawling
Standard

API 를 호출하여 JSON 이나 XML 포맷의 결과를 받아오는 경우가 아니라, HTML 로 구성된 웹페이지를 파싱해서 원하는 데이터 베이스를 구축할 필요가 있는 경우에는 웹크롤링 작업이 필요한데, 이번에는 간단한 웹 크롤러를 만들어본다.

이를 위해 사용한 패키지는 아래의 3가지 패키지이다.

  1. sunra/php-simple-html-dom-parser
  2. egeloen/google-map
  3. willdurand/geocoder

우선 composer.json 에 아래 라인을 추가하고, composer update 명령으로 디펜던시를 불러온다.

"sunra/php-simple-html-dom-parser": "v1.5.0",
"egeloen/google-map": "dev-master",
"willdurand/geocoder": "*",

아래는 골프장 정보를 모업체의 웹페이지 결과로 부터 받아서 추출하는 샘플 내용이다. 키포인트는 sunra/php-simple-html-dom-parser 의 사용으로, DOM element selector 가 jQuery 의 그것과 매우 유사하게 동작하여 매우 쉽게 이용할 수 있다는 것이다. 약간의 regular expression 과의 조합으로 훌륭한 web crawling 이 가능하다. 나머지 2개 패키지는 주소 값을 이용하여 그 위치의 위도/경도 값을 구해오는 geocoding 을 위해서 사용했다.

<?php

use Sunra\PhpSimple\HtmlDomParser;
use Ivory\GoogleMap\Services\Geocoding\Geocoder;
use Ivory\GoogleMap\Services\Geocoding\GeocoderProvider;
use Geocoder\HttpAdapter\CurlHttpAdapter;

class HomeController extends BaseController {

    public function home()
    {
        for($i = 1; $i <= 7; $i++) {
            $this->update($i);
        }
    }

    public function update($club_region)
    {
        $club_type = 3;

        // init the resource
        $url = sprintf("http://gcinfo.xxx.com/gz/searchcc/ajax/area/0000%d/1/1", $club_region);
        $postData = array(
            'pagesize' => '100',
            'membership' => $club_type + 1,
            'facility' => '0',
            'gfeeoption' => '0',
            'gfeefrom' => '0',
            'gfeeto' => '250000',
            'openfrom' => '1960',
            'opento' => '2014'
        );
        $postOutput = $this->postPage($url, $postData);

        $html = HtmlDomParser::str_get_html( $postOutput );
        $elements = $html->find('div[class=onc_cc_data]');

        $idx = 0;
        foreach ($elements as $element) {
            $idx++;

            $club_id = sprintf("%02d%02d%03d", $club_type, $club_region, $idx);
            echo $club_id;
            echo "\n";

            echo $club_type;
            echo "\n";

            echo $club_region;
            echo "\n";

            $href = $element->find('a', 0)->href;

            $pos = strripos($href, '/');
            $webidx = substr($href, $pos + 1);

            $url = 'http://gcinfo.xxx.com'.$href;
            $url2 = 'http://gcinfo.xxx.com/gz/ajax/html/searchcc/home/'.$webidx;
            $url3 = 'http://gcinfo.xxx.com/gz/ajax/html/searchcc/booking/'.$webidx;

            $info = trim($element->find('a', 1)->plaintext);
            $ret = preg_match("/\[(.+)\]\s+(\([A-Z]+\))([^|]+)\s+\|\s+(.+)/", $info, $matches);

            if (!$ret)
                dd('Fatal error. Can\'t match with the regular expression.');

            echo $matches[2];
            echo "\n";

            $region = $matches[1]; 
            echo $matches[1];
            echo "\n";

            $name = $matches[3]; 
            echo $matches[3];
            echo "\n";

            $holes = intval($matches[4]);
            echo intval($matches[4]);
            echo "\n";

            $info = trim($element->find('dd.course_name', 0)->plaintext);
            $info = str_replace(' ', '', $info);
            $courses = ($info) ?: 'n/a';
            echo $courses;
            echo "\n";

            $info = $element->find('dd.infophone', 0)->plaintext;
            $phone = trim(str_replace('문의전화 ', '', $info));
            echo ($phone);
            echo "\n";

            //
            // sub page
            //

            $getOutput = $this->getPage($url);
            $html2 = HtmlDomParser::str_get_html( $getOutput );

            $address = trim($html2->find('div.detail_gc table td', 0)->plaintext);
            echo ($address);
            echo "\n";

            $geocoder = new Geocoder();
            $geocoder->registerProviders(array(
                new GeocoderProvider(new CurlHttpAdapter()),
            ));
            $response = $geocoder->geocode($address);

            $latitude = 0;
            $longitude = 0;

            $results = $response->getResults();
            foreach ($results as $result) {
                $location = $result->getGeometry()->getLocation();
                $latitude = $location->getLatitude();
                $longitude = $location->getLongitude();
            }

            $detail = $html2->find('div.detail_gc a.btn_go_homepage', 0)->href;
            $webpage = ($detail) ?: 'n/a';
            echo $webpage;
            echo "\n";

            //
            // sub page
            //

            $getOutput2 = $this->getPage($url3);
            $html3 = HtmlDomParser::str_get_html( $getOutput2 );

            $weekday_fee = 0;
            $weekend_fee = 0;
            $cart_fee = 0;
            $caddie_fee = 0;

            if ($html3->find('div.greenfee_data td', 0)) {

                $detail = trim($html3->find('div.greenfee_data td', 0)->plaintext);
                $detail = trim(str_replace(',', '', $detail));
                $weekday_fee = intval($detail);
                echo $weekday_fee;
                echo "\n";

                $detail = trim($html3->find('div.greenfee_data td', 1)->plaintext);
                $detail = trim(str_replace(',', '', $detail));
                $weekend_fee = intval($detail);
                echo $weekend_fee;
                echo "\n";

                $detail = trim($html3->find('div.rounding_info li', 0)->plaintext);
                $detail = preg_replace("/\s+/", '', $detail);
                $detail = trim(str_replace('카트피:', '', $detail));
                $detail = trim(str_replace(',', '', $detail));
                $cart_fee = intval($detail);
                echo $cart_fee;
                echo "\n";

                $detail = trim($html3->find('div.rounding_info li', 1)->plaintext);
                $detail = preg_replace("/\s+/", '', $detail);
                $detail = trim(str_replace('캐디피:', '', $detail));
                $detail = trim(str_replace(',', '', $detail));
                $caddie_fee = intval($detail);
                echo $caddie_fee;
                echo "\n";

            } else {
                echo "0\n0\n0\n0\n";
            }

            // write into the DB table here
        }
    }


    public function postPage($url, $data) {
        // init the resource
        $ch = curl_init();

        curl_setopt_array($ch, array(
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $data,
            CURLOPT_FOLLOWLOCATION => true
        ));

        $output = curl_exec($ch);

        // free
        curl_close($ch);

        return $output;
    }


    public function getPage($url) {
        // init the resource
        $ch = curl_init();

        curl_setopt_array($ch, array(
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
        ));

        $output = curl_exec($ch);

        // free
        curl_close($ch);

        return $output;
    }
}