플러그인에 있는 파싱 쓰고있는데 php7.2로 업데이트 이후
본문
GnuboardScrap.php 코드
<?php
/**
* @author: 명랑폐인 *** 개인정보보호를 위한 이메일주소 노출방지 ***
* License: 상업적 이용 금지. 소스 수정후 배포금지. 라이센스 문구 삭제금지.
* 상업적 이용은 sir.co.kr 컨텐츠몰에서 구매 바랍니다.
*/
require 'library/vendor/autoload.php';
use Goutte\Client;
use Symfony\Component\DomCrawler\Crawler;
class GnuboardScrap
{
private $protocol = "https";
private $doamin = "";
protected $is_debug = false;
protected $debug_info = array();
protected $client = null;
protected $ssl_options = null;
public function __construct($domain, $protocol="http", $is_debug=false) {
$this->domain = $domain;
$this->protocol = strtolower($protocol);
$client = new GuzzleHttp\Client();
$res = $client->request('GET', 'https://api.github.com/gists/c027285c8058e707fa95', array());
//https 접속시 ssl 오류가 발생하면, ['verify' => false] 옵션을 추가한다.
if($this->protocol == "https") {
$this->ssl_options = ['verify' => false];
}
$this->client = new \Goutte\Client();
// Create and use a guzzle client instance that will time out after 90 seconds
$guzzleClient = new \GuzzleHttp\Client(array(
'timeout' => 90,
'verify' => false,
));
$this->client->setClient($guzzleClient);
}
public function __destruct() {
}
public function setDebug($is_debug) {
$this->is_debug = $is_debug ? true : false;
}
/**
* 게시판 목록을 파싱한다.
* 파싱대상에는 반드시 content_url 정보가 필요하다.
* @param $uri 파싱대상 uri
* @return array 게시판 리스트 목록.
*/
public function getList($uri) {
$url = $this->protocol."://".$this->domain.$uri;
$crawler = $this->client->request('GET', $url);
$items = $crawler->filter('div.sir_ul01 ul')->children();
$result = array();
foreach($items as $item) {
$row = array();
$obj = new Crawler($item);
try { //제목영역은 정상 파싱이 안되기 때문에 예외처리로 제외한다.
$wr_id = $obj->filter("div.li_num")->text();
$title = $obj->filter('a.title_link')->text();
$href = $obj->filter('a.title_link')->attr("href");
if(strpos($href, "//") ===0) $href = $this->protocol.":".$href;
$row['wr_id'] = trim($wr_id);
$row['title'] = trim($title);
$row['content_url'] = trim($href);
$result[] = $row;
} catch(Exception $e) {
$this->addDebugInfo("[" . __METHOD__ . "() line ".__LINE__."] ".$e->getMessage(). "");
}
}
return $result;
}
/**
* 게시판 본문 내용을 크롤링후 파싱한다.
* 파싱대상은 제목, 본문내용, 첨부파일, 등록자명, 등록자 아이디, 등록일자 등
*
* @param $uri
* @return array
*/
public function getContent($wr_id, $url) {
$result = array();
$crawler = $this->client->request('GET', $url);
$result['wr_id'] = $wr_id;
$result['wr_subject'] = $crawler->filter("h2.head_h2")->text();
$result['wr_name'] = $crawler->filter("#info_name span.sv_wrap a .member")->text();
$writer_link = $crawler->filter("#info_name span.sv_wrap a")->attr("href");
$parts = parse_url($writer_link);
parse_str($parts['query'], $query);
$result['mb_id'] = $send_mb_id = $query['mb_id'];
$result['wr_datetime'] = $crawler->filter("#info_date")->text();
$result['wr_hit'] = $crawler->filter("#info_hit span")->text();
$result['wr_comment_cnt'] = $crawler->filter("#info_cmt a span")->text();
$result['wr_content'] = $crawler->filter("#vbo_con .con_inner")->html();
$result['content_images'] = $this->getContentImages($crawler->filter("#vbo_con .con_inner"));
$result['comments'] = $this->getCommentList($crawler->filter("section.vbo_vcmt"));
return $result;
}
/**
* 본문 컨텐츠에서 코멘트를 추출한다.
*
* @param $node
* @return array 컨텐츠 목록
*
*/
public function getCommentList($node) {
$comments = $node->filter('article.vcmt')->each(function($node, $i) {
});
//todo 코멘트 리스트 파싱 처리
//todo 댓글이 폐이징되어 표시되는 경우, ajax로 호출하는 경우 처리
}
/**
* 본문 컨텐츠에서 첨부 이미지를 추출한다.
* 추출 대상 이미지는 editor영역 에서 추출한 이미지와, 가변파일로 첨부한 이미지
* @param $node
* @return array, editor 영역 이미지와 가변파일 이미지를 따로 추출한다.
*/
private function getContentImages($node) {
//가변파일 업로드로 업로드한 이미지들.
$upload_images = $node->filter("img")->each(function($node) {
//todo 보안을 위해서는 이미지 확장자를 체크하는게 좋다.
$img_src = $node->attr("src");
if(stripos($img_src, $this->doamin."/data/editor") !== FALSE) {
return $img_src;
}
});
//에디터로 업로드한 이미지들.
$embed_images = $node->filter("img")->each(function($node) {
$img_src = $node->attr("src");
//todo 보안을 위해서는 이미지 확장자를 체크하는게 좋다.
if(stripos($img_src, $this->doamin."/data/file/") !== FALSE) {
return $img_src;
}
});
//return ["upload_images" => array_filter($upload_images)
// , "embed_images" => array_filter($embed_images)
//];
return array_filter(array_merge($upload_images, $embed_images));
}
/**
* url 에 있는 파일을 다운받아 $upload_temp_dir 에 저장한다.
* $filepath 는 cheditor 경로
* @param $url 다운받을 파일 위치
* @param $upload_temp_dir 임시 업로드 위치
* @param $filepath
* @return 다운로드후 저장된 파일의 경로, 실패시 null
*/
public function downloadUrlImage($url, $upload_dir, $filename) {
try {
$filepath = $upload_dir . "/" . $filename;
$file_handle = fopen($filepath, 'w');
$client = new \GuzzleHttp\Client();
$response = $client->get($url, ['save_to' => $file_handle]);
return $filepath;
} catch(Exception $e) {
$this->addDebugInfo("[" . __METHOD__ . "() line ".__LINE__."] ".$e->getMessage(). "");
return null;
}
}
private function addDebugInfo($comment) {
$this->debug_info[] = "DEBUG ".date("Y-m-d H:i:s")." ".$comment;
}
public function getDebugInfo() {
return $this->debug_info;
}
}
board_scrap_functions.php 플러그인 코드
<?php
/**
* @author: 명랑폐인 *** 개인정보보호를 위한 이메일주소 노출방지 ***
* License : 제작자 삭제 금지. 출처 표기.
*/
if(!function_exists('board_write')) {
/**
* 그누보드 게시물 등록 함수.
* 그누보드의 게시판 등록 파일(write_update.php) 로직을 참고하였습니다.
* @param $bo_table 게시판명 ex) freeboard
* @param $subject 게시물 제목
* @param $content 게시물 내용
* @param array $files 업로드할 가변파일 목록, 가변파일이 필요한 경우만 사용
* ex /home/account/www/data/uploads/images1.jpg,,,,
*/
function board_write($bo_table, $contents, $files=array()) {
global $g5, $config;
$write_table = "g5_write_{$bo_table}";
$wr_num = get_next_num($write_table);
$wr_reply = '';
$ca_name = "";
$wr_option = "html1";
$secret = "";
$mail = "";
$wr_subject = $contents['wr_subject'];
$wr_content = $contents['wr_content'];
$wr_link1 = "";
$wr_link2 = "";
//파싱원본글에 대한 wr_id
//todo 중복등록을 피하기 위해서 $ori_wr_id값을 확장필드에 저장후, 중복 체크하는게 좋다.
$ori_wr_id = $contents['wr_id'];
//관리자가 등록한것으로 처리한다.
//$mb_id = $contents['mb_id'];
//$wr_name = $contents['wr_name'];
$mb_id = "admin";
$wr_name = "관리자";
$wr_email = "";
$sql = " insert into $write_table
set wr_num = '$wr_num',
wr_reply = '$wr_reply',
wr_comment = 0,
ca_name = '$ca_name',
wr_option = '$wr_option',
wr_subject = '$wr_subject',
wr_content = '$wr_content',
wr_link1 = '$wr_link1',
wr_link2 = '$wr_link2',
wr_link1_hit = 0,
wr_link2_hit = 0,
wr_hit = 0,
wr_good = 0,
wr_nogood = 0,
mb_id = '$mb_id',
wr_password = '',
wr_name = '$wr_name',
wr_email = '$wr_email',
wr_homepage = '',
wr_datetime = '" . G5_TIME_YMDHIS . "',
wr_last = '" . G5_TIME_YMDHIS . "',
wr_ip = '0.0.0.0',
wr_1 = '',
wr_2 = '',
wr_3 = '',
wr_4 = '',
wr_5 = '',
wr_6 = '',
wr_7 = '',
wr_8 = '',
wr_9 = '',
wr_10 = '' ";
sql_query($sql);
$wr_id = sql_insert_id();
// 부모 아이디에 UPDATE
sql_query(" update $write_table set wr_parent = '$wr_id' where wr_id = '$wr_id' ");
// 새글 INSERT
sql_query(" insert into {$g5['board_new_table']} ( bo_table, wr_id, wr_parent, bn_datetime, mb_id ) values ( '{$bo_table}', '{$wr_id}', '{$wr_id}', '" . G5_TIME_YMDHIS . "', '$mb_id' ) ");
// 게시글 1 증가
sql_query("update {$g5['board_table']} set bo_count_write = bo_count_write + 1 where bo_table = '{$bo_table}'");
// 파일개수 체크
$file_count = 0;
$upload_count = count($files);
for ($i = 0; $i < $upload_count; $i++) {
if ($files[$i] && file_exists($files[$i]))
$file_count++;
}
// 디렉토리가 없다면 생성합니다. (퍼미션도 변경하구요.)
@mkdir(G5_DATA_PATH . '/file/' . $bo_table, G5_DIR_PERMISSION);
@chmod(G5_DATA_PATH . '/file/' . $bo_table, G5_DIR_PERMISSION);
$chars_array = array_merge(range(0, 9), range('a', 'z'), range('A', 'Z'));
// 가변 파일 업로드
$file_upload_msg = '';
$upload = array();
for ($i = 0; $i < count($files); $i++) {
$upload[$i]['file'] = '';
$upload[$i]['source'] = '';
$upload[$i]['filesize'] = 0;
$upload[$i]['image'] = array();
$upload[$i]['image'][0] = '';
$upload[$i]['image'][1] = '';
$upload[$i]['image'][2] = '';
$upload[$i]['del_check'] = false;
$tmp_file = $files[$i];
$filesize = filesize($files[$i]);
$filename = basename($files[$i]);
$filename = get_safe_filename($filename);
if (file_exists($tmp_file)) {
//=================================================================\
// 090714
// 이미지나 플래시 파일에 악성코드를 심어 업로드 하는 경우를 방지
// 에러메세지는 출력하지 않는다.
//-----------------------------------------------------------------
$timg = @getimagesize($tmp_file);
// image type
if (preg_match("/\.({$config['cf_image_extension']})$/i", $filename) ||
preg_match("/\.({$config['cf_flash_extension']})$/i", $filename)) {
if ($timg['2'] < 1 || $timg['2'] > 16)
continue;
}
//=================================================================
$upload[$i]['image'] = $timg;
// 프로그램 원래 파일명
$upload[$i]['source'] = $filename;
$upload[$i]['filesize'] = $filesize;
// 아래의 문자열이 들어간 파일은 -x 를 붙여서 웹경로를 알더라도 실행을 하지 못하도록 함
$filename = preg_replace("/\.(php|phtm|htm|cgi|pl|exe|jsp|asp|inc)/i", "$0-x", $filename);
shuffle($chars_array);
$shuffle = implode('', $chars_array);
// 첨부파일 첨부시 첨부파일명에 공백이 포함되어 있으면 일부 PC에서 보이지 않거나 다운로드 되지 않는 현상이 있습니다. (길상여의 님 090925)
$upload[$i]['file'] = abs(ip2long($_SERVER['REMOTE_ADDR'])) . '_' . substr($shuffle, 0, 8) . '_' . replace_filename($filename);
$dest_file = G5_DATA_PATH . '/file/' . $bo_table . '/' . $upload[$i]['file'];
// 업로드가 안된다면 에러메세지 출력하고 죽어버립니다.
$error_code = copy($tmp_file, $dest_file) or die("upload error");
// 올라간 파일의 퍼미션을 변경합니다.
chmod($dest_file, G5_FILE_PERMISSION);
}
}
// 나중에 테이블에 저장하는 이유는 $wr_id 값을 저장해야 하기 때문입니다.
for ($i = 0; $i < count($upload); $i++) {
if (!get_magic_quotes_gpc()) {
$upload[$i]['source'] = addslashes($upload[$i]['source']);
}
$sql = " insert into {$g5['board_file_table']}
set bo_table = '{$bo_table}',
wr_id = '{$wr_id}',
bf_no = '{$i}',
bf_source = '{$upload[$i]['source']}',
bf_file = '{$upload[$i]['file']}',
bf_content = '',
bf_download = 0,
bf_filesize = '{$upload[$i]['filesize']}',
bf_width = '{$upload[$i]['image']['0']}',
bf_height = '{$upload[$i]['image']['1']}',
bf_type = '{$upload[$i]['image']['2']}',
bf_datetime = '" . G5_TIME_YMDHIS . "' ";
sql_query($sql);
}
// 업로드된 파일 내용에서 가장 큰 번호를 얻어 거꾸로 확인해 가면서
// 파일 정보가 없다면 테이블의 내용을 삭제합니다.
$row = sql_fetch(" select max(bf_no) as max_bf_no from {$g5['board_file_table']} where bo_table = '{$bo_table}' and wr_id = '{$wr_id}' ");
for ($i = (int)$row['max_bf_no']; $i >= 0; $i--) {
$row2 = sql_fetch(" select bf_file from {$g5['board_file_table']} where bo_table = '{$bo_table}' and wr_id = '{$wr_id}' and bf_no = '{$i}' ");
// 정보가 있다면 빠집니다.
if ($row2['bf_file']) break;
// 그렇지 않다면 정보를 삭제합니다.
sql_query(" delete from {$g5['board_file_table']} where bo_table = '{$bo_table}' and wr_id = '{$wr_id}' and bf_no = '{$i}' ");
}
// 파일의 개수를 게시물에 업데이트 한다.
$row = sql_fetch(" select count(*) as cnt from {$g5['board_file_table']} where bo_table = '{$bo_table}' and wr_id = '{$wr_id}' ");
sql_query(" update {$write_table} set wr_file = '{$row['cnt']}' where wr_id = '{$wr_id}' ");
return $wr_id;
}
}
if(!function_exists('board_content_image_replace')) {
/**
* 게시판 본문에 첨부된 이미지 url경로를 업로드한 url경로로 치환한다.
* 원본 이미지 url 경로에서 domain과, path, filename 을 변경한다.
* @param $content 게시판 본문
* @param $images array original 이미지 경로와
*/
function board_content_image_replace($content, $images) {
$new_content = $content;
$domain = G5_DOMAIN ? G5_DOMAIN : $_SERVER['HTTP_HOST'];
//사이트 호스트정보가 IP로 나오는경우 삭제처리
if(filter_var($domain, FILTER_VALIDATE_IP)) {
$domain = '';
}
for($i =0; $i<count($images); $i++) {
$original_url = $images[$i]['original_url'];
$upload_filepath = $images[$i]['upload_filepath'];
$my_domain_url = $domain ? "//".$domain.$upload_filepath : $upload_filepath;
$new_content = str_replace($original_url, $my_domain_url, $new_content);
}
/* 테스트 코드
echo "<xmp>";
echo "============ old wr_content =========\n";
echo $content;
echo "============ new wr_content =========\n";
echo $new_content;
echo "</xmp>";
*/
return $new_content;
}
}
if(!function_exists('generate_new_filename')) {
/**
* 랜덤한 파일명을 생성한다. 초기 파일 확장자는 유지한다.
* 그누보드의 파일명 생성 로직을 참고하였습니다.
* @param $filname 파일명
* @return String 신규 파일명
*/
function generate_new_filename($filename) {
$chars_array = array_merge(range(0,9), range('a','z'), range('A','Z'));
shuffle($chars_array);
$shuffle = implode('', $chars_array);
$prefix = abs(ip2long($_SERVER['REMOTE_ADDR'])).'_'.substr($shuffle,0,8);
$usec = get_microtime();
$info = pathinfo($filename);
$ext = $info['extension'];
$new_filename = sha1($_SERVER['REMOTE_ADDR'].$usec).($ext ? '.'.$ext : "");
return $prefix."_".$new_filename;
}
}
if(!function_exists('gnuboard_scrap')) {
/**
* 대상 url 게시물을 파싱후 게시판에 등록한다.
* @param $url 파싱 대상 url
* @param $target_bo_table 파싱후 등록할 대상 테이블
* @return array 게시물들이 등록되면, 등록한 wr_id
*/
function gnuboard_scrap($url, $target_bo_table, $print_debug = false) {
global $g5, $member;
include_once dirname(__DIR__)."/lib/GnuboardScrap.php";
$ym = date('ym', G5_SERVER_TIME);
$upload_temp_dir = G5_DATA_PATH."/scrap"; //임시 업로드 디렉토리
@mkdir($upload_temp_dir, G5_DIR_PERMISSION);
@chmod($upload_temp_dir, G5_DIR_PERMISSION);
$upload_editor_dir = G5_DATA_PATH."/editor/".$ym; //에디터 업로드 디렉토리
@mkdir($upload_editor_dir, G5_DIR_PERMISSION);
@chmod($upload_editor_dir, G5_DIR_PERMISSION);
$url_info = parse_url($url);
$domain = $url_info['host'];
$scheme = $url_info['scheme'];
$uri = $url_info['path'].($url_info['query'] ? "?".$url_info['query'] : "");
$result = array();
$scrap = new GnuboardScrap($domain, $scheme);
if($print_debug) {
$scrap->setDebug(true);
}
$list = $scrap->getList($uri);
foreach($list as $row) {
//todo 이미 파싱한 게시물인지 체크, $row['wr_id'] 값을 게시물에 저장후 중복된 데이타인지 체크
$contents = $scrap->getContent($row['wr_id'], $row['content_url']);
$upload_data_list = array();
//todo 가변파일로 업로드된 이미지와, 에디터 embed 이미지는 분리하여 처리하여야 하나
// 불필요하게 코드를 복잡하게 만들기 때문에, 에디터 embed 이미지로 처리한다.
for($i = 0;$i<count($contents['content_images']); $i++) {
$download_url = $contents['content_images'][$i];
$url_info = parse_url($download_url);
$path_info = pathinfo($url_info['path']);
$filename = $path_info['basename'];
$filename = generate_new_filename($filename); //원본 이미지 url 경로와 다른 파일명으로 저장한다.
$upload_filepath = $scrap->downloadUrlImage($download_url, $upload_editor_dir, $filename);
//다운로드 url 경로와 다운로드 받은 파일경로를 같이 저장한다.
$upload_data_list[] = array(
"original_url" => $download_url
, "upload_filepath" => "/".G5_DATA_DIR."/editor/".$ym."/".$filename
);
}
//게시판 본문 이미지 태그의 정보를 다운로드한 이미지 경로로 변경한다.
$contents['wr_content'] = board_content_image_replace($contents['wr_content'], $upload_data_list);
$wr_id = board_write($target_bo_table, $contents);
$result[] = array("wr_subject" => $contents['wr_subject'], "wr_id" => $wr_id);
//가변파일을 처리하는 경우, 임시 업로드된 이미지를 삭제한다.
//foreach (glob($upload_temp_dir."/*") as $temp_filename) {
// unlink($temp_filename);
//}
sleep(1);
//FIXME 예제코드가 sir.co.kr 유머게시판을 파싱합니다.
//과도한 트래픽과 request를 방지하기 위해 게시물 1개만 파싱되도록 하였습니다.
//페이지별로 전체를 파싱하기 위해선 아래 break 문을 삭제하시면 됩니다.
//과도한 request가 발생하면 sir.co.kr 사이트에서 접속이 차단될수 있습니다.(주의바람)
//sir.co.kr 에 대한 파싱은 테스트로만 사용하길 바랍니다.
break;
}
if($print_debug) {
print_r2($scrap->getDebugInfo());
}
return $result;
}
}
?>
제목은 잘가져오고 여러분들이 답변 주셔서 오류는 안뜨는데
내용을 못가져오고 있는데 왜그런건가요?
!-->!-->답변 3
크롤링 대상 서버에서 코드구성을 바꾼 경우 기존에 지정된 filter 는 동작안할 가능성도 있으며
그런 경우 filter 에 지정된 selector 를 수정해야 할수 있습니다.
e.g.
크롤링 대상 서버에서 응답 텍스트가 다음과 같이 떨어질때는
<div id="info_name"><span class="sv_wrap"><a href="#" class="member">text</a></span></div>
다음 셀렉터가 유효하지만
$crawler->filter("#info_name span.sv_wrap a .member");
크롤링 대상 서버에서 응답 텍스트가 이전과 다르게 다음과 같이 수정되었다면
<div id="info_nam"><b class="sv_wra"><a href="#" class="membe">text</a></b></div>
다음 셀렉터는 유효하지 않게 됩니다.
$crawler->filter("#info_name span.sv_wrap a .member");
if(strpos($href, "//") ===0) $href = $this->protocol.":".$href;
→
if (strpos($href, "//") == 0) $href = $this->protocol.":".$href;
Or
if(strpos($href, "//") ===0) $href = $this->protocol.":".$href;
→
if (substr($href, 0, 2) == "//") $href = $this->protocol.":".$href;
이거 플러그인내용이 뭔데요? 깃헙 관련 된거 같아보인데 정확히 알고 싶어요
답변을 작성하시기 전에 로그인 해주세요.