디자인 패턴의 템플릿 메소드 패턴에 대해 알아보려고 합니다.
크롤링을 주로 작업하면서 한개의 크롤러를 만들어 사용하다 여러가지 문제로 인해 여러개의 크롤러를 사용하기로 합니다.
이전까지는 curl 만 이용했지만 아래와 같이 curl 이외의 크롤러를 사용하기 위해 템플릿 메소드를 사용하기로 합니다.
- curl
- casperjs
- puppeteer 등등
다이어그램은 아래와 같습니다.
ParseCurl 객체 ParsePuppeteer 객체 또는 다른 크롤러 추가시 OCP 를 만족하도록 추가만 해주면 됩니다.
다이어그램은 봤으니 프로그램을 살펴보도록 하겠습니다.
먼저 호스트 코드를 먼저 살펴보도록 하겠습니다.
example.php
<?php
ini_set('memory_limit','512M');
date_default_timezone_set('Asia/Seoul');
require __DIR__.'/../vendor/autoload.php';
use Carbon\Carbon;
use Doctrine\DBAL\DriverManager;
use Dotenv\Dotenv;
use Keri\Db\HumorAll;
use Keri\Crawling\TemplateAbstract;
use Keri\Crawling\ParsePuppeteer;
use Keri\Crawling\ParseCurl;
use Keri\Crawling\Gnu;
use Keri\Crawling\Coupang;
/*
* 2023.02.01
* .env 사용
*/
$dotenv = Dotenv::createImmutable(__DIR__.'/../');
$dotenv->load();
$connectionParams = [
'dbname' => $_SERVER['DB_NAME'],
'user' => $_SERVER['DB_USER'],
'password' => $_SERVER['DB_PASS'],
'host' => $_SERVER['DB_HOST'],
'driver' => $_SERVER['DB_DRIVER'],
];
$conn = DriverManager::getConnection($connectionParams);
exampleCoupangPupperteer();
function exampleCoupangPupperteer()
{
global $conn;
$obj = new Coupang(
new ParsePuppeteer('https://www.coupang.com/np/categories/178454'),
$conn
);
$obj->crawling();
}
function exampleGnuPupperteer()
{
$obj = new Gnu(new ParsePuppeteer('https://naver.co.kr/bbs/board.php?bo_table=by2_2&wr_id=10'));
$obj->crawling();
}
function exampleCurl()
{
$obj = new Gnu(new ParseCurl('https://naver.co.kr/bbs/board.php?bo_table=by2_2&wr_id=10'));
$obj->crawling();
}
호스트 코드는 크롤러별 함수로 제작했습니다.
Coupang 객체의 생성자에 2개의 인자를 넣어줍니다.
$obj = new Coupang(
new ParsePuppeteer('https://www.coupang.com/np/categories/178454'),
$conn
);
$obj->crawling();
첫번째 인자는 클롤러
두번째 인자는 DB 인스턴스
그후 크롤링이 시작됩니다.
Coupang.php
<?php
namespace Keri\Crawling;
use Keri\Common;
class Coupang extends Common
{
private $host = 'https://www.coupang.com';
private $page;
private $sangPage;
private $links = [];
/**
* Coupang constructor.
* @param $crawler
* @param $conn
*/
public function __construct($crawler, $conn)
{
$this->page = $crawler->getContent();
$this->conn = $conn;
}
.....
Coupang 객체를 살펴보면 $crawler->getContent() 를 호출합니다.
$crawler 변수에 ParsePuppeteer 객체가 담겨있으니 ParsePuppeteer 객체의 getContent() 메소드를 호출 하려고 합니다.
그런데 ParsePuppeteer 객체를 살펴보면 getContent() 메소드가 없습니다.
TemplateAbstract 객체를 extends 했으니 TemplateAbstract 객체 안을 살펴봅니다.
final public function getContent() {
return $this->_parsing();
}
getContent() 메소드는 _parsing() 메소드를 호출하려 리턴합니다.
_parsing() 메소드는 abstract 선언되어 있어 자식 클래스에서 꼭 만들어줘야 합니다.
ParsePuppeteer 객체를 보면 _parsing() 메소드가 구현되어 있어 호출하게 됩니다.
템플릿 메소드는 객체지향의 근간을 이루고 있는 Polymorphism 에 의하여 작동됩니다.
Polymorphism 은 아래 두가지 특징이 있습니다.
- 대체가능성 : 즉 자식이 부모를 대신할 수 있음
- 내적동질성 : 객체는 생성 당시 메소드가 우선시 됨
여기서는 Polymorphism 의 내적동질성을 이용하게 됩니다.
protected function _parsing()
{
// TODO: Implement parsing() method.
echo __METHOD__." ParsePuppeteer start..".PHP_EOL;
exec('node /Users/nayakim/Documents/naya/program/me/webdocument/dom.js '.$this->url, $output);
$result = implode("\n",$output);
return $result;
}
만약 아래와 같이 ParseCurl 객체를 주입해주면
$obj = new Coupang(
new ParseCurl('https://www.coupang.com/np/categories/178454'),
$conn
);
$obj->crawling();
ParseCurl 객체의 _parsing() 메소드가 실행될것입니다.
protected function _parsing()
{
// TODO: Implement parsing() method.
echo __METHOD__." Curl start...".PHP_EOL;
return $this->getPage();
}
만약 새로운 크롤러 추가해된다면 Coupang 객체 생성자 첫번째 인자에 추가한 크롤러 객체만 주입해주면 끝입니다.
두번째 인자인 $conn DB 인스턴스도 마찬가지 입니다.
아래는 템플릿 메소드를 사용한 abstract 객체 1개와 extends 객체 2개의 소스코드 입니다.
TemplateAbstract.php
<?php
namespace Keri\Crawling;
abstract class TemplateAbstract
{
protected $url;
protected $refer;
protected $agent;
abstract protected function _parsing();
final public function getContent() {
return $this->_parsing();
}
}
ParsePuppeteer.php
<?php
namespace Keri\Crawling;
class ParsePuppeteer extends TemplateAbstract
{
public function __construct($url)
{
$this->url = htmlspecialchars_decode($url);
}
protected function _parsing()
{
// TODO: Implement parsing() method.
echo __METHOD__." ParsePuppeteer start..".PHP_EOL;
exec('node /Users/nayakim/Documents/naya/program/me/webdocument/dom.js '.$this->url, $output);
$result = implode("\n",$output);
return $result;
}
private function setHeaders()
{
echo '>>> dom.js 파일에서 헤더 수정해 줘야 함'.PHP_EOL;
}
}
ParseCurl.php
<?php
namespace Keri\Crawling;
class ParseCurl extends TemplateAbstract
{
public function __construct($url)
{
$this->url = htmlspecialchars_decode($url);
$this->setAgent();
$this->setReferer();
}
protected function _parsing()
{
// TODO: Implement parsing() method.
echo __METHOD__." Curl start...".PHP_EOL;
return $this->getPage();
}
public function setReferer($refer='') {
$this->refer = $refer?$refer:$this->url;
}
public function setAgent() {
$arr = [
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36',
'Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405',
'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0',
'Opera/9.80 (Windows NT 6.1; WOW64; U; ko) Presto/2.10.229 Version/11.62',
'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36 OPR/17.0.1241.53',
'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36',
'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2',
'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)',
'Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko',
'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
];
$this->agent = $arr[array_rand($arr, 1)];
}
public function getPage() {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->url);
if($this->refer) curl_setopt($curl, CURLOPT_REFERER, $this->refer);
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
curl_setopt($curl, CURLOPT_USERAGENT, $this->agent);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); // 속도 빠르게
$page = curl_exec($curl);
curl_close($curl);
return $page;
}
public function postPage(array $post, array $headers=[], $header=0) {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->url);
if($header) curl_setopt($curl, CURLOPT_HEADER, true);
if($headers) curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
if($this->refer) curl_setopt($curl, CURLOPT_REFERER, $this->refer);
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
curl_setopt($curl, CURLOPT_USERAGENT, $this->agent);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($post));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$page = curl_exec($curl);
curl_close($curl);
return $page;
}
}
새로운 크롤러를 추가하려면 _parsing() 메소드 만 새로운 객체를 만들어 추가해주면 끝납니다. (open 에 열려있다)
기존 코드는 수정될 일이 없습니다.
이로써 템플릿 메소를 알아봤습니다.
다음에는 상속관계가 아닌 위임관계인 전략패턴을 알아보도록 하겠습니다.
'프로그래밍 > Php' 카테고리의 다른 글
php 로 만드는 socket Server 와 socket Client (0) | 2023.02.16 |
---|---|
easyOCR 이미지 한글 추출 기능 (php, python, fetch 사용) (1) | 2021.10.28 |
PHP에서 Fetch API를 사용하여 JavaScript로 파일 업로드 (0) | 2021.10.28 |
php exec 사용하여 파이썬 호출후 한글이 안나올때 (0) | 2021.10.26 |
mysql error ERROR 1366 (HY000): Incorrect string value: '\xF0\x9F\x98\x9C' for column 'comment' at row 1 (0) | 2020.11.09 |