2013년 7월 7일 일요일

PHP, 스누피 클래스를 이용해서 현재 주가 긁어오기

PHP로 작성된 강력한 외부 소켓 클래스인 스누피(Snoopy)클래스를 이용하면 웹사이트 내용을 쉽게 스크래핑 해 올 수 있습니다. 제 경우에는 투자를 위해서 스누피 클래스를 사용하고 있습니다. 현재 주가를 실시간으로 긁어와서 제가 생각하는 목표 가격대와 괴리율이 얼마인지 한눈에 볼 수 있어서 편리합니다. 또 각 포털사이트와 신문사의 기사 중 관심 키워드가 포함된 신문 기사도 실시간으로 긁어와서 구독하고 있기 때문에 편리합니다.


스누피 클래스를 응용하는 방법은 많겠지만 오늘은 포털 사이트 다음에서 제공하는 실시간 주가를 스크래핑해서 가져오는 방법을 간단하게 소개 드리겠습니다.

스누피 클래스 설치


스누피 클래스는 따로 설치할 필요없이 다운로드만 받아 프로젝트 내에 복사하면 됩니다. 스누피 클래스를 다운로드 받을 수 있는 주소는 다음과 같습니다.

다운로드 : http://sourceforge.net/projects/snoopy/files/latest/download

다운로드를 받고 나서 압축을 풀면 Snoopy-1.2.4라는 폴더가 생깁니다. 이전 1.2.3버전에서는 구문 오류가 몇 군데 있었던 것 같은데 이번 버전에서는 고쳐진 것 같네요. Snoopy-1.2.4 디렉토리를 열어보면 아래와 같은 파일들이 있습니다.


스누피 클래스가 지원하는 기능이나 사용법에 대해서 궁금하신 부분이 있으면 README 파일을 읽어보시면 됩니다. 가장 핵심적인 파일은 Snoopy.class.php 파일 입니다. 저 파일을 우리가 만들고자 하는 프로젝트 내에 위치시키면 바로 사용할 수 있는 준비가 됩니다. 설치라고 할 것도 없네요. 이것으로 스누피 클래스가 준비되었습니다.

페이지 스크래핑 테스트(네이버 메인 페이지 긁어오기)


그럼 스누피 클래스를 이용해서 웹페이지 하나를 테스트로 스크래핑 해 보겠습니다. 일단 test.php 파일을 하나 생성해서 HTML의 <body>태그안에 다음과 같은 코드를 작성해 보았습니다.
include_once 'Snoopy.class.php';
$snoopy = new snoopy;
$snoopy->fetch("http://www.naver.com");
$txt = $snoopy->results;
print_r($txt);
구글 블로그에서 Syntax Highligher를 쓰니 일부 구문이 깨지거나 왜곡 되네요. 감안하여 코드를 읽어주시면 감사 드리겠습니다.

스누피 클래스를 현재 프로젝트에 인클루드 하여(이 예제의 경우 스누피 클래스 파일이 동일한 폴더내에 있다고 가정), $snoopy에 새 객체를 만들어서 담았습니다. fetch 구문을 이용해서 네이버의 메인페이지를 모두 긁어오라고 구문 한 줄을 추가했고, 긁어온 구문은 $txt변수에 담아서 이를 print_r 해서 화면에 뿌려주는 간단한 구문입니다.

정말 너무너무 간단하고 강력합니다. 아래는 위 구문을 실행해서 네이버 메인페이지를 스크래핑해서 긁어온 결과입니다. (스타일 시트는 당연히 안 긁어옵니다.)


너무너무 쉽고 간단하게 네이버 메인 페이지의 내용들을 스크래핑 해 왔습니다. 정말 쉽고 강력합니다.

원하는 부분만 가져오기 (주가만 긁어오기)


웹페이지 전체를 긁어오지 않고 원하는 부분만 가져올 수 없을까요? 당연히 그것도 가능합니다. 아래 화면은 포털 사이트 다음에서 제공하는 주식 종목 정보 페이지 입니다.


'수출포장'이라는 종목 정보 페이지입니다. 이 종목 페이지 내용을 전부 긁어오고 싶은게 아니라, 현재가만 가져오려고 합니다. 그러려면 먼저 이 페이지의 URL을 알아야겠습니다.

다음 증권 종목 페이지 주소 : 
http://stock.daum.net/item/main.daum?code=002200

주소에서 가장 마지막 숫자 6자리는 종목코드입니다. 이 예제에서는 종목코드 '002200'번  즉, 수출포장 종목의 시세를 가져오도록 하겠습니다.

현재가 부분을 가져 오려면 HTML DOM 구조를 파악해야 합니다. 해당 페이지 소스 보기를 하거나 개발자 도구를 이용해서 현재가 부분의 마크업을 살펴보도록 하겠습니다.


우리가 가져 올 현재가는 <em class="curPrice up"></em> 이라는 방법으로 마크업이 돼 있네요. 보니까 시세는 curePrice라는 클래스를 사용하는 것 같고, 뒤에 up, down 으로 하락인지, 상승인지, 보합인지 색상을 입혀주는 클래스가 추가로 적용돼 있는 것 같습니다.

그럼 정규식을 이용해서 저런 마크업을 찾아 저기 있는 숫자만 빼내 오도록 하겠습니다. 참, 동일한 페이지 내에서 동일한 마크업을 사용하는 부분이 다른 곳에도 있다면 스크래핑이 여러번 되므로 저렇게 마크업 한게 저 부분 뿐인지 꼭 확인해야 합니다.
include_once 'Snoopy.class.php';
$snoopy=new snoopy;
$o="";
$snoopy->fetch("http://stock.daum.net/item/main.daum?code=002200");
$txt=$snoopy->results;
$rex="/\<em class=\"curPrice.+\"\>(.*)\<\/em\>/";
preg_match_all($rex,$txt,$o);
print_r($o[0][0]);
아까 네이버 메인 페이지 전체를 긁어온 코드를 조금 수정했습니다. fetch는 다음 증권 페이지 수출포장 종목을 바라 보도록 했습니다.

그리고 $rex 변수에 담기는 정규식을 잘 봐주세요. <em>태그에 curePrice라는 클래스를 가지고 있는 부분을 찾는 구문입니다. <em>~</em>태그 사이에 숫자만 뽑아와 $rex 변수에 담았습니다.

그리고 PHP의 정규 함수인 preg_match_all 함수에 이를 담아 정규식을 해석하고 print_r로 가져 온 가격 정보를 화면에 뿌려주는 간단한 코드입니다. 이 코드를 실행한 결과는 아래 화면과 같습니다.


정규식으로 추출한 <em>태그가 잘 스크래핑 돼 가져와 졌음을 확인할 수 있습니다. 이런식으로 스누피 클래스를 응용하면 다양한 벙법으로 나만의 편리한 도구를 만들어서 사용할 수 있습니다.

만약 DB에 종목 모드 정보를 넣어 놓고 동적으로 종목별 가격 정보를 얻어 오고 싶다면 다음과 같이 코딩하면 됩니다.

먼저 아래와 같은 코드를 작성해서 DB에 연결을 해야겠죠.
// DB 연결
$connect = mysql_connect("localhost","root","root") or die("DB에 연결할 수 없습니다.<br>");
mysql_query('set names utf8');
mysql_select_db("stocks",$connect);

// 종목 목록 가져오기
$query="SELECT*FROM list";
$result=mysql_query($query,$connect);
DB연결에 성공하고 종목 목록 정보를 가져 왔다면 while 문을 이용해서 종목 개수 즉, DB의 row 개수 만큼 반복을 돌립니다.
while ($row=mysql_fetch_array($result)) {
  // 여기에 스크래퍼에서 가져 온 마크업 정보를 뿌려주면 되겠죠.
}
그리고 while 문 안에,
include_once 'Snoopy.class.php';
$snoopy=new snoopy;
$o="";
$snoopy->fetch("http://stock.daum.net/item/main.daum?code=".$row[s_code]);
$txt=$snoopy->results;
$rex="/\<em class=\"curPrice.+\"\>(.*)\<\/em\>/";
preg_match_all($rex,$txt,$o);

<tr>
 <th>현재가</th>
 <td><? print_r($o[0][0]); ?></td>
</tr>
이와 같은 식으로 코드가 들어가면 됩니다. snoopy 객체 fetch 하는 부분에서 다음 증권 URL 마지막 숫자 6자리는 종목 코드라고 했습니다. 위 예시 코드에서는 DB에서 종목코드를 저장할 때 필드명을 's_code'로 지정했기 때문에 .$row[s_code] 라고 받아오면 동적으로 처리가 가능합니다.

그리고 루프를 돌면서 <tr>태그를 한 줄씩 뿌려가며 가격 정보를 보여주겠죠. 간단하지만 강력한 스누피 클래스를 이용한 스크래핑 방법에 대해서 알아보았습니다.

스크래핑 시 주의 사항


이미 잘 알고 계시는 독자분들도 많으시겠지만, 스크래핑을 할 때는 몇 가지 주의해야 할 사항들이 있습니다.

  • 웹페이지의 디자인이 바뀌어 마크업 DOM구조가 바뀌면 스크래핑 코드도 바꿔줘야 합니다. 혹시 스크래퍼 제대로 페이지 내용을 스크래핑 해오지 못하면 가장 먼저 긁어 올 대상 웹페이지의 디자인이 바뀌지 않았는지를 체크해야 합니다.
  • 스크립트를 이용해서 스크래퍼가 매일 같은 시간에 지속적으로 대량의 트래픽을 발생시켜 스크래핑을 한다면 내 서버쪽 IP가 차단돼 해당 사이트에 접속이 안 될수도 있습니다. 당연히 스크래핑은 안되겠죠. 불규칙적 비정기적, 다양한 IP로 스크래핑 하는 것이 편법이기는 하지만 안전합니다.
  • 아예 서버에서 외부 스크래핑을 막는 경우도 있으니 스크래핑 하기 전 간단하게 테스트를 해봐야 합니다.

스누피 클래스


스누피클래스는 스크래퍼 이외에도 소켓 통신을 위한 다양한 기능을 지원하는데 이 부분에 대해서는 추후 시간이 나면 다시 설명드릴 수 있도록 하겠습니다. 간단한 프로그래밍으로 투자 효율을 높이시기 바랍니다.

2013년 7월 7일
송종식 드림

댓글 27개:

  1. 좋은 정보 잘 읽었습니다. 감사합니다.

    답글삭제
    답글
    1. 방문자님 방문해 주셔서 감사드리고 댓글도 감사합니다. 새해 복 많이 받으세요~

      삭제
  2. 좋은 정보 잘 읽었습니다. 덕분에 많이 배워갑니다!!!

    답글삭제
    답글
    1. 방문 감사합니다. 좋은 하루 되세요^^

      삭제
  3. 감사합니다 요즘 블로그를 시작했는데, 출처를 남기고 필요한 부분부분 글 좀 보고 작성하도록 하겠습니다.
    좋은 정보 감사합니다.
    좋은 하루 되세요~

    답글삭제
    답글
    1. 블로그를 시작하셨나보네요!
      블로깅 정말 재미있습니다. 종종 들를게요.
      출처 표기만 해주시면 얼마든지 인용하셔도 돼요.
      건투를 빌게요^^

      삭제
  4. 만들고자하는 프로젝트를 어떻게 이해해야하나요?
    컴퓨터관련 프로그램은 잘몰라서요;;

    답글삭제
    답글
    1. 저것은 특정 웹사이트의 내용을 긁어오는 단편적인 내용이구요.
      저 툴을 이용해서 다양하게 응용할 수 있으리라 생각됩니다.
      저 같은 경우에는 본문에 인용된 툴을 이용해서 저만의 투자도구를 만들어 쓰고 있는데 실시간으로 주가를 받아오는 부분에 응용하고 있습니다^^

      삭제
  5. 이글을 지금이라도 보게되어 감사합니다.
    덕분에 기초는 잘 알아서 가는거 같아요
    저는 이제 이걸 알았는데 작성자님께서는 2013년부터 알고계셨군요!
    덕분에 좋은글 보고갑니다 감사합니다!

    답글삭제
    답글
    1. 프로그래밍을 잘 다루면 어려운 일도 쉽게 해결되는 경우가 많은 것 같습니다. 하시는 일이 모두 잘 풀리시길 기원합니다^^

      삭제
  6. 작성자가 댓글을 삭제했습니다.

    답글삭제
  7. 지금 전체를 긁어오는건 가능한데 저기 정규식에서 문제가 생기는군요.. div는 되나요 그리고 만약 숫자말고 문자를 가져오려고 하면 정규식을 어떤식으로 써야할까요

    답글삭제
    답글
    1. 아마 위 예제에서는 em태그안에 있는 숫자를 뽑아오기 때문에 숫자 중간에 있는 쉼표등이 문제가 될 수 있을 것 같습니다.

      $rex에 담아둔 값을 아래 정규식을 이용해서 숫자만 추출하는 과정을 한 번 더 하셔야 할 것 같네요.

      "/[^0-9]*/s"

      그리고 숫자를 제외한 문자를 가져오는 건 다음과 같이 코딩하시면 될 것 같습니다.

      '/([\xEA-\xED][\x80-\xBF]{2}|[a-zA-Z])+/'

      위의 예시는 한글과 알파벳만 추출하는 예시입니다.
      UTF-8 인코딩의 경우이니 인코딩이 다르다면 iconv에서 인코딩 설정을 바꾸셔야 할 것입니다.

      정규식에 대한 부분은 이미 잘 아시는 것 같지만 참고삼아서 아래 링크 하나 더 드리겠습니다.

      https://opentutorials.org/module/6/5141

      삭제
  8. 작성자가 댓글을 삭제했습니다.

    답글삭제
  9. 감사합니다. 아이디어 잘 써먹었습니다. 정말 감사~

    답글삭제
    답글
    1. 네~ 좋은 곳에 잘 사용하셨으면 좋겠습니다. 고맙습니다.

      삭제
  10. 작성자가 댓글을 삭제했습니다.

    답글삭제
  11. 안녕하세요 유용한 정보를 알려주셔서 정말 감사합니다ㅠㅠ
    한가지만 여쭤보면..
    만약 가져오고싶은 소스가 div안에 클래스 이름이 있고 그안에 em으로 원하는 값이 있다면 어떤 정규식으로 변수에 저장을 해야하나요..? 또 문자열을 어떤식으로 저장해야할지도 궁금합니다. 그에 맞는 값이 여러개가 있을때 말입니다..

    답글삭제
    답글
    1. 정규식 다루는게 익숙하지 않으시면 "PHP Simple HTML DOM Parser"라는 라이브러리를 써보시는 것도 방법이니다. HTML마크업을 하실 수 있고, DOM을 이해한다면 아주 간단하게 웹페이지를 크롤링하고 파싱할 수 있습니다. 문자열 저장에 대한부분은 문자열을 int형으로 저장할지, string으로 저장할지, 띄어쓰기에 따라 끊어서 저장을 할지, 그리고 DB에 저장을 할지, 그냥 변수에 담았다가 뿌려줄지에 따라서 구현 방법이 달라지는데, 어떤식의 문자열을 뽑아서, 어떻게 저장하시기를 원하시는지 조금 더 구체적으로 말씀해주시면 답변을 드리겠습니다^^

      삭제
  12. 안녕하세요. 스누피 클래스에대해서 잘읽었습니다.

    한가지 질문이 있습니다.

    스누피 클래스와 PHP Simple HTML DOM Parser 차이점은 뭔가요?

    속도면이나 기능면등등 자세한 설명 부탁드립니다.

    답글삭제
    답글
    1. 스누피는 소켓 클래스입니다. 웹페이지 파싱 기능은 스누피가 지원하는 많은 기능 중 하나일 뿐입니다. 말씀하신 simple html dom parser는 말 그대로 파싱 도구이구요. 절대적 비교는 어렵지만 html 파싱만 하신다면 속도는 후자가 훨씬 빠를거라 생각합니다.

      삭제
  13. 안녕하세요 글 잘읽엇습니다.
    저가 특정 사이트에 기제되어있는 데이터정보를 크롤링후 db저장후 그 db를 다시 가공하여 저가 서비스하는 웹에다가 출력해주려하는데
    simple html dom parser 이것을 이용하는게 입문자에게는 편할까요
    스누피가 편할까요??

    답글삭제
    답글
    1. 어떤 것을 사용해도 크게 어렵지는 않을거라고 생각합니다.
      단, HTML DOM 구조에 대한 것을 먼저 꼼꼼하게 확인하시면 어떤 것을 쓰셔도 도움이 되실 것 같습니다.

      삭제
  14. 반갑습니다. 위에 내용을보면 em class 안에있는 숫자를 불러 오는 소스로 구성되어있는걸 확인했습니다. 헌데 저가 불러오고 싶은건 div class 의 ranked-stats__rating-point 라는 항목에 있는 숫자를 불러오고싶은데 소스를 작성했을때 아무 숫자도 출력되지도 않더라구요 어떻게해야할지 조언좀 얻어 보고싶습니다.

    답글삭제
    답글
    1. 코드를 봐야 문제 원인을 알텐데요, 정규표현식이나 DOM을 제대로 사용한게 맞으신지 먼저 체크를 해보시면 어떨까 생각합니다~^^

      삭제
  15. 좋은 정보네요...PHP 초보인데 정말 좋은 공부가 됬습니다...감사합니다!!!!

    답글삭제
    답글
    1. 감사합니다. 도움이 되셨다니 다행입니다~

      삭제