매시업 폐쇄를 요청하는 오피넷

오후에 'OPINET에서 연락'이 왔다. "현재 운영하고 있는 오즈용 주유소 최저가 검색 사이트가 불법이므로 사이트를 닫아달라"는 것이었다. 연락을 받고 바로 닫으면 문제 삼지 않겠다는 것이었다. OPINET에서 데이타를 가져오는 방식이 인터넷 브라우저로 해당 사이트를 접속해서 데이타를 가져오는 방식과 같다. 또 사이트가 일반 영리 목적의 회사가 운영하는 사이트가 아니다. 따라서 그 정보 공영성을 고려할 때 별 문제가 없을 것으로 생각했다. <사진: 바보를 표현할 때 주로 사용하는 짤>

죽어 버린 오피넷

어제 올린 글에서 알 수 있지만 요즘 기름 값이 너무 비싸다. 따라서 기름을 조금이라도 싸게 사기위해 주유소 최저가 검색 사이트를 이용한다. 특히 경유값은 너무 비싸다. 휘발유보다 비싼 곳도 이제는 많다. 이렇다 보니 경유 대신에 등유를 넣고 있는 사람도 있한다. 높은 기름값의 파고를 짐작할 수 있는 이야기이다. 너무 올라 버린 기름값을 주유소 사이의 경쟁을 통해 잡기위해 정부와 석유공사는 OPINET이라는 주유소 최저가 검색 사이트를 운영하기 시작했다.

높은 기름값에 대한 관심을 반영하듯 OPINET은 첫날 30만명, 다음날 36만명이 방문했다고 한다. 나 역시 차를 가지고 있다. 또 충주는 전국에서 기름값이 가장 싸다고 한다. 그런데 그 충주에서도 1700원 미만의 주유소는 찾기 힘들다. 그래서 OPINET을 사용했다. 그런데 LGT OZ용 주유소 최저가 검색 서비스라는 글에서 설명한 것처럼 너무 느렸다. 시도 및 구를 선택하는데 AJAX를 사용하기 때문에 시도 및 구를 선택하는 것도 힘들었다.

또 요즘 주로 사용하고 있는 터치웹폰으로는 아예 검색조차할 수 없었다. 그래서 만든 것이 OPINET의 데이타를 이용해서 터치웹폰으로 접속할 수 있는 주유소 최저가 검색 사이트였다. OPINET 데이타를 이용하지만 OPINET 사이트 보다 빠르고 터치웹폰이나 iPod Touch로도 접속할 수 있다. 또 인터페이스도 상당히 간단하기 때문에 하루 100명 정도의 사용자가 사용해 왔다.

그런데 어제 갑자기 검색 서비스가 동작하지 않았다. 확인해 보니 OPINET이 죽은 것이었다. 서버의 부하 때문인지 다른 문제 때문인지 모르겠지만 명색이 국가에서 만든 사이트가 너무 느리고 너무 잘 죽는 것 같아 죽어버린 주유소 가격 비교 사이트라는 글을 올렸다.

매시업 폐쇄를 요청하는 오피넷

그리고 오후에 'OPINET에서 연락'이 왔다. 현재 운영하고 있는 오즈용 주유소 최저가 검색 사이트가 불법이므로 사이트를 닫아달라는 것이었다. 연락을 받고 바로 닫으면 문제 삼지 않겠다는 것이었다. OPINET에서 데이타를 가져오는 방식이 인터넷 브라우저로 해당 사이트를 접속해서 데이타를 가져오는 방식과 같다. 또 사이트가 일반 영리 목적의 회사가 운영하는 사이트가 아니다. 따라서 그 정보공영성을 고려할 때 별 문제가 없을 것으로 생각했다.

그러나 OPINET의 생각은 다른 것 같았다. 어차피 OPINET을 이용하는 것보다 쿼리의 수가 작고 접속자 수가 많지 않아 그대로 용인해 줄것을 요청했지만 사이트를 닫아 달라는 답변만 받았다. 또 정 그렇다면 내가 만든 소스를 저작권을 요청하지 않고 줄테니 대신 서비스 해달라고 했지만 이 역시 거절당했다. 통화한 사람에게 그런 권한이 없는 듯했다.

다만 '소스를 참조해서 대국민 서비스를 하는 경우 저작권을 요구하지 않을 것인지 물었다'. 다른 사람에게 도움이 되도록 만든 서비스이고 만드는데 오랜 시간을 소모한 것도 아니기 때문에 일단 사이트를 내리고 나중에 소스를 제공하기로 했다.

소스 공개

내가 만든 서비스이고 잠시 였지만 유용하게 사용하던 서비스이기 때문에 닫는 것이 조금 아쉬웠다. 또 어차피 OPINET에 제공하기로 한 소스라 간단히 프로그램에 대한 설명만 붙여서 인터넷에 공개하기로 했다. 소스 공개목적이지만 또 다른 목적은 어제 만든 dp.SyntaxHighLighter 플러그인[1]시험할 목적도 있는 셈이다.

<?
// 최저가 주유소를 검색하는 폼을 출력
// 배열로 저장된 시 이름과 구 이름을 자바 스크립트로 출력
function printForm () {
    global $cName;
    echo "<script language="javascript">
    function changeCity(a,b) {
        switch(a) {";
            foreach($cName as $k=>$v) {
                echo "\n\t\tcase '$k':\n";
                $i=1;
                $len=count($v);
                if(!preg_match("/lgtelecom/",$_SERVER[HTTP_USER_AGENT])) echo "\t\t\tb.length=$len;\n";
                foreach($v as $city) {
                    echo "\t\t\tb.options[$i]=new Option('$city');\n";
                    $i++;
                }
                echo "\t\t\tb.disabled = false;";
                echo "\n\t\t\tbreak;\n";
            }
            echo "\n\t\tdefault:\n\t\t\tb.disabled = true;\n\t\t\tbreak;";
    echo "\n\t\t}\n}\n</script>";

    echo "<div align='center'><h2>주유소 최저가 검색</h2>\n<form name='searchOil' action="/oz">\n";
    echo "<select name='cityName' onChange="javascript:changeCity(this.value, statName);">\n";
    echo "\t<option value="">특별/광역/시도</option>\n";

    foreach($cName as $k=>$v) {
        echo "\t<option value='$k'>$k</option>\n";
    }

    echo "</select>\n";
    echo "<select name="statName">\n\t<option value="">시/군/구</option>\n</select>\n";
    echo "<input type='text' name='streetName' size="8">";
    echo "<input type='submit' value='찾기'>";
    echo "<script language="javascript">changeCity(document.searchOil.cityName.value, document.searchOil.statName);</script>\n";
    echo "<input type='hidden' name='op' value='search'>";
    echo "</form></div>\n";
}

// HTTP를 이용해서 다른 사이트의 웹 페이지를 가져오는 함수
function getQuery($host_ip, $port, $query) {
    // 참조 URL을 검사할 수 있기 때문에 참조 URL을 가짜로 만듬
    $referer = "http://".불_SERVER["HTTP_HOST"].불_SERVER["REQUEST_URI"];

    // fsockopen 함수를 이용해서 쿼리를 날림
    $fp = @fsockopen($host_ip, $port,  &$errno, &$errstr, 10);
    if(!$fp) {
        echo "$errstr: $errno <br>\n";
    }else {
        @fwrite($fp, "GET $query HTTP/1.1\r\nHost: $host_ip\r\nUser-Agent: DoA/1.1\r\nReferer: $referer\r\nConnection: Close\r\nAccept-Encoding: gzip, deflate\r\n\r\n");
        while(!@feof($fp)) {
            $list .= @fgets($fp, 1024);
        }
    }
    @fclose($fp);

    // 받은 데이터를 헤더와 몸체로 분리
    list($header, $body) = preg_split("/\r\n\r\n/", $list, 2);

    // gzip으로 압축된 경우 패킷을 분리
    $body_array=preg_split("/\r\n[0-9a-fA-Z][0-9a-fA-Z]*\r\n/", "\r\n$body");

    // 불필요한 부분을 버림
    array_pop($body_array);
    array_shift($body_array);

    foreach($body_array as $value) {
        $body1.=$value;
    }

    // gzip으로 압축된 부분만 추출
    // 파일로 저장한 뒤 gzip의 압축을 품
    // gzip 인코딩을 사용하지 않으면 속도가 너무 느리기 때문에 gzip을 사용
    $body=substr($body1, 10, strlen($body1)-1);
    fileSave("a.gz", $body);
    $body=gzinflate($body);
    return $body;
}

// 파일을 저장하는 함수
function fileSave($file, $data) {
    $f=fopen($file, "w");
    fputs($f, $data);
    fclose($f);
}

// 주유소 정보를 배열로 저장한 뒤 반환
function getBankInfo($body) {
    $list=explode(';', $body);
    foreach($list as $value) {
        if(preg_match("/'([0-9][0-9]*)','([^']*)','([^']*)','([^']*)',([.0-9][.0-9]*),([.0-9][.0-9]*),'([0-9])','([YN])','([-,0-9,][-,0-9,]*)','([-,0-9,][-,0-9,]*)','([-,0-9,][-,0-9,]*)','([-,0-9,][-,0-9,]*)','([-,0-9,][-,0-9,]*)','([-,0-9,][-,0-9,]*)','([YN])'/", $value, $oil_info)) {
            $code=$oil_info[1];
            $oil[$code][name]=$oil_info[2];
            $oil[$code][bank]=$oil_info[3];
            $oil[$code][date]=$oil_info[4];
            $oil[$code][x]=$oil_info[5];
            $oil[$code][y]=$oil_info[6];
            $oil[$code][flag]=$oil_info[7];
            $oil[$code][yes]=$oil_info[8];
            $oil[$code][advance]=str_replace(',', '', $oil_info[9]);
            $oil[$code][gasolin]=str_replace(',', '', $oil_info[10]);
            $oil[$code][cost1]=str_replace(',', '', $oil_info[11]);
            $oil[$code][lightoil]=str_replace(',', '', $oil_info[12]);
            $oil[$code][lampoil]=str_replace(',', '', $oil_info[13]);
            $oil[$code][boiler]=str_replace(',', '', $oil_info[14]);
            $oil[$code][no]=$oil_info[15];
        }
    }
    return $oil;
}

// 배열을 이용해서 주유소 목록 생성
function printBankList($oil) {
    global $icon;
    echo "<table border=1 cellpadding=2 cellspacing=0 align=center>\n";
    echo "<caption>$_GET[cityName] - $_GET[statName]</caption>\n";
    echo "<tr><th colspan=2>주유소</th><th>고급</th><th>휘발유</th><th>기타</th><th>경유</th><th>등유</th><th>보일러</th><th>날짜</th></tr>\n";
    foreach($oil as $key=>$value) {
        $bank = $value[bank];
        $img=$icon[$bank];
        list($xcoord)=explode('.', $value[x]);
        list($ycoord)=explode('.', $value[y]);
        $name=urlencode($value[name]);
        echo "<tr><td><img src='$img' align=absmiddle></td><td><a href='http://maps.naver.com/?x=${xcoord}00&y=${ycoord}00&title=$name' target=_blank>$value[name]</a></td><td align=center>$value[advance]</td><td align=center>$value[gasolin]</td><td align=center>$value[cost1]</td><td align=center>$value[lightoil]</td><td align=center>$value[lampoil]</td><td align=center>$value[boiler]</td><td>$value[date]</td></tr>\n";
    }
    echo "</table>\n";
}

// 도움말
function help() {
// OPINET 폐쇄 요청으로 프로그램의 기능을 죽이고 공지를 출력하도록 바꿈
    echo <<<___HELP___
<div style="text-align: center"><div style="padding:10px; background-color:#F7F7F7; border:1px solid #CCCCCC; text-align: justify; width=600px">
<fieldset><legend> 공지 </legend><b>OPINET의 요청으로 이 오즈용 주유소 최저가 검색 서비스를 폐지</b>합니다. 공공 사이트이고 인터넷에 올라온 데이타를 프로그램을 이용해서 자동으로 수집한 것이기 때문에 큰 문제가 없을 것으로 생각했습니다. 그러나 이 것 역시 불법으로 사이트 폐지 요청이 들어와서 어쩔 수 없이 폐지하게 되었습니다. 그 동안 이용해 주신 것에 대해 감사드립니다. 아울러 석유공사 측에 이 프로그램의 소스를 제공해서 빠른 시일 내에 유사 검색 서비스가 생길 수 있도록 하겠습니다.</fieldset>
<p>
<!--<ul>
    <li><a href="http://www.opinet.co.kr/">석유공사</a>(<a href="http://www.opinet.co.kr/">www.opinet.co.kr</a>)의 주유소 최저 가격 검색 데이타를 이용합니다. 다만 이 사이트가 너무 느리고 <b>오즈에서 사용할 수 없기 때문</b>에 오즈용으로 만든 것입니다.
<p>
    <li>지도 데이타는 오즈 전용폰에 최적화 되어 있는 네이버 지도를 사용했습니다.
<p>
    <li><b>사용법</b>
    <ol>
        <li> 특별/광역/시도에서 시 또는 도를 선택하면 시/군/구 활성화됩니다.
        <li>시/군/구에서 시, 군 또는 구를 선택하고 찾기 단추를 누릅니다.
        <li>검색 결과를 동으로 제한하려면 세번째 입력 창에 동의 이름을 입력합니다. 한가지 주의할 것은 대치동, 대치1동처럼 동을 정확히 입력해야 합니다.
        <li>나타난 목록에서 주유소 이름을 클릭하면 해당 주유소의 지도로 이동합니다.
    </ol>
<p>
    <li><b>개선점</b>
    <ul>
        <li>정렬 기능은 추가하고 있습니다.
        <li>개선 사항은 제 블로그의 <a href="https://offree.net/entry/LGT-OZ-Searching-Oil-Bank">LGT OZ용 주유소 최저가 검색 서비스</a>라는 글에 남겨 주시기 바랍니다.
    </ul>
</ul>-->
</div></div>
___HELP___;
}

// html 머릿말 출력
function html_header() {
    echo <<<___HEADER___
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>OZ: 주유소 최저가 검색</title>
<meta http-equiv="content-type" content="text/html; charset=euc-kr">
<meta name="author" content="QAOS.com | 주유소 최저가 검색">
<meta name="copyright" content="Copyright (c) 1996-2005 by QAOS.com">
<meta name="keywords" content="OZ, 오즈, 주유소 최저가 검색, LH2300, 아르고폰, 터치웹폰">
<meta name="description" content="오즈 전용 단말기로 주유소 최저가를 검색할 수 있도록 한 서비스입니다.">
<meta name="robots" content="noarchive">
</head>
<body >
___HEADER___;
}

// html 꼬릿말 출력
function html_footer() {
$GLOBALS['adl_count_params']=true; 
@include_once $GLOBALS['HTTP_SERVER_VARS']['DOCUMENT_ROOT'].'/twatch_include/logger.php';

    echo "<br> <div style='font-size: 15px; text-align: center'>Powered by <a href="https://qaos.com/" target=_blank>QAOS.com</a></div>";
    echo "</body></html>";
}

global $cName, $icon;
// 시 정보와 구정보를 배열로 생성
// OPINET은 AJAX로 처리하고 있지만 속도가 너무 느리며
// 새로운 동이 자주 생기는 것이 아니므로 하드 코딩함
$cName=array(
    '서울특별시'=>array("강남구","강동구","강북구","강서구","관악구","광진구","구로구","금천구","노원구","도봉구","동대문구","동작구","마포구","서대문구","서초구","성동구","성북구","송파구","양천구","영등포구","용산구","은평구","종로구","중구","중랑구"),
    '부산광역시'=>array("강서구","금정구","기장군","남구","동구","동래구","부산진구","북구","사상구","사하구","서구","수영구","연제구","영도구","중구","해운대구"),
    '대구광역시'=>array("남구","달서구","달성군","동구","북구","서구","수성구","중구"),
    '인천광역시'=>array("강화군","계양구","남구","남동구","동구","부평구","서구","연수구","옹진군","중구"),
    '광주광역시'=>array("광산구","남구","동구","북구","서구"),
    '대전광역시'=>array("대덕구","동구","서구","유성구","중구"),
    '울산광역시'=>array("남구","동구","북구","울주군","중구"),
    '경기도'=>array("가평군","고양시덕양구","고양시일산동구","고양시일산서구","과천시","광명시","광주시","구리시","군포시","김포시","남양주시","동두천시","부천시소사구","부천시오정구","부천시원미구","성남시분당구","성남시수정구","성남시중원구","수원시권선구","수원시영통구","수원시장안구","수원시팔달구","시흥시","안산시단원구","안산시상록구","안성시","안양시동안구","안양시만안구","양주시","양평군","여주군","연천군","오산시","용인시기흥구","용인시수지구","용인시처인구","의왕시","의정부시","이천시","파주시","평택시","포천시","하남시","화성시"),
    '강원도'=>array("강릉시","고성군","동해시","삼척시","속초시","양구군","양양군","영월군","원주시","인제군","정선군","철원군","춘천시","태백시","평창군","홍천군","화천군","횡성군"),
    '충청북도'=>array("괴산군","단양군","보은군","영동군","옥천군","음성군","제천시","증평군","진천군","청원군","청주시상당구","청주시흥덕구","충주시"),
    '충청남도'=>array("계룡시","공주시","금산군","논산시","당진군","보령시","부여군","서산시","서천군","아산시","연기군","예산군","천안시","청양군","태안군","홍성군"),
    '전라북도'=>array("고창군","군산시","김제시","남원시","무주군","부안군","순창군","완주군","익산시","임실군","장수군","전주시덕진구","전주시완산구","정읍시","진안군"),
    '전라남도'=>array("강진군","고흥군","곡성군","광양시","구례군","나주시","담양군","목포시","무안군","보성군","순천시","신안군","여수시","영광군","영암군","완도군","장성군","장흥군","진도군","함평군","해남군","화순군"),
    '경상북도'=>array("경산시","경주시","고령군","구미시","군위군","김천시","문경시","봉화군","상주시","성주군","안동시","영덕군","영양군","영주시","영천시","예천군","울릉군","울진군","의성군","청도군","청송군","칠곡군","포항시남구","포항시북구"),
    '경상남도'=>array("거제시","거창군","고성군","김해시","남해군","마산시","밀양시","사천시","산청군","양산시","의령군","진주시","진해시","창녕군","창원시","통영시","하동군","함안군","함양군","합천군"),
    '제주특별자치도'=>array("서귀포시","제주시"));

// 사용되는 아이콘의 경로
$icon=array('HDO'=> "/imgs/hdo.png", 'SKE'=>'/imgs/ske.png', 'GSC'=>"/imgs/gsc.png", 'SOL'=>'/imgs/sol.png', 'ETC'=>'/imgs/etc.png');

if($_GET[op] == '' ) {
// 옵션이 억으면 HTML 폼을 출력
    html_header();
    echo "<br />";
    printForm();
    help();
    html_footer();
}else {
// 옵션이 있으면 검색 결과를 출력
    html_header();
    echo "<br />";
    printForm();

    $host='www.opinet.co.kr';
    $timestamp=time();
    $cityName=urlencode($_GET[cityName]);
    $statName=urlencode($_GET[statName]);
    $streetName=urlencode($_GET[streetName]);

    $query="/gis.do?cmd=gis.region.list&timestamp=$timestamp&searchType=C&orderName=&sidoNm=$cityName&sigunNm=$statName&dongNm=$streetName&pollDivCd=&osNm=";

    $body=getQuery($host, 80, $query);

    if(preg_match("/데이터가 없습니다./", $body)) {
        echo "<div style='text-align: center; font-size: 14px; color: red'>데이터가 없습니다.</div>";
        exit;
    }

    $body=preg_replace("|.*(<script[^>]*>(.*)</script>).*|is","\\2", $body);
    $body=preg_replace("/(parent.JsInvokInMap|parent.mapframe.BeginAddPOI()|parent.mapframe.AddPOI|\"|parent.mapframe.EndAddPOI\('Y'\))/is", '', $body);
    $body=str_replace("('", "'", $body);
    $body=str_replace("')'", "'", $body);
    $body=preg_replace("/(^;|;;;*)/", '', $body);

    $oil = getBankInfo($body);
    printBankList($oil);
    html_footer();
}
?>

관련 글타래


  1. 구글 크롬에서 http:// 프로토콜에 대해 경고 메시지를 띄우면서 '도아의 세상사는 이야기'도 https://를 지원하도록 바꿨다. 문제는 HTTP를 HTTPS로 바꾼 뒤 dp.SyntaxHighLighter 플러그인을 사용하는 페이지가 너무 늦게 떴다. 결국 마크다운의 코드 기능을 사용하도록 블로그 글을 편집하고 dp.SyntaxHighLighter 플러그인을 사용 중지했다(2018년 9월).