์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿ•ท๏ธ PHP ์›น ์Šคํฌ๋ž˜ํ•‘ ์™„์ „์ •๋ณต

๐Ÿ•ท๏ธ PHP ์›น ์Šคํฌ๋ž˜ํ•‘ ์™„์ „์ •๋ณต

cURL๊ณผ Guzzle๋กœ ์›น ๋ฐ์ดํ„ฐ๋ฅผ ์ž์œ ์ž์žฌ๋กœ ์ˆ˜์ง‘ํ•˜๋Š” ๋ฐฉ๋ฒ•

์•ผ, ์ธํ„ฐ๋„ท์— ์žˆ๋Š” ์—„์ฒญ๋‚œ ๋ฐ์ดํ„ฐ๋“ค ๋ณด๋ฉด ์ž…๋ง› ๋‹ค์‹œ์ง€ ์•Š์•„? ๐Ÿคค ๊ฒฝ์Ÿ์‚ฌ ๊ฐ€๊ฒฉ ์ •๋ณด, ๋ถ€๋™์‚ฐ ์‹œ์„ธ, ๋‰ด์Šค ํ—ค๋“œ๋ผ์ธ, SNS ํŠธ๋ Œ๋“œ... ์ด๋Ÿฐ ๊ฒƒ๋“ค์„ ์ผ์ผ์ด ๋ณต์‚ฌ-๋ถ™์—ฌ๋„ฃ๊ธฐ ํ•˜๋Š” ๊ฑด 21์„ธ๊ธฐ์— ๋งž์ง€ ์•Š์ž–์•„? ๋ฐ”๋กœ ์—ฌ๊ธฐ์„œ ์›น ์Šคํฌ๋ž˜ํ•‘(Web Scraping)์ด ๋“ฑ์žฅํ•˜๋Š” ๊ฑฐ์•ผ!

์›น ์Šคํฌ๋ž˜ํ•‘์€ ์‰ฝ๊ฒŒ ๋งํ•ด์„œ ์›น์‚ฌ์ดํŠธ์—์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ž๋™์œผ๋กœ ๊ธ์–ด์˜ค๋Š” ๊ธฐ์ˆ ์ด์•ผ. ๋งˆ์น˜ ๋กœ๋ด‡ ๋น„์„œ๊ฐ€ ๋Œ€์‹  ์›น์„œํ•‘ํ•˜๋ฉด์„œ ์ •๋ณด๋ฅผ ์ •๋ฆฌํ•ด์ฃผ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ง์ด์ง€. ์˜ค๋Š˜์€ PHP๋กœ ์ด๊ฑธ ์–ด๋–ป๊ฒŒ ํ•˜๋Š”์ง€, ํŠนํžˆ cURL๊ณผ Guzzle์ด๋ผ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๋“ค์„ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•˜๋Š”์ง€ ์นœ๊ตฌ์ฒ˜๋Ÿผ ํŽธํ•˜๊ฒŒ ์•Œ๋ ค์ค„๊ฒŒ! ๐Ÿ˜Ž

PHP Web Scraping cURL + Guzzle DATA

๐ŸŽฏ ์›น ์Šคํฌ๋ž˜ํ•‘์ด ๋ญ”๋ฐ?

์›น ์Šคํฌ๋ž˜ํ•‘์€ HTML ๋ฌธ์„œ์—์„œ ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์ถ”์ถœํ•˜๋Š” ๊ณผ์ •์ด์•ผ. ์›น์‚ฌ์ดํŠธ๋ฅผ ๋ฐฉ๋ฌธํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ HTML, CSS, JavaScript๋ฅผ ๋ฐ›์•„์„œ ์˜ˆ์œ ํ™”๋ฉด์„ ๋ณด์—ฌ์ฃผ์ž–์•„? ์šฐ๋ฆฌ๋Š” ๊ทธ HTML ์ฝ”๋“œ ์†์—์„œ ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋งŒ ์™์™ ๋ฝ‘์•„๋‚ด๋Š” ๊ฑฐ์ง€! ๐Ÿ“Š

๐Ÿ” ์‹ค์ƒํ™œ ํ™œ์šฉ ์‚ฌ๋ก€ 1. ๊ฐ€๊ฒฉ ๋น„๊ต ์„œ๋น„์Šค: ์—ฌ๋Ÿฌ ์‡ผํ•‘๋ชฐ์˜ ์ƒํ’ˆ ๊ฐ€๊ฒฉ์„ ์ž๋™์œผ๋กœ ์ˆ˜์ง‘ํ•ด์„œ ๋น„๊ต
2. ๋ถ€๋™์‚ฐ ์ •๋ณด ์ˆ˜์ง‘: ๋งค๋ฌผ ์ •๋ณด๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋ง
3. ๋‰ด์Šค ์• ๊ทธ๋ฆฌ๊ฒŒ์ดํ„ฐ: ๋‹ค์–‘ํ•œ ์–ธ๋ก ์‚ฌ์˜ ๊ธฐ์‚ฌ๋ฅผ ํ•œ๊ณณ์— ๋ชจ์•„์„œ ์ œ๊ณต
4. SNS ๋ถ„์„: ํŠน์ • ํ‚ค์›Œ๋“œ๋‚˜ ํ•ด์‹œํƒœ๊ทธ์˜ ํŠธ๋ Œ๋“œ ๋ถ„์„
5. ์ฑ„์šฉ ์ •๋ณด ํ†ตํ•ฉ: ์—ฌ๋Ÿฌ ๊ตฌ์ธ๊ตฌ์ง ์‚ฌ์ดํŠธ์˜ ๊ณต๊ณ ๋ฅผ ํ•œ๋ˆˆ์—

๊ทผ๋ฐ ์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ์ ! โš ๏ธ ์›น ์Šคํฌ๋ž˜ํ•‘์€ ํ•ฉ๋ฒ•์ ์œผ๋กœ ํ•ด์•ผ ํ•ด. ์›น์‚ฌ์ดํŠธ์˜ robots.txt ํŒŒ์ผ์„ ํ™•์ธํ•˜๊ณ , ์ด์šฉ์•ฝ๊ด€์„ ์ค€์ˆ˜ํ•˜๊ณ , ์„œ๋ฒ„์— ๊ณผ๋ถ€ํ•˜๋ฅผ ์ฃผ์ง€ ์•Š๋„๋ก ์ ์ ˆํ•œ ๋”œ๋ ˆ์ด๋ฅผ ๋‘๋Š” ๊ฒŒ ๋งค๋„ˆ์•ผ. ๊ทธ๋ฆฌ๊ณ  ๊ฐœ์ธ์ •๋ณด๋‚˜ ์ €์ž‘๊ถŒ์ด ์žˆ๋Š” ์ฝ˜ํ…์ธ ๋Š” ์กฐ์‹ฌํ•ด์•ผ ํ•˜๊ณ !

๐Ÿ› ๏ธ PHP๋กœ ์›น ์Šคํฌ๋ž˜ํ•‘ํ•˜๋Š” ๋ฐฉ๋ฒ•๋“ค

PHP์—์„œ ์›น ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ์–ด. ๊ฐ๊ฐ์˜ ํŠน์ง•์„ ์‚ดํŽด๋ณด์ž!

1. file_get_contents() - ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•

์ด๊ฑด ์ •๋ง ์ดˆ๋ณด์ž๋„ ์‰ฝ๊ฒŒ ์“ธ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด์•ผ. ๋‹จ ํ•œ ์ค„๋กœ ์›น ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ฑฐ๋“ ! ๐ŸŽˆ

<?php
// ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ์‚ฌ์šฉ๋ฒ•
$html = file_get_contents('https://example.com');
echo $html;

// ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•œ ์‚ฌ์šฉ๋ฒ•
$options = [
    'http' => [
        'method' => 'GET',
        'header' => 'User-Agent: Mozilla/5.0'
    ]
];
$context = stream_context_create($options);
$html = file_get_contents('https://example.com', false, $context);
?>
๐Ÿ’ก ์žฅ์ : ๊ฐ„๋‹จํ•˜๊ณ  ๋น ๋ฅด๊ฒŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ, ์ถ”๊ฐ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ถˆํ•„์š”
โš ๏ธ ๋‹จ์ : ๋ณต์žกํ•œ ์š”์ฒญ ์ฒ˜๋ฆฌ ์–ด๋ ค์›€, ์—๋Ÿฌ ํ•ธ๋“ค๋ง ์ œํ•œ์ , HTTPS ์ธ์ฆ์„œ ๋ฌธ์ œ ๋ฐœ์ƒ ๊ฐ€๋Šฅ

2. cURL - ๊ฐ•๋ ฅํ•œ ๋„ค์ดํ‹ฐ๋ธŒ ๋„๊ตฌ

cURL์€ PHP์— ๊ธฐ๋ณธ์œผ๋กœ ๋‚ด์žฅ๋œ ๊ฐ•๋ ฅํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์•ผ. ๋ณต์žกํ•œ HTTP ์š”์ฒญ๋„ ์ž์œ ์ž์žฌ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์ง€! ๐Ÿ’ช

<?php
// cURL ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•
$ch = curl_init();

// ์˜ต์…˜ ์„ค์ •
curl_setopt($ch, CURLOPT_URL, 'https://example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0');

// ์‹คํ–‰
$response = curl_exec($ch);

// ์—๋Ÿฌ ์ฒดํฌ
if(curl_errno($ch)) {
    echo 'Error: ' . curl_error($ch);
}

// ์—ฐ๊ฒฐ ์ข…๋ฃŒ
curl_close($ch);

echo $response;
?>

3. Guzzle - ํ˜„๋Œ€์ ์ธ HTTP ํด๋ผ์ด์–ธํŠธ

Guzzle์€ PHP ์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ ๊ฐ€์žฅ ์‚ฌ๋ž‘๋ฐ›๋Š” HTTP ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์•ผ. ๊ฐ์ฒด์ง€ํ–ฅ์ ์ด๊ณ  ์‚ฌ์šฉํ•˜๊ธฐ ํŽธํ•ด์„œ ๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ์„ ํ˜ธํ•˜์ง€! ๐ŸŒŸ

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;

// ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
$client = new Client();

// GET ์š”์ฒญ
$response = $client->request('GET', 'https://example.com');

// ์‘๋‹ต ์ฒ˜๋ฆฌ
$statusCode = $response->getStatusCode();
$body = $response->getBody()->getContents();

echo $body;
?>

๐Ÿš€ cURL ์™„์ „ ์ •๋ณตํ•˜๊ธฐ

์ž, ์ด์ œ cURL์„ ์ œ๋Œ€๋กœ ํŒŒํ—ค์ณ๋ณด์ž! cURL์€ Client URL์˜ ์•ฝ์ž๋กœ, ๋‹ค์–‘ํ•œ ํ”„๋กœํ† ์ฝœ์„ ์ง€์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ „์†ก ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์•ผ. HTTP๋ฟ๋งŒ ์•„๋‹ˆ๋ผ FTP, SMTP ๋“ฑ๋„ ์ง€์›ํ•˜์ง€๋งŒ, ์šฐ๋ฆฌ๋Š” ์›น ์Šคํฌ๋ž˜ํ•‘์— ์ง‘์ค‘ํ• ๊ฒŒ! ๐ŸŽฏ

cURL์˜ ํ•ต์‹ฌ ์˜ต์…˜๋“ค

cURL์„ ์ œ๋Œ€๋กœ ํ™œ์šฉํ•˜๋ ค๋ฉด ๋‹ค์–‘ํ•œ ์˜ต์…˜๋“ค์„ ์•Œ์•„์•ผ ํ•ด. ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ์˜ต์…˜๋“ค์„ ์ •๋ฆฌํ•ด๋ดค์–ด!

์˜ต์…˜ ์„ค๋ช… ์˜ˆ์‹œ
CURLOPT_URL ์š”์ฒญํ•  URL ์ง€์ • curl_setopt($ch, CURLOPT_URL, 'https://api.example.com')
CURLOPT_RETURNTRANSFER ๊ฒฐ๊ณผ๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ฐ˜ํ™˜ (true) ๋˜๋Š” ์ง์ ‘ ์ถœ๋ ฅ (false) curl_setopt($ch, CURLOPT_RETURNTRANSFER, true)
CURLOPT_FOLLOWLOCATION ๋ฆฌ๋‹ค์ด๋ ‰์…˜ ์ž๋™ ๋”ฐ๋ผ๊ฐ€๊ธฐ curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true)
CURLOPT_TIMEOUT ํƒ€์ž„์•„์›ƒ ์‹œ๊ฐ„ ์„ค์ • (์ดˆ) curl_setopt($ch, CURLOPT_TIMEOUT, 30)
CURLOPT_USERAGENT User-Agent ํ—ค๋” ์„ค์ • curl_setopt($ch, CURLOPT_USERAGENT, 'MyBot/1.0')

์‹ค์ „ cURL ์˜ˆ์ œ - POST ์š”์ฒญํ•˜๊ธฐ

GET ์š”์ฒญ๋งŒ ํ•  ์ค„ ์•Œ๋ฉด ์žฌ๋ฏธ์—†์ง€! POST ์š”์ฒญ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์•Œ์•„๋ณด์ž. ๋กœ๊ทธ์ธ์ด๋‚˜ ํผ ์ œ์ถœํ•  ๋•Œ ํ•„์ˆ˜์•ผ! ๐Ÿ”

<?php
$ch = curl_init();

// POST ๋ฐ์ดํ„ฐ ์ค€๋น„
$postData = [
    'username' => 'myuser',
    'password' => 'mypass',
    'remember' => 'on'
];

// cURL ์˜ต์…˜ ์„ค์ •
curl_setopt_array($ch, [
    CURLOPT_URL => 'https://example.com/login',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query($postData),
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_COOKIEJAR => 'cookies.txt',  // ์ฟ ํ‚ค ์ €์žฅ
    CURLOPT_COOKIEFILE => 'cookies.txt', // ์ฟ ํ‚ค ์‚ฌ์šฉ
    CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
    CURLOPT_HTTPHEADER => [
        'Accept: text/html,application/xhtml+xml',
        'Accept-Language: ko-KR,ko;q=0.9',
        'Content-Type: application/x-www-form-urlencoded'
    ]
]);

$response = curl_exec($ch);

if(curl_errno($ch)) {
    die('cURL Error: ' . curl_error($ch));
}

curl_close($ch);

echo $response;
?>
โš ๏ธ ์ฟ ํ‚ค ๊ด€๋ฆฌ ์ฃผ์˜์‚ฌํ•ญ ์›น ์Šคํฌ๋ž˜ํ•‘ํ•  ๋•Œ ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„. ์ด๋•Œ ์ฟ ํ‚ค๋ฅผ ์ œ๋Œ€๋กœ ๊ด€๋ฆฌํ•˜์ง€ ์•Š์œผ๋ฉด ๋งค๋ฒˆ ๋กœ๊ทธ์ธํ•ด์•ผ ํ•˜๋Š” ๋ถˆํŽธํ•จ์ด ์žˆ์ง€. CURLOPT_COOKIEJAR์™€ CURLOPT_COOKIEFILE์„ ์‚ฌ์šฉํ•˜๋ฉด ์ฟ ํ‚ค๋ฅผ ํŒŒ์ผ๋กœ ์ €์žฅํ•˜๊ณ  ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด!

JSON API ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ

์š”์ฆ˜์€ ๋งŽ์€ ์›น์‚ฌ์ดํŠธ๊ฐ€ JSON API๋ฅผ ์ œ๊ณตํ•ด. HTML์„ ํŒŒ์‹ฑํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ํ›จ์”ฌ ๊น”๋”ํ•˜๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์ง€! ๐Ÿ“ฆ

<?php
function fetchJsonApi($url, $headers = []) {
    $ch = curl_init();
    
    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => array_merge([
            'Accept: application/json',
            'Content-Type: application/json'
        ], $headers),
        CURLOPT_TIMEOUT => 30
    ]);
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    
    curl_close($ch);
    
    if($httpCode !== 200) {
        throw new Exception("HTTP Error: $httpCode");
    }
    
    return json_decode($response, true);
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
try {
    $data = fetchJsonApi('https://api.example.com/products');
    
    foreach($data['products'] as $product) {
        echo "์ƒํ’ˆ๋ช…: {$product['name']}, ๊ฐ€๊ฒฉ: {$product['price']}์›\n";
    }
} catch(Exception $e) {
    echo "์—๋Ÿฌ ๋ฐœ์ƒ: " . $e->getMessage();
}
?>

๋ฉ€ํ‹ฐ cURL๋กœ ๋™์‹œ์— ์—ฌ๋Ÿฌ ์š”์ฒญํ•˜๊ธฐ

ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์”ฉ ์š”์ฒญํ•˜๋ฉด ๋„ˆ๋ฌด ๋А๋ฆฌ์ž–์•„? ๋ฉ€ํ‹ฐ cURL์„ ์‚ฌ์šฉํ•˜๋ฉด ์—ฌ๋Ÿฌ ์š”์ฒญ์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์„œ ์†๋„๊ฐ€ ์—„์ฒญ ๋นจ๋ผ์ ธ! โšก

<?php
function multiCurlRequest($urls) {
    $multiHandle = curl_multi_init();
    $curlHandles = [];
    
    // ๊ฐ URL์— ๋Œ€ํ•œ cURL ํ•ธ๋“ค ์ƒ์„ฑ
    foreach($urls as $i => $url) {
        $curlHandles[$i] = curl_init();
        curl_setopt_array($curlHandles[$i], [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 30
        ]);
        curl_multi_add_handle($multiHandle, $curlHandles[$i]);
    }
    
    // ๋ชจ๋“  ์š”์ฒญ ์‹คํ–‰
    $running = null;
    do {
        curl_multi_exec($multiHandle, $running);
        curl_multi_select($multiHandle);
    } while($running > 0);
    
    // ๊ฒฐ๊ณผ ์ˆ˜์ง‘
    $results = [];
    foreach($curlHandles as $i => $ch) {
        $results[$i] = curl_multi_getcontent($ch);
        curl_multi_remove_handle($multiHandle, $ch);
        curl_close($ch);
    }
    
    curl_multi_close($multiHandle);
    
    return $results;
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
$urls = [
    'https://example.com/page1',
    'https://example.com/page2',
    'https://example.com/page3'
];

$responses = multiCurlRequest($urls);

foreach($responses as $index => $html) {
    echo "ํŽ˜์ด์ง€ " . ($index + 1) . " ๊ธธ์ด: " . strlen($html) . " bytes\n";
}
?>
๐Ÿ’ก ์„ฑ๋Šฅ ํŒ: ๋ฉ€ํ‹ฐ cURL์„ ์‚ฌ์šฉํ•˜๋ฉด 10๊ฐœ์˜ ํŽ˜์ด์ง€๋ฅผ ์ˆœ์ฐจ์ ์œผ๋กœ ๊ฐ€์ ธ์˜ฌ ๋•Œ 10์ดˆ ๊ฑธ๋ฆฌ๋˜ ๊ฒƒ์„ 1~2์ดˆ๋กœ ๋‹จ์ถ•ํ•  ์ˆ˜ ์žˆ์–ด! ํ•˜์ง€๋งŒ ๋„ˆ๋ฌด ๋งŽ์€ ๋™์‹œ ์š”์ฒญ์€ ์„œ๋ฒ„์— ๋ถ€๋‹ด์„ ์ฃผ๋‹ˆ๊นŒ ์ ์ ˆํžˆ ์กฐ์ ˆํ•ด์•ผ ํ•ด.

โœจ Guzzle๋กœ ์šฐ์•„ํ•˜๊ฒŒ ์Šคํฌ๋ž˜ํ•‘ํ•˜๊ธฐ

์ž, ์ด์ œ Guzzle์˜ ์„ธ๊ณ„๋กœ ๋“ค์–ด๊ฐ€๋ณด์ž! Guzzle์€ cURL์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋งŒ๋“ค์–ด์ง„ ํ˜„๋Œ€์ ์ธ HTTP ํด๋ผ์ด์–ธํŠธ์•ผ. ์ฝ”๋“œ๊ฐ€ ํ›จ์”ฌ ๊น”๋”ํ•˜๊ณ  ์ฝ๊ธฐ ์‰ฌ์›Œ์„œ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ํŽธํ•ด! ๐ŸŽจ

Guzzle ์„ค์น˜ํ•˜๊ธฐ

Guzzle์€ Composer๋ฅผ ํ†ตํ•ด ์„ค์น˜ํ•ด์•ผ ํ•ด. ํ”„๋กœ์ ํŠธ ๋””๋ ‰ํ† ๋ฆฌ์—์„œ ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๋ผ!

composer require guzzlehttp/guzzle

Guzzle ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

Guzzle์˜ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•์€ ์ •๋ง ์ง๊ด€์ ์ด์•ผ. ๊ฐ์ฒด์ง€ํ–ฅ ๋ฐฉ์‹์ด๋ผ ์ฝ”๋“œ๊ฐ€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ฝํ˜€!

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;

// ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ (๊ธฐ๋ณธ ์„ค์ • ํฌํ•จ)
$client = new Client([
    'base_uri' => 'https://api.example.com',
    'timeout' => 30.0,
    'headers' => [
        'User-Agent' => 'MyApp/1.0',
        'Accept' => 'application/json'
    ]
]);

try {
    // GET ์š”์ฒญ
    $response = $client->request('GET', '/products');
    
    // ์‘๋‹ต ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
    $statusCode = $response->getStatusCode(); // 200
    $contentType = $response->getHeaderLine('content-type');
    $body = $response->getBody()->getContents();
    
    echo "์ƒํƒœ ์ฝ”๋“œ: $statusCode\n";
    echo "์‘๋‹ต ๋‚ด์šฉ: $body\n";
    
} catch(RequestException $e) {
    echo "์š”์ฒญ ์‹คํŒจ: " . $e->getMessage();
    
    if($e->hasResponse()) {
        echo "\n์‘๋‹ต ์ฝ”๋“œ: " . $e->getResponse()->getStatusCode();
    }
}
?>

Guzzle๋กœ POST ์š”์ฒญํ•˜๊ธฐ

POST ์š”์ฒญ๋„ cURL๋ณด๋‹ค ํ›จ์”ฌ ๊ฐ„๋‹จํ•ด! ๋‹ค์–‘ํ•œ ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‰ฝ๊ฒŒ ์ „์†กํ•  ์ˆ˜ ์žˆ์–ด. ๐Ÿš€

<?php
use GuzzleHttp\Client;

$client = new Client();

// 1. ํผ ๋ฐ์ดํ„ฐ ์ „์†ก
$response = $client->request('POST', 'https://example.com/login', [
    'form_params' => [
        'username' => 'myuser',
        'password' => 'mypass'
    ]
]);

// 2. JSON ๋ฐ์ดํ„ฐ ์ „์†ก
$response = $client->request('POST', 'https://api.example.com/users', [
    'json' => [
        'name' => 'ํ™๊ธธ๋™',
        'email' => 'hong@example.com',
        'age' => 25
    ]
]);

// 3. ๋ฉ€ํ‹ฐํŒŒํŠธ ํผ ๋ฐ์ดํ„ฐ (ํŒŒ์ผ ์—…๋กœ๋“œ)
$response = $client->request('POST', 'https://example.com/upload', [
    'multipart' => [
        [
            'name' => 'file',
            'contents' => fopen('/path/to/file.jpg', 'r'),
            'filename' => 'photo.jpg'
        ],
        [
            'name' => 'description',
            'contents' => 'ํ”„๋กœํ•„ ์‚ฌ์ง„'
        ]
    ]
]);

echo $response->getBody();
?>

Guzzle์˜ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ๋“ค

Guzzle์ด ์ธ๊ธฐ ์žˆ๋Š” ์ด์œ ๋Š” ๋‹จ์ˆœํžˆ ์‚ฌ์šฉํ•˜๊ธฐ ์‰ฌ์›Œ์„œ๋งŒ์ด ์•„๋‹ˆ์•ผ. ์ •๋ง ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ๋“ค์ด ๋งŽ๊ฑฐ๋“ ! ๐Ÿ”ฅ

๐ŸŽฏ Guzzle์˜ ์ฃผ์š” ๊ธฐ๋Šฅ 1. Promise์™€ ๋น„๋™๊ธฐ ์š”์ฒญ: ๋…ผ๋ธ”๋กœํ‚น ๋ฐฉ์‹์œผ๋กœ ์—ฌ๋Ÿฌ ์š”์ฒญ์„ ๋™์‹œ์— ์ฒ˜๋ฆฌ
2. ๋ฏธ๋“ค์›จ์–ด: ์š”์ฒญ/์‘๋‹ต์„ ๊ฐ€๋กœ์ฑ„์„œ ์ปค์Šคํ…€ ๋กœ์ง ์ถ”๊ฐ€
3. ์ž๋™ ์žฌ์‹œ๋„: ์‹คํŒจํ•œ ์š”์ฒญ์„ ์ž๋™์œผ๋กœ ์žฌ์‹œ๋„
4. ์ŠคํŠธ๋ฆฌ๋ฐ: ๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ์„ ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ
5. ์ฟ ํ‚ค ๊ด€๋ฆฌ: ์„ธ์…˜ ์œ ์ง€๋ฅผ ์œ„ํ•œ ์ฟ ํ‚ค ์ž๋™ ๊ด€๋ฆฌ

๋น„๋™๊ธฐ ์š”์ฒญ์œผ๋กœ ์„ฑ๋Šฅ ๊ทน๋Œ€ํ™”

Guzzle์˜ ๋น„๋™๊ธฐ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ฉด ์—ฌ๋Ÿฌ ํŽ˜์ด์ง€๋ฅผ ๋™์‹œ์— ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์–ด. ๋ฉ€ํ‹ฐ cURL๊ณผ ๋น„์Šทํ•˜์ง€๋งŒ ์ฝ”๋“œ๊ฐ€ ํ›จ์”ฌ ๊น”๋”ํ•ด! ๐Ÿ˜

<?php
use GuzzleHttp\Client;
use GuzzleHttp\Promise;

$client = new Client();

// ๋น„๋™๊ธฐ ์š”์ฒญ ์ƒ์„ฑ
$promises = [
    'page1' => $client->getAsync('https://example.com/page1'),
    'page2' => $client->getAsync('https://example.com/page2'),
    'page3' => $client->getAsync('https://example.com/page3')
];

// ๋ชจ๋“  ์š”์ฒญ์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
$results = Promise\Utils::unwrap($promises);

// ๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ
foreach($results as $key => $response) {
    echo "$key: " . $response->getStatusCode() . "\n";
    echo "๋‚ด์šฉ ๊ธธ์ด: " . strlen($response->getBody()) . " bytes\n\n";
}

// ๋˜๋Š” ๊ฐ ์š”์ฒญ์— ์ฝœ๋ฐฑ ์ถ”๊ฐ€
$promises = [];
$urls = [
    'https://example.com/page1',
    'https://example.com/page2',
    'https://example.com/page3'
];

foreach($urls as $url) {
    $promises[] = $client->getAsync($url)->then(
        function($response) use ($url) {
            echo "์„ฑ๊ณต: $url - " . $response->getStatusCode() . "\n";
        },
        function($exception) use ($url) {
            echo "์‹คํŒจ: $url - " . $exception->getMessage() . "\n";
        }
    );
}

// ๋ชจ๋“  Promise ์‹คํ–‰
Promise\Utils::settle($promises)->wait();
?>

์žฌ์‹œ๋„ ๋กœ์ง ๊ตฌํ˜„ํ•˜๊ธฐ

๋„คํŠธ์›Œํฌ๋Š” ๋ถˆ์•ˆ์ •ํ•  ์ˆ˜ ์žˆ์–ด. ์ผ์‹œ์ ์ธ ์˜ค๋ฅ˜๋กœ ์‹คํŒจํ•œ ์š”์ฒญ์„ ์ž๋™์œผ๋กœ ์žฌ์‹œ๋„ํ•˜๋Š” ๊ฑด ํ•„์ˆ˜์•ผ! ๐Ÿ”„

<?php
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

// ์žฌ์‹œ๋„ ๋ฏธ๋“ค์›จ์–ด ์ƒ์„ฑ
$handlerStack = HandlerStack::create();

$handlerStack->push(Middleware::retry(
    function($retries, RequestInterface $request, ResponseInterface $response = null, $exception = null) {
        // ์ตœ๋Œ€ 3๋ฒˆ๊นŒ์ง€ ์žฌ์‹œ๋„
        if($retries >= 3) {
            return false;
        }
        
        // 5xx ์—๋Ÿฌ๋‚˜ ์—ฐ๊ฒฐ ์‹คํŒจ ์‹œ ์žฌ์‹œ๋„
        if($exception !== null) {
            return true;
        }
        
        if($response && $response->getStatusCode() >= 500) {
            return true;
        }
        
        return false;
    },
    function($retries) {
        // ์žฌ์‹œ๋„ ๊ฐ„๊ฒฉ (์ง€์ˆ˜ ๋ฐฑ์˜คํ”„)
        return 1000 * pow(2, $retries);
    }
));

$client = new Client([
    'handler' => $handlerStack,
    'timeout' => 10
]);

try {
    $response = $client->request('GET', 'https://unstable-api.example.com/data');
    echo $response->getBody();
} catch(\Exception $e) {
    echo "3๋ฒˆ ์žฌ์‹œ๋„ ํ›„์—๋„ ์‹คํŒจ: " . $e->getMessage();
}
?>
๐Ÿ’ก ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„(Exponential Backoff)๋ž€? ์žฌ์‹œ๋„ํ•  ๋•Œ ๋Œ€๊ธฐ ์‹œ๊ฐ„์„ ์ ์  ๋Š˜๋ ค๊ฐ€๋Š” ์ „๋žต์ด์•ผ. 1์ดˆ โ†’ 2์ดˆ โ†’ 4์ดˆ ์ด๋Ÿฐ ์‹์œผ๋กœ! ์„œ๋ฒ„๊ฐ€ ์ผ์‹œ์ ์œผ๋กœ ๊ณผ๋ถ€ํ•˜ ์ƒํƒœ์ผ ๋•Œ ๋ฐ”๋กœ ์žฌ์‹œ๋„ํ•˜๋ฉด ์ƒํ™ฉ์„ ๋” ์•…ํ™”์‹œํ‚ฌ ์ˆ˜ ์žˆ๊ฑฐ๋“ . ์‹œ๊ฐ„์„ ๋‘๊ณ  ์žฌ์‹œ๋„ํ•˜๋ฉด ์„ฑ๊ณต ํ™•๋ฅ ์ด ๋†’์•„์ ธ!

๐Ÿ” HTML ํŒŒ์‹ฑํ•˜๊ธฐ - ๋ฐ์ดํ„ฐ ์ถ”์ถœ์˜ ํ•ต์‹ฌ

HTML์„ ๊ฐ€์ ธ์™”์œผ๋ฉด ์ด์ œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”์ถœํ•ด์•ผ์ง€! ์ด ๊ณผ์ •์„ ํŒŒ์‹ฑ(Parsing)์ด๋ผ๊ณ  ํ•ด. PHP์—๋Š” ์—ฌ๋Ÿฌ ํŒŒ์‹ฑ ๋„๊ตฌ๊ฐ€ ์žˆ๋Š”๋ฐ, ๊ฐ€์žฅ ๋งŽ์ด ์“ฐ์ด๋Š” ๊ฒƒ๋“ค์„ ์•Œ์•„๋ณด์ž! ๐ŸŽฃ

DOMDocument์™€ DOMXPath

PHP ๋‚ด์žฅ ํด๋ž˜์Šค์ธ DOMDocument๋Š” HTML/XML์„ ํŒŒ์‹ฑํ•˜๋Š” ๊ธฐ๋ณธ ๋„๊ตฌ์•ผ. ์ข€ ํˆฌ๋ฐ•ํ•˜์ง€๋งŒ ๊ฐ•๋ ฅํ•ด!

<?php
$html = '<html>
<body>
    <div class="product">
        <h2>๋…ธํŠธ๋ถ</h2>
        <span class="price">1,200,000์›</span>
    </div>
    <div class="product">
        <h2>๋งˆ์šฐ์Šค</h2>
        <span class="price">35,000์›</span>
    </div>
</body>
</html>';

// DOMDocument ์ƒ์„ฑ
$dom = new DOMDocument();
@$dom->loadHTML($html); // @ ๋Š” ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€ ์ˆจ๊น€

// XPath ์ƒ์„ฑ
$xpath = new DOMXPath($dom);

// ๋ชจ๋“  ์ƒํ’ˆ๋ช… ์ถ”์ถœ
$productNames = $xpath->query('//div[@class="product"]/h2');
foreach($productNames as $name) {
    echo "์ƒํ’ˆ๋ช…: " . $name->textContent . "\n";
}

// ๋ชจ๋“  ๊ฐ€๊ฒฉ ์ถ”์ถœ
$prices = $xpath->query('//span[@class="price"]');
foreach($prices as $price) {
    echo "๊ฐ€๊ฒฉ: " . $price->textContent . "\n";
}

// ํŠน์ • ์กฐ๊ฑด์œผ๋กœ ๊ฒ€์ƒ‰
$expensiveProducts = $xpath->query('//div[@class="product"][contains(span[@class="price"], "1,200,000")]');
echo "\n๊ณ ๊ฐ€ ์ƒํ’ˆ ๊ฐœ์ˆ˜: " . $expensiveProducts->length;
?>

Simple HTML DOM Parser

DOMDocument๊ฐ€ ๋„ˆ๋ฌด ๋ณต์žกํ•˜๋‹ค๋ฉด Simple HTML DOM Parser๋ฅผ ์จ๋ด! jQuery์ฒ˜๋Ÿผ ์ง๊ด€์ ์ด์•ผ. ๐Ÿ˜Š

<?php
// Composer๋กœ ์„ค์น˜: composer require simplehtmldom/simplehtmldom
require 'vendor/autoload.php';

use simplehtmldom\HtmlWeb;

$client = new HtmlWeb();
$html = $client->load('https://example.com');

// CSS ์„ ํƒ์ž๋กœ ์š”์†Œ ์ฐพ๊ธฐ
foreach($html->find('.product') as $product) {
    $name = $product->find('h2', 0)->plaintext;
    $price = $product->find('.price', 0)->plaintext;
    
    echo "์ƒํ’ˆ: $name - $price\n";
}

// ์†์„ฑ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ
foreach($html->find('a') as $link) {
    echo "๋งํฌ: " . $link->href . "\n";
}

// ์ฒซ ๋ฒˆ์งธ ์š”์†Œ๋งŒ ๊ฐ€์ ธ์˜ค๊ธฐ
$firstProduct = $html->find('.product', 0);
if($firstProduct) {
    echo "์ฒซ ๋ฒˆ์งธ ์ƒํ’ˆ: " . $firstProduct->find('h2', 0)->plaintext;
}
?>

Symfony DomCrawler - ํ”„๋กœํŽ˜์…”๋„์˜ ์„ ํƒ

Symfony์˜ DomCrawler ์ปดํฌ๋„ŒํŠธ๋Š” ์ •๋ง ๊ฐ•๋ ฅํ•ด! CSS ์„ ํƒ์ž์™€ XPath๋ฅผ ๋ชจ๋‘ ์ง€์›ํ•˜๊ณ , ์ฒด์ด๋‹ ๋ฐฉ์‹์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์–ด. ๐Ÿ†

<?php
// Composer๋กœ ์„ค์น˜: composer require symfony/dom-crawler symfony/css-selector
require 'vendor/autoload.php';

use Symfony\Component\DomCrawler\Crawler;

$html = file_get_contents('https://example.com');
$crawler = new Crawler($html);

// CSS ์„ ํƒ์ž๋กœ ํ•„ํ„ฐ๋ง
$products = $crawler->filter('.product')->each(function(Crawler $node) {
    return [
        'name' => $node->filter('h2')->text(),
        'price' => $node->filter('.price')->text(),
        'link' => $node->filter('a')->attr('href')
    ];
});

print_r($products);

// ํŠน์ • ํ…์ŠคํŠธ๋ฅผ ํฌํ•จํ•˜๋Š” ์š”์†Œ ์ฐพ๊ธฐ
$laptops = $crawler->filter('.product')->reduce(function(Crawler $node) {
    return strpos($node->filter('h2')->text(), '๋…ธํŠธ๋ถ') !== false;
});

echo "๋…ธํŠธ๋ถ ์ƒํ’ˆ ์ˆ˜: " . $laptops->count() . "\n";

// ํ…Œ์ด๋ธ” ๋ฐ์ดํ„ฐ ์ถ”์ถœ
$tableData = $crawler->filter('table tr')->each(function(Crawler $row) {
    return $row->filter('td')->each(function(Crawler $cell) {
        return $cell->text();
    });
});

print_r($tableData);
?>
๐ŸŽฏ ํŒŒ์‹ฑ ๋„๊ตฌ ์„ ํƒ ๊ฐ€์ด๋“œ DOMDocument: ์ถ”๊ฐ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์—†์ด ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ, ํ‘œ์ค€ ์ค€์ˆ˜๊ฐ€ ์ค‘์š”ํ•  ๋•Œ
Simple HTML DOM: ๋น ๋ฅธ ํ”„๋กœํ† ํƒ€์ดํ•‘, ๊ฐ„๋‹จํ•œ ์Šคํฌ๋ž˜ํ•‘ ์ž‘์—…
Symfony DomCrawler: ๋ณต์žกํ•œ ์Šคํฌ๋ž˜ํ•‘, ๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ, ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ ์ค‘์š”ํ•  ๋•Œ

๐ŸŽญ ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ์‡ผํ•‘๋ชฐ ๊ฐ€๊ฒฉ ๋น„๊ต ์Šคํฌ๋ž˜ํผ

์ด๋ก ๋งŒ ๋ฐฐ์šฐ๋ฉด ์žฌ๋ฏธ์—†์ง€! ์‹ค์ œ๋กœ ๋™์ž‘ํ•˜๋Š” ๊ฐ€๊ฒฉ ๋น„๊ต ์Šคํฌ๋ž˜ํผ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž. ์—ฌ๋Ÿฌ ์‡ผํ•‘๋ชฐ์˜ ์ƒํ’ˆ ๊ฐ€๊ฒฉ์„ ์ž๋™์œผ๋กœ ์ˆ˜์ง‘ํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์ด์•ผ! ๐Ÿ›’

<?php
require 'vendor/autoload.php';

use GuzzleHttp\Client;
use GuzzleHttp\Promise;
use Symfony\Component\DomCrawler\Crawler;

class PriceComparator {
    private $client;
    private $results = [];
    
    public function __construct() {
        $this->client = new Client([
            'timeout' => 30,
            'headers' => [
                'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
            ]
        ]);
    }
    
    public function searchProduct($keyword) {
        $promises = [
            'siteA' => $this->searchSiteA($keyword),
            'siteB' => $this->searchSiteB($keyword),
            'siteC' => $this->searchSiteC($keyword)
        ];
        
        $results = Promise\Utils::settle($promises)->wait();
        
        foreach($results as $site => $result) {
            if($result['state'] === 'fulfilled') {
                $this->results[$site] = $result['value'];
            } else {
                echo "[$site] ๊ฒ€์ƒ‰ ์‹คํŒจ: " . $result['reason']->getMessage() . "\n";
            }
        }
        
        return $this->getLowestPrice();
    }
    
    private function searchSiteA($keyword) {
        return $this->client->getAsync("https://siteA.com/search?q=" . urlencode($keyword))
            ->then(function($response) {
                $html = $response->getBody()->getContents();
                $crawler = new Crawler($html);
                
                $products = $crawler->filter('.product-item')->each(function(Crawler $node) {
                    return [
                        'name' => $node->filter('.product-name')->text(),
                        'price' => $this->parsePrice($node->filter('.price')->text()),
                        'url' => $node->filter('a')->attr('href')
                    ];
                });
                
                return $products;
            });
    }
    
    private function searchSiteB($keyword) {
        return $this->client->getAsync("https://siteB.com/products?keyword=" . urlencode($keyword))
            ->then(function($response) {
                $html = $response->getBody()->getContents();
                $crawler = new Crawler($html);
                
                $products = $crawler->filter('.item')->each(function(Crawler $node) {
                    return [
                        'name' => $node->filter('h3')->text(),
                        'price' => $this->parsePrice($node->filter('.item-price')->text()),
                        'url' => 'https://siteB.com' . $node->filter('a')->attr('href')
                    ];
                });
                
                return $products;
            });
    }
    
    private function searchSiteC($keyword) {
        // API๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒฝ์šฐ
        return $this->client->getAsync("https://api.siteC.com/search", [
            'query' => ['q' => $keyword]
        ])->then(function($response) {
            $data = json_decode($response->getBody(), true);
            
            return array_map(function($item) {
                return [
                    'name' => $item['title'],
                    'price' => $item['price'],
                    'url' => $item['link']
                ];
            }, $data['items']);
        });
    }
    
    private function parsePrice($priceText) {
        // "1,200,000์›" -> 1200000
        $price = preg_replace('/[^0-9]/', '', $priceText);
        return intval($price);
    }
    
    private function getLowestPrice() {
        $allProducts = [];
        
        foreach($this->results as $site => $products) {
            foreach($products as $product) {
                $product['site'] = $site;
                $allProducts[] = $product;
            }
        }
        
        // ๊ฐ€๊ฒฉ์ˆœ ์ •๋ ฌ
        usort($allProducts, function($a, $b) {
            return $a['price'] - $b['price'];
        });
        
        return $allProducts;
    }
    
    public function displayResults() {
        if(empty($this->results)) {
            echo "๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.\n";
            return;
        }
        
        $products = $this->getLowestPrice();
        
        echo "\n=== ๊ฐ€๊ฒฉ ๋น„๊ต ๊ฒฐ๊ณผ ===\n\n";
        
        foreach($products as $index => $product) {
            echo ($index + 1) . ". [{$product['site']}] {$product['name']}\n";
            echo "   ๊ฐ€๊ฒฉ: " . number_format($product['price']) . "์›\n";
            echo "   ๋งํฌ: {$product['url']}\n\n";
        }
        
        if(count($products) > 0) {
            $cheapest = $products[0];
            echo "๐Ÿ’ฐ ์ตœ์ €๊ฐ€: " . number_format($cheapest['price']) . "์› ({$cheapest['site']})\n";
        }
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
$comparator = new PriceComparator();
$comparator->searchProduct('๋ฌด์„  ์ด์–ดํฐ');
$comparator->displayResults();
?>

์ด ์ฝ”๋“œ๋Š” ์‹ค์ œ๋กœ ๋™์ž‘ํ•˜๋Š” ๊ฐ€๊ฒฉ ๋น„๊ต ์‹œ์Šคํ…œ์˜ ๊ธฐ๋ณธ ๊ตฌ์กฐ์•ผ. ๋ฌผ๋ก  ์‹ค์ œ ์‡ผํ•‘๋ชฐ์˜ HTML ๊ตฌ์กฐ์— ๋งž๊ฒŒ ์„ ํƒ์ž๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•˜์ง€๋งŒ, ํ•ต์‹ฌ ๋กœ์ง์€ ์ด๋ ‡๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด! ๐ŸŽ‰

โšก ์„ฑ๋Šฅ ์ตœ์ ํ™”์™€ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

์›น ์Šคํฌ๋ž˜ํ•‘์„ ํ•  ๋•Œ๋Š” ์„ฑ๋Šฅ๊ณผ ์•ˆ์ •์„ฑ์ด ์ •๋ง ์ค‘์š”ํ•ด. ๋ช‡ ๊ฐ€์ง€ ํ•ต์‹ฌ ํŒ์„ ์•Œ๋ ค์ค„๊ฒŒ! ๐Ÿ’Ž

1. ์บ์‹ฑ ์ „๋žต

๊ฐ™์€ ํŽ˜์ด์ง€๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ๊ฐ€์ ธ์˜ค๋Š” ๊ฑด ๋น„ํšจ์œจ์ ์ด์•ผ. ์บ์‹ฑ์„ ํ™œ์šฉํ•˜๋ฉด ์†๋„๋„ ๋น ๋ฅด๊ณ  ์„œ๋ฒ„ ๋ถ€๋‹ด๋„ ์ค„์ผ ์ˆ˜ ์žˆ์–ด!

<?php
class CachedScraper {
    private $cacheDir = './cache/';
    private $cacheTime = 3600; // 1์‹œ๊ฐ„
    
    public function __construct() {
        if(!is_dir($this->cacheDir)) {
            mkdir($this->cacheDir, 0755, true);
        }
    }
    
    public function fetch($url) {
        $cacheFile = $this->getCacheFile($url);
        
        // ์บ์‹œ๊ฐ€ ์œ ํšจํ•˜๋ฉด ์บ์‹œ ๋ฐ˜ํ™˜
        if(file_exists($cacheFile) && (time() - filemtime($cacheFile)) < $this->cacheTime) {
            return file_get_contents($cacheFile);
        }
        
        // ์ƒˆ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ
        $client = new \GuzzleHttp\Client();
        $response = $client->request('GET', $url);
        $content = $response->getBody()->getContents();
        
        // ์บ์‹œ ์ €์žฅ
        file_put_contents($cacheFile, $content);
        
        return $content;
    }
    
    private function getCacheFile($url) {
        return $this->cacheDir . md5($url) . '.cache';
    }
    
    public function clearCache() {
        $files = glob($this->cacheDir . '*.cache');
        foreach($files as $file) {
            unlink($file);
        }
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
$scraper = new CachedScraper();
$html = $scraper->fetch('https://example.com');
?>

2. Rate Limiting (์†๋„ ์ œํ•œ)

์„œ๋ฒ„์— ๋„ˆ๋ฌด ๋น ๋ฅด๊ฒŒ ์š”์ฒญํ•˜๋ฉด IP๊ฐ€ ์ฐจ๋‹จ๋  ์ˆ˜ ์žˆ์–ด. ์ ์ ˆํ•œ ๋”œ๋ ˆ์ด๋ฅผ ๋‘๋Š” ๊ฒŒ ์ค‘์š”ํ•ด! โฑ๏ธ

<?php
class RateLimitedScraper {
    private $lastRequestTime = 0;
    private $minDelay = 1; // ์ดˆ
    
    public function fetch($url) {
        // ๋งˆ์ง€๋ง‰ ์š”์ฒญ ์ดํ›„ ์ถฉ๋ถ„ํ•œ ์‹œ๊ฐ„์ด ์ง€๋‚ฌ๋Š”์ง€ ํ™•์ธ
        $elapsed = microtime(true) - $this->lastRequestTime;
        
        if($elapsed < $this->minDelay) {
            $sleepTime = ($this->minDelay - $elapsed) * 1000000; // ๋งˆ์ดํฌ๋กœ์ดˆ
            usleep($sleepTime);
        }
        
        $client = new \GuzzleHttp\Client();
        $response = $client->request('GET', $url);
        
        $this->lastRequestTime = microtime(true);
        
        return $response->getBody()->getContents();
    }
    
    public function fetchMultiple($urls) {
        $results = [];
        
        foreach($urls as $url) {
            echo "Fetching: $url\n";
            $results[] = $this->fetch($url);
        }
        
        return $results;
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
$scraper = new RateLimitedScraper();
$urls = [
    'https://example.com/page1',
    'https://example.com/page2',
    'https://example.com/page3'
];

$results = $scraper->fetchMultiple($urls);
// ๊ฐ ์š”์ฒญ ์‚ฌ์ด์— 1์ดˆ์”ฉ ๋Œ€๊ธฐ
?>

3. User-Agent ๋กœํ…Œ์ด์…˜

๊ฐ™์€ User-Agent๋กœ ๊ณ„์† ์š”์ฒญํ•˜๋ฉด ๋ด‡์œผ๋กœ ์ธ์‹๋  ์ˆ˜ ์žˆ์–ด. ์—ฌ๋Ÿฌ User-Agent๋ฅผ ๋ฒˆ๊ฐˆ์•„ ์‚ฌ์šฉํ•˜๋ฉด ๋” ์ž์—ฐ์Šค๋Ÿฌ์›Œ!

<?php
class SmartScraper {
    private $userAgents = [
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15',
        'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
    ];
    
    private $client;
    
    public function __construct() {
        $this->client = new \GuzzleHttp\Client([
            'timeout' => 30,
            'allow_redirects' => true
        ]);
    }
    
    private function getRandomUserAgent() {
        return $this->userAgents[array_rand($this->userAgents)];
    }
    
    public function fetch($url) {
        $response = $this->client->request('GET', $url, [
            'headers' => [
                'User-Agent' => $this->getRandomUserAgent(),
                'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
                'Accept-Language' => 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
                'Accept-Encoding' => 'gzip, deflate, br',
                'DNT' => '1',
                'Connection' => 'keep-alive',
                'Upgrade-Insecure-Requests' => '1'
            ]
        ]);
        
        return $response->getBody()->getContents();
    }
}
?>

4. ์—๋Ÿฌ ํ•ธ๋“ค๋ง๊ณผ ๋กœ๊น…

์Šคํฌ๋ž˜ํ•‘์€ ์–ธ์ œ๋“  ์‹คํŒจํ•  ์ˆ˜ ์žˆ์–ด. ๊ฒฌ๊ณ ํ•œ ์—๋Ÿฌ ํ•ธ๋“ค๋ง๊ณผ ๋กœ๊น…์ด ํ•„์ˆ˜์•ผ! ๐Ÿ“

<?php
class RobustScraper {
    private $logFile = 'scraper.log';
    
    public function fetch($url, $maxRetries = 3) {
        $attempt = 0;
        
        while($attempt < $maxRetries) {
            try {
                $attempt++;
                $this->log("[$attempt/$maxRetries] Fetching: $url");
                
                $client = new \GuzzleHttp\Client(['timeout' => 30]);
                $response = $client->request('GET', $url);
                
                $statusCode = $response->getStatusCode();
                
                if($statusCode === 200) {
                    $this->log("Success: $url");
                    return $response->getBody()->getContents();
                } else {
                    $this->log("Unexpected status code: $statusCode for $url");
                }
                
            } catch(\GuzzleHttp\Exception\ConnectException $e) {
                $this->log("Connection error for $url: " . $e->getMessage());
                
            } catch(\GuzzleHttp\Exception\RequestException $e) {
                $this->log("Request error for $url: " . $e->getMessage());
                
                if($e->hasResponse()) {
                    $statusCode = $e->getResponse()->getStatusCode();
                    
                    // 4xx ์—๋Ÿฌ๋Š” ์žฌ์‹œ๋„ํ•ด๋„ ์†Œ์šฉ์—†์Œ
                    if($statusCode >= 400 && $statusCode < 500) {
                        $this->log("Client error ($statusCode), stopping retries");
                        throw $e;
                    }
                }
                
            } catch(\Exception $e) {
                $this->log("Unexpected error for $url: " . $e->getMessage());
            }
            
            // ์žฌ์‹œ๋„ ์ „ ๋Œ€๊ธฐ
            if($attempt < $maxRetries) {
                $waitTime = pow(2, $attempt); // ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„
                $this->log("Waiting {$waitTime}s before retry...");
                sleep($waitTime);
            }
        }
        
        $this->log("Failed to fetch $url after $maxRetries attempts");
        throw new \Exception("Failed to fetch $url");
    }
    
    private function log($message) {
        $timestamp = date('Y-m-d H:i:s');
        $logMessage = "[$timestamp] $message\n";
        
        file_put_contents($this->logFile, $logMessage, FILE_APPEND);
        echo $logMessage;
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
$scraper = new RobustScraper();

try {
    $html = $scraper->fetch('https://example.com');
    echo "Successfully fetched " . strlen($html) . " bytes\n";
} catch(\Exception $e) {
    echo "Fatal error: " . $e->getMessage() . "\n";
}
?>
โš ๏ธ ๋ฒ•์  ์ฃผ์˜์‚ฌํ•ญ ์›น ์Šคํฌ๋ž˜ํ•‘์„ ํ•  ๋•Œ๋Š” ๋ฐ˜๋“œ์‹œ ๋‹ค์Œ ์‚ฌํ•ญ๋“ค์„ ํ™•์ธํ•ด์•ผ ํ•ด:

1. robots.txt ํ™•์ธ: ์‚ฌ์ดํŠธ๊ฐ€ ํฌ๋กค๋ง์„ ํ—ˆ์šฉํ•˜๋Š”์ง€ ํ™•์ธ
2. ์ด์šฉ์•ฝ๊ด€ ๊ฒ€ํ† : ์ž๋™ํ™”๋œ ์ ‘๊ทผ์„ ๊ธˆ์ง€ํ•˜๋Š”์ง€ ํ™•์ธ
3. ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ: ๊ฐœ์ธ์ •๋ณด๋ฅผ ์ˆ˜์ง‘ํ•˜๊ฑฐ๋‚˜ ์ €์žฅํ•˜์ง€ ์•Š๊ธฐ
4. ์ €์ž‘๊ถŒ ์กด์ค‘: ์ˆ˜์ง‘ํ•œ ๋ฐ์ดํ„ฐ์˜ ์ƒ์—…์  ์ด์šฉ ์ „ ๊ถŒ๋ฆฌ ํ™•์ธ
5. ์„œ๋ฒ„ ๋ถ€ํ•˜ ์ตœ์†Œํ™”: ์ ์ ˆํ•œ ๋”œ๋ ˆ์ด์™€ ๋™์‹œ ์—ฐ๊ฒฐ ์ˆ˜ ์ œํ•œ

๐ŸŒŸ ๊ณ ๊ธ‰ ๊ธฐ๋ฒ•: JavaScript ๋ Œ๋”๋ง ํŽ˜์ด์ง€ ์Šคํฌ๋ž˜ํ•‘

์š”์ฆ˜ ๋งŽ์€ ์›น์‚ฌ์ดํŠธ๊ฐ€ React, Vue, Angular ๊ฐ™์€ ํ”„๋ ˆ์ž„์›Œํฌ๋กœ ๋งŒ๋“ค์–ด์ ธ์„œ JavaScript๋กœ ์ฝ˜ํ…์ธ ๋ฅผ ๋™์ ์œผ๋กœ ์ƒ์„ฑํ•ด. ์ผ๋ฐ˜์ ์ธ HTTP ์š”์ฒญ์œผ๋กœ๋Š” ๋นˆ HTML๋งŒ ๋ฐ›๊ฒŒ ๋˜๋Š” ๊ฑฐ์ง€. ์ด๋Ÿด ๋•Œ๋Š” ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ? ๐Ÿค”

Headless Browser ํ™œ์šฉ

Headless Browser๋Š” ํ™”๋ฉด ์—†์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰๋˜๋Š” ๋ธŒ๋ผ์šฐ์ €์•ผ. JavaScript๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์™„์ „ํžˆ ๋ Œ๋”๋ง๋œ ํŽ˜์ด์ง€๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์–ด!

๐ŸŽฏ PHP์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ Headless Browser ๋„๊ตฌ 1. Chrome PHP: Chrome/Chromium์„ ์ œ์–ดํ•˜๋Š” PHP ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
2. Symfony Panther: Symfony์—์„œ ๋งŒ๋“  ์›น ์Šคํฌ๋ž˜ํ•‘ ๋„๊ตฌ
3. Puppeteer (Node.js): PHP์—์„œ Node.js ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰
4. Selenium WebDriver: ๋ธŒ๋ผ์šฐ์ € ์ž๋™ํ™”์˜ ํ‘œ์ค€

Chrome PHP ์‚ฌ์šฉ ์˜ˆ์ œ

<?php
// Composer๋กœ ์„ค์น˜: composer require chrome-php/chrome
require 'vendor/autoload.php';

use HeadlessChromium\BrowserFactory;

$browserFactory = new BrowserFactory('chromium-browser');

// ๋ธŒ๋ผ์šฐ์ € ์‹œ์ž‘
$browser = $browserFactory->createBrowser([
    'headless' => true,
    'noSandbox' => true,
]);

try {
    // ์ƒˆ ํŽ˜์ด์ง€ ์ƒ์„ฑ
    $page = $browser->createPage();
    
    // URL ์ด๋™
    $page->navigate('https://example.com')->waitForNavigation();
    
    // JavaScript ์‹คํ–‰ ๋Œ€๊ธฐ
    $page->evaluate('document.readyState')->waitForResponse();
    
    // ํŠน์ • ์š”์†Œ๊ฐ€ ๋‚˜ํƒ€๋‚  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
    $page->waitForElement('.product-list', 10000); // 10์ดˆ ๋Œ€๊ธฐ
    
    // HTML ๊ฐ€์ ธ์˜ค๊ธฐ
    $html = $page->evaluate('document.documentElement.outerHTML')->getReturnValue();
    
    // ์Šคํฌ๋ฆฐ์ƒท ์ฐ๊ธฐ
    $page->screenshot()->saveToFile('screenshot.png');
    
    // ํŠน์ • ์š”์†Œ ํด๋ฆญ
    $page->mouse()->find('.load-more-button')->click();
    
    // ์Šคํฌ๋กค
    $page->evaluate('window.scrollTo(0, document.body.scrollHeight)');
    
    // ํผ ์ž…๋ ฅ
    $page->evaluate('document.querySelector("#search").value = "๋…ธํŠธ๋ถ"');
    $page->keyboard()->typeText('๋…ธํŠธ๋ถ');
    
    echo "ํŽ˜์ด์ง€ ๊ธธ์ด: " . strlen($html) . " bytes\n";
    
} finally {
    // ๋ธŒ๋ผ์šฐ์ € ์ข…๋ฃŒ
    $browser->close();
}
?>

Symfony Panther๋กœ ๋” ์‰ฝ๊ฒŒ

Symfony Panther๋Š” ์‚ฌ์šฉํ•˜๊ธฐ ๋” ์‰ฝ๊ณ  ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด!

<?php
// Composer๋กœ ์„ค์น˜: composer require symfony/panther
require 'vendor/autoload.php';

use Symfony\Component\Panther\Client;

// Chrome ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ
$client = Client::createChromeClient();

// ํŽ˜์ด์ง€ ๋ฐฉ๋ฌธ
$crawler = $client->request('GET', 'https://example.com');

// JavaScript๊ฐ€ ์‹คํ–‰๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
$client->waitFor('.product-list');

// ์š”์†Œ ์ฐพ๊ธฐ (CSS ์„ ํƒ์ž)
$products = $crawler->filter('.product-item')->each(function($node) {
    return [
        'name' => $node->filter('.product-name')->text(),
        'price' => $node->filter('.price')->text()
    ];
});

print_r($products);

// ๋ฒ„ํŠผ ํด๋ฆญ
$crawler->filter('.load-more')->click();

// ํผ ์ œ์ถœ
$form = $crawler->selectButton('๊ฒ€์ƒ‰')->form();
$crawler = $client->submit($form, ['keyword' => '๋…ธํŠธ๋ถ']);

// ์Šคํฌ๋ฆฐ์ƒท
$client->takeScreenshot('result.png');

$client->quit();
?>
๐Ÿ’ก Headless Browser ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์‚ฌํ•ญ 1. ๋ฆฌ์†Œ์Šค ์†Œ๋ชจ: ์ผ๋ฐ˜ HTTP ์š”์ฒญ๋ณด๋‹ค ํ›จ์”ฌ ๋งŽ์€ ๋ฉ”๋ชจ๋ฆฌ์™€ CPU ์‚ฌ์šฉ
2. ์†๋„: JavaScript ์‹คํ–‰๊ณผ ๋ Œ๋”๋ง ๋•Œ๋ฌธ์— ๋А๋ฆผ
3. ์„œ๋ฒ„ ํ™˜๊ฒฝ: Chrome/Chromium์ด ์„ค์น˜๋˜์–ด ์žˆ์–ด์•ผ ํ•จ
4. ๋น„์šฉ: ํด๋ผ์šฐ๋“œ ํ™˜๊ฒฝ์—์„œ๋Š” ์ถ”๊ฐ€ ๋น„์šฉ ๋ฐœ์ƒ ๊ฐ€๋Šฅ

๊ผญ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉํ•˜๊ณ , ๊ฐ€๋Šฅํ•˜๋ฉด API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์ข‹์•„!

๐Ÿ” ์ธ์ฆ์ด ํ•„์š”ํ•œ ํŽ˜์ด์ง€ ์Šคํฌ๋ž˜ํ•‘

๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•œ ํŽ˜์ด์ง€๋ฅผ ์Šคํฌ๋ž˜ํ•‘ํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ? ์„ธ์…˜๊ณผ ์ฟ ํ‚ค ๊ด€๋ฆฌ๊ฐ€ ํ•ต์‹ฌ์ด์•ผ! ๐Ÿ”‘

์ฟ ํ‚ค ๊ธฐ๋ฐ˜ ์ธ์ฆ

<?php
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;

class AuthenticatedScraper {
    private $client;
    private $cookieJar;
    
    public function __construct() {
        $this->cookieJar = new CookieJar();
        $this->client = new Client([
            'cookies' => $this->cookieJar,
            'timeout' => 30
        ]);
    }
    
    public function login($username, $password) {
        // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ๋ฐฉ๋ฌธ (CSRF ํ† ํฐ ๋“ฑ์„ ์–ป๊ธฐ ์œ„ํ•ด)
        $response = $this->client->request('GET', 'https://example.com/login');
        $html = $response->getBody()->getContents();
        
        // CSRF ํ† ํฐ ์ถ”์ถœ (ํ•„์š”ํ•œ ๊ฒฝ์šฐ)
        preg_match('/name="csrf_token" value="([^"]+)"/', $html, $matches);
        $csrfToken = $matches[1] ?? '';
        
        // ๋กœ๊ทธ์ธ ์š”์ฒญ
        $response = $this->client->request('POST', 'https://example.com/login', [
            'form_params' => [
                'username' => $username,
                'password' => $password,
                'csrf_token' => $csrfToken
            ]
        ]);
        
        // ๋กœ๊ทธ์ธ ์„ฑ๊ณต ํ™•์ธ
        if($response->getStatusCode() === 200) {
            echo "๋กœ๊ทธ์ธ ์„ฑ๊ณต!\n";
            return true;
        }
        
        return false;
    }
    
    public function fetchProtectedPage($url) {
        // ์ฟ ํ‚ค๊ฐ€ ์ž๋™์œผ๋กœ ํฌํ•จ๋จ
        $response = $this->client->request('GET', $url);
        return $response->getBody()->getContents();
    }
    
    public function saveCookies($filename) {
        // ์ฟ ํ‚ค๋ฅผ ํŒŒ์ผ๋กœ ์ €์žฅ
        $cookies = [];
        foreach($this->cookieJar as $cookie) {
            $cookies[] = [
                'Name' => $cookie->getName(),
                'Value' => $cookie->getValue(),
                'Domain' => $cookie->getDomain(),
                'Path' => $cookie->getPath(),
                'Expires' => $cookie->getExpires()
            ];
        }
        file_put_contents($filename, json_encode($cookies));
    }
    
    public function loadCookies($filename) {
        // ์ €์žฅ๋œ ์ฟ ํ‚ค ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
        if(file_exists($filename)) {
            $cookies = json_decode(file_get_contents($filename), true);
            foreach($cookies as $cookieData) {
                $this->cookieJar->setCookie(new \GuzzleHttp\Cookie\SetCookie($cookieData));
            }
        }
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
$scraper = new AuthenticatedScraper();

// ์ €์žฅ๋œ ์ฟ ํ‚ค๊ฐ€ ์žˆ์œผ๋ฉด ๋กœ๋“œ
$scraper->loadCookies('session_cookies.json');

// ์ฟ ํ‚ค๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ๋กœ๊ทธ์ธ
try {
    $html = $scraper->fetchProtectedPage('https://example.com/mypage');
} catch(\Exception $e) {
    echo "์„ธ์…˜ ๋งŒ๋ฃŒ, ์žฌ๋กœ๊ทธ์ธ...\n";
    $scraper->login('myusername', 'mypassword');
    $scraper->saveCookies('session_cookies.json');
    $html = $scraper->fetchProtectedPage('https://example.com/mypage');
}

echo "ํŽ˜์ด์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ ์„ฑ๊ณต!\n";
?>

API ํ† ํฐ ์ธ์ฆ

๋งŽ์€ ํ˜„๋Œ€์ ์ธ ์›น์‚ฌ์ดํŠธ๋Š” API ํ† ํฐ์„ ์‚ฌ์šฉํ•ด. ์ด๊ฒŒ ํ›จ์”ฌ ๊น”๋”ํ•˜๊ณ  ์•ˆ์ „ํ•ด! ๐ŸŽซ

<?php
use GuzzleHttp\Client;

class TokenAuthScraper {
    private $client;
    private $token;
    
    public function __construct() {
        $this->client = new Client(['timeout' => 30]);
    }
    
    public function authenticate($apiKey, $apiSecret) {
        // OAuth 2.0 ํ† ํฐ ๋ฐœ๊ธ‰
        $response = $this->client->request('POST', 'https://api.example.com/oauth/token', [
            'form_params' => [
                'grant_type' => 'client_credentials',
                'client_id' => $apiKey,
                'client_secret' => $apiSecret
            ]
        ]);
        
        $data = json_decode($response->getBody(), true);
        $this->token = $data['access_token'];
        
        echo "ํ† ํฐ ๋ฐœ๊ธ‰ ์„ฑ๊ณต!\n";
    }
    
    public function fetchData($endpoint) {
        $response = $this->client->request('GET', "https://api.example.com/$endpoint", [
            'headers' => [
                'Authorization' => "Bearer {$this->token}",
                'Accept' => 'application/json'
            ]
        ]);
        
        return json_decode($response->getBody(), true);
    }
    
    public function saveToken($filename) {
        file_put_contents($filename, json_encode([
            'token' => $this->token,
            'created_at' => time()
        ]));
    }
    
    public function loadToken($filename) {
        if(file_exists($filename)) {
            $data = json_decode(file_get_contents($filename), true);
            
            // ํ† ํฐ์ด 24์‹œ๊ฐ„ ์ด๋‚ด๋ฉด ์žฌ์‚ฌ์šฉ
            if(time() - $data['created_at'] < 86400) {
                $this->token = $data['token'];
                return true;
            }
        }
        return false;
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
$scraper = new TokenAuthScraper();

if(!$scraper->loadToken('api_token.json')) {
    $scraper->authenticate('your_api_key', 'your_api_secret');
    $scraper->saveToken('api_token.json');
}

$products = $scraper->fetchData('products');
print_r($products);
?>

๐Ÿ“Š ๋ฐ์ดํ„ฐ ์ €์žฅ๊ณผ ๊ด€๋ฆฌ

์Šคํฌ๋ž˜ํ•‘ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌํ• ๊นŒ? ์šฉ๋„์— ๋”ฐ๋ผ ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์ด ์žˆ์–ด! ๐Ÿ’พ

1. CSV ํŒŒ์ผ๋กœ ์ €์žฅ

๊ฐ„๋‹จํ•œ ํ‘œ ํ˜•ํƒœ์˜ ๋ฐ์ดํ„ฐ๋Š” CSV๊ฐ€ ์ตœ๊ณ ์•ผ. Excel์—์„œ๋„ ๋ฐ”๋กœ ์—ด ์ˆ˜ ์žˆ๊ณ !

<?php
class CsvExporter {
    private $filename;
    private $handle;
    
    public function __construct($filename) {
        $this->filename = $filename;
        $this->handle = fopen($filename, 'w');
        
        // UTF-8 BOM ์ถ”๊ฐ€ (Excel์—์„œ ํ•œ๊ธ€ ๊นจ์ง ๋ฐฉ์ง€)
        fprintf($this->handle, chr(0xEF).chr(0xBB).chr(0xBF));
    }
    
    public function writeHeader($headers) {
        fputcsv($this->handle, $headers);
    }
    
    public function writeRow($data) {
        fputcsv($this->handle, $data);
    }
    
    public function writeRows($rows) {
        foreach($rows as $row) {
            $this->writeRow($row);
        }
    }
    
    public function close() {
        fclose($this->handle);
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
$products = [
    ['๋…ธํŠธ๋ถ', 1200000, '์‚ผ์„ฑ์ „์ž', '์žฌ๊ณ ์žˆ์Œ'],
    ['๋งˆ์šฐ์Šค', 35000, '๋กœ์ง€ํ…', '์žฌ๊ณ ์žˆ์Œ'],
    ['ํ‚ค๋ณด๋“œ', 89000, '๋ ˆ์˜คํด๋“œ', 'ํ’ˆ์ ˆ']
];

$exporter = new CsvExporter('products.csv');
$exporter->writeHeader(['์ƒํ’ˆ๋ช…', '๊ฐ€๊ฒฉ', '์ œ์กฐ์‚ฌ', '์žฌ๊ณ ์ƒํƒœ']);
$exporter->writeRows($products);
$exporter->close();

echo "CSV ํŒŒ์ผ ์ƒ์„ฑ ์™„๋ฃŒ!\n";
?>

2. JSON ํŒŒ์ผ๋กœ ์ €์žฅ

๋ณต์žกํ•œ ๊ตฌ์กฐ์˜ ๋ฐ์ดํ„ฐ๋Š” JSON์ด ํŽธํ•ด. ๋‚˜์ค‘์— ๋‹ค์‹œ ์ฝ์–ด์„œ ์ฒ˜๋ฆฌํ•˜๊ธฐ๋„ ์‰ฝ๊ณ !

<?php
class JsonStorage {
    private $filename;
    
    public function __construct($filename) {
        $this->filename = $filename;
    }
    
    public function save($data) {
        $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
        file_put_contents($this->filename, $json);
    }
    
    public function load() {
        if(file_exists($this->filename)) {
            return json_decode(file_get_contents($this->filename), true);
        }
        return [];
    }
    
    public function append($newData) {
        $existing = $this->load();
        $existing[] = $newData;
        $this->save($existing);
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
$storage = new JsonStorage('products.json');

$product = [
    'id' => 1,
    'name' => '๋ฌด์„  ์ด์–ดํฐ',
    'price' => 89000,
    'specs' => [
        'battery' => '6์‹œ๊ฐ„',
        'bluetooth' => '5.0',
        'color' => ['๋ธ”๋ž™', 'ํ™”์ดํŠธ']
    ],
    'reviews' => [
        ['rating' => 5, 'comment' => '์Œ์งˆ ์ข‹์•„์š”'],
        ['rating' => 4, 'comment' => '๊ฐ€์„ฑ๋น„ ์ตœ๊ณ ']
    ]
];

$storage->append($product);
echo "JSON ์ €์žฅ ์™„๋ฃŒ!\n";
?>

3. MySQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ

๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒด๊ณ„์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋ ค๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ํ•„์ˆ˜์•ผ! ๐Ÿ—„๏ธ

<?php
class DatabaseStorage {
    private $pdo;
    
    public function __construct($host, $dbname, $username, $password) {
        $dsn = "mysql:host=$host;dbname=$dbname;charset=utf8mb4";
        $this->pdo = new PDO($dsn, $username, $password, [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ]);
    }
    
    public function createTable() {
        $sql = "CREATE TABLE IF NOT EXISTS products (
            id INT AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(255) NOT NULL,
            price INT NOT NULL,
            manufacturer VARCHAR(100),
            stock_status VARCHAR(50),
            url TEXT,
            scraped_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            INDEX idx_name (name),
            INDEX idx_price (price)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
        
        $this->pdo->exec($sql);
    }
    
    public function insertProduct($product) {
        $sql = "INSERT INTO products (name, price, manufacturer, stock_status, url) 
                VALUES (:name, :price, :manufacturer, :stock_status, :url)";
        
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($product);
        
        return $this->pdo->lastInsertId();
    }
    
    public function insertBatch($products) {
        $this->pdo->beginTransaction();
        
        try {
            $sql = "INSERT INTO products (name, price, manufacturer, stock_status, url) 
                    VALUES (:name, :price, :manufacturer, :stock_status, :url)";
            $stmt = $this->pdo->prepare($sql);
            
            foreach($products as $product) {
                $stmt->execute($product);
            }
            
            $this->pdo->commit();
            echo count($products) . "๊ฐœ ์ƒํ’ˆ ์ €์žฅ ์™„๋ฃŒ!\n";
            
        } catch(\Exception $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }
    
    public function updatePrice($productId, $newPrice) {
        $sql = "UPDATE products SET price = :price WHERE id = :id";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute(['price' => $newPrice, 'id' => $productId]);
    }
    
    public function findByName($name) {
        $sql = "SELECT * FROM products WHERE name LIKE :name ORDER BY price ASC";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute(['name' => "%$name%"]);
        return $stmt->fetchAll();
    }
    
    public function getPriceHistory($productName) {
        $sql = "SELECT price, scraped_at FROM products 
                WHERE name = :name 
                ORDER BY scraped_at DESC 
                LIMIT 30";
        
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute(['name' => $productName]);
        return $stmt->fetchAll();
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
$db = new DatabaseStorage('localhost', 'scraping_db', 'username', 'password');
$db->createTable();

$products = [
    [
        'name' => '๋ฌด์„  ์ด์–ดํฐ',
        'price' => 89000,
        'manufacturer' => '์‚ผ์„ฑ',
        'stock_status' => '์žฌ๊ณ ์žˆ์Œ',
        'url' => 'https://example.com/product/1'
    ],
    [
        'name' => '๋ธ”๋ฃจํˆฌ์Šค ์Šคํ”ผ์ปค',
        'price' => 125000,
        'manufacturer' => 'JBL',
        'stock_status' => '์žฌ๊ณ ์žˆ์Œ',
        'url' => 'https://example.com/product/2'
    ]
];

$db->insertBatch($products);

// ๊ฒ€์ƒ‰
$results = $db->findByName('์ด์–ดํฐ');
print_r($results);
?>
๐Ÿ’ก ๋ฐ์ดํ„ฐ ์ €์žฅ ๋ฐฉ๋ฒ• ์„ ํƒ ๊ฐ€์ด๋“œ CSV: ์†Œ๋Ÿ‰ ๋ฐ์ดํ„ฐ, ์ผํšŒ์„ฑ ๋ถ„์„, Excel ๊ณต์œ  ํ•„์š”ํ•  ๋•Œ
JSON: ๋ณต์žกํ•œ ๊ตฌ์กฐ, API ์‘๋‹ต ์ €์žฅ, ์ค‘๊ฐ„ ๊ทœ๋ชจ ๋ฐ์ดํ„ฐ
SQLite: ์ค‘๊ฐ„ ๊ทœ๋ชจ, ๋‹จ์ผ ํŒŒ์ผ๋กœ ๊ด€๋ฆฌ, ์„œ๋ฒ„ ์„ค์ • ๋ถˆํ•„์š”
MySQL/PostgreSQL: ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ, ๋ณต์žกํ•œ ์ฟผ๋ฆฌ, ๋‹ค์ค‘ ์‚ฌ์šฉ์ž, ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ

๐ŸŽ“ ์‹ค๋ฌด์—์„œ ์ž์ฃผ ๋งŒ๋‚˜๋Š” ๋ฌธ์ œ๋“ค๊ณผ ํ•ด๊ฒฐ๋ฒ•

์ด๋ก ์€ ์™„๋ฒฝํ•ด๋„ ์‹ค์ „์—์„œ๋Š” ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋ฌธ์ œ๋“ค์ด ์ƒ๊ฒจ. ์ž์ฃผ ๋งŒ๋‚˜๋Š” ๋ฌธ์ œ๋“ค๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์•Œ๋ ค์ค„๊ฒŒ! ๐Ÿ”ง

๋ฌธ์ œ 1: CAPTCHA์™€ ๋ด‡ ํƒ์ง€

๋งŽ์€ ์‚ฌ์ดํŠธ๊ฐ€ ๋ด‡์„ ๋ง‰๊ธฐ ์œ„ํ•ด CAPTCHA๋ฅผ ์‚ฌ์šฉํ•ด. ์ด๊ฑธ ์–ด๋–ป๊ฒŒ ์šฐํšŒํ• ๊นŒ?

๐ŸŽฏ CAPTCHA ๋Œ€์‘ ์ „๋žต 1. API ์‚ฌ์šฉ: ๊ฐ€๋Šฅํ•˜๋ฉด ๊ณต์‹ API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์ตœ์„ 
2. CAPTCHA ํ•ด๊ฒฐ ์„œ๋น„์Šค: 2Captcha, Anti-Captcha ๊ฐ™์€ ์œ ๋ฃŒ ์„œ๋น„์Šค ํ™œ์šฉ
3. ์‚ฌ๋žŒ์ฒ˜๋Ÿผ ํ–‰๋™: ๋งˆ์šฐ์Šค ์›€์ง์ž„, ์Šคํฌ๋กค, ๋žœ๋ค ๋”œ๋ ˆ์ด ์ถ”๊ฐ€
4. ์„ธ์…˜ ์žฌ์‚ฌ์šฉ: ํ•œ ๋ฒˆ ์ธ์ฆ๋ฐ›์€ ์„ธ์…˜์„ ์ตœ๋Œ€ํ•œ ์˜ค๋ž˜ ์‚ฌ์šฉ
5. ํ˜‘์˜: ์‚ฌ์ดํŠธ ์šด์˜์ž์—๊ฒŒ ์—ฐ๋ฝํ•ด์„œ ํ—ˆ๊ฐ€๋ฐ›๊ธฐ

๋ฌธ์ œ 2: ๋™์ ์œผ๋กœ ๋กœ๋“œ๋˜๋Š” ์ฝ˜ํ…์ธ 

๋ฌดํ•œ ์Šคํฌ๋กค์ด๋‚˜ "๋”๋ณด๊ธฐ" ๋ฒ„ํŠผ์œผ๋กœ ์ฝ˜ํ…์ธ ๊ฐ€ ๋กœ๋“œ๋˜๋Š” ๊ฒฝ์šฐ!

<?php
// Panther๋กœ ๋ฌดํ•œ ์Šคํฌ๋กค ์ฒ˜๋ฆฌ
use Symfony\Component\Panther\Client;

$client = Client::createChromeClient();
$crawler = $client->request('GET', 'https://example.com/infinite-scroll');

$allProducts = [];
$previousCount = 0;
$maxScrolls = 10;
$scrollCount = 0;

while($scrollCount < $maxScrolls) {
    // ํ˜„์žฌ ์ƒํ’ˆ ์ˆ˜์ง‘
    $products = $crawler->filter('.product-item')->each(function($node) {
        return [
            'name' => $node->filter('.name')->text(),
            'price' => $node->filter('.price')->text()
        ];
    });
    
    // ์ƒˆ๋กœ์šด ์ƒํ’ˆ์ด ์—†์œผ๋ฉด ์ข…๋ฃŒ
    if(count($products) === $previousCount) {
        break;
    }
    
    $allProducts = $products;
    $previousCount = count($products);
    
    // ํŽ˜์ด์ง€ ๋๊นŒ์ง€ ์Šคํฌ๋กค
    $client->executeScript('window.scrollTo(0, document.body.scrollHeight)');
    
    // ์ƒˆ ์ฝ˜ํ…์ธ  ๋กœ๋”ฉ ๋Œ€๊ธฐ
    sleep(2);
    
    $scrollCount++;
    echo "์Šคํฌ๋กค $scrollCount: " . count($products) . "๊ฐœ ์ƒํ’ˆ ๋ฐœ๊ฒฌ\n";
}

echo "์ด " . count($allProducts) . "๊ฐœ ์ƒํ’ˆ ์ˆ˜์ง‘ ์™„๋ฃŒ!\n";
$client->quit();
?>

๋ฌธ์ œ 3: IP ์ฐจ๋‹จ

๋„ˆ๋ฌด ๋งŽ์€ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด IP๊ฐ€ ์ฐจ๋‹จ๋  ์ˆ˜ ์žˆ์–ด. ํ”„๋ก์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•ด๊ฒฐ๋ผ!

<?php
use GuzzleHttp\Client;

class ProxyScraper {
    private $proxies = [
        'http://proxy1.example.com:8080',
        'http://proxy2.example.com:8080',
        'http://proxy3.example.com:8080'
    ];
    
    private $currentProxyIndex = 0;
    
    public function fetch($url) {
        $proxy = $this->getNextProxy();
        
        $client = new Client([
            'proxy' => $proxy,
            'timeout' => 30,
            'verify' => false // SSL ๊ฒ€์ฆ ๋น„ํ™œ์„ฑํ™” (ํ”„๋ก์‹œ ์‚ฌ์šฉ ์‹œ)
        ]);
        
        try {
            $response = $client->request('GET', $url);
            return $response->getBody()->getContents();
            
        } catch(\Exception $e) {
            echo "ํ”„๋ก์‹œ $proxy ์‹คํŒจ: " . $e->getMessage() . "\n";
            
            // ๋‹ค๋ฅธ ํ”„๋ก์‹œ๋กœ ์žฌ์‹œ๋„
            return $this->fetch($url);
        }
    }
    
    private function getNextProxy() {
        $proxy = $this->proxies[$this->currentProxyIndex];
        $this->currentProxyIndex = ($this->currentProxyIndex + 1) % count($this->proxies);
        return $proxy;
    }
}

// ๋˜๋Š” ํ”„๋ก์‹œ ๋กœํ…Œ์ด์…˜ ์„œ๋น„์Šค ์‚ฌ์šฉ
class RotatingProxyScraper {
    private $client;
    
    public function __construct($proxyApiKey) {
        $this->client = new Client([
            'proxy' => "http://api:$proxyApiKey@proxy.example.com:8080",
            'timeout' => 30
        ]);
    }
    
    public function fetch($url) {
        // ํ”„๋ก์‹œ ์„œ๋น„์Šค๊ฐ€ ์ž๋™์œผ๋กœ IP๋ฅผ ๋กœํ…Œ์ด์…˜
        $response = $this->client->request('GET', $url);
        return $response->getBody()->getContents();
    }
}
?>

๋ฌธ์ œ 4: ์ธ์ฝ”๋”ฉ ๋ฌธ์ œ

ํ•œ๊ธ€์ด ๊นจ์ง€๋Š” ๋ฌธ์ œ๋Š” ์ •๋ง ํ”ํ•ด. ์ธ์ฝ”๋”ฉ์„ ์ œ๋Œ€๋กœ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ด! ๐Ÿ”ค

<?php
class EncodingHandler {
    public function fetchAndConvert($url) {
        $client = new \GuzzleHttp\Client();
        $response = $client->request('GET', $url);
        
        $html = $response->getBody()->getContents();
        
        // ์ธ์ฝ”๋”ฉ ๊ฐ์ง€
        $encoding = mb_detect_encoding($html, ['UTF-8', 'EUC-KR', 'CP949', 'ISO-8859-1'], true);
        
        echo "๊ฐ์ง€๋œ ์ธ์ฝ”๋”ฉ: $encoding\n";
        
        // UTF-8๋กœ ๋ณ€ํ™˜
        if($encoding && $encoding !== 'UTF-8') {
            $html = mb_convert_encoding($html, 'UTF-8', $encoding);
        }
        
        return $html;
    }
    
    public function fixBrokenText($text) {
        // ๊นจ์ง„ ํ•œ๊ธ€ ๋ณต๊ตฌ ์‹œ๋„
        $encodings = ['UTF-8', 'EUC-KR', 'CP949', 'ISO-8859-1'];
        
        foreach($encodings as $from) {
            foreach($encodings as $to) {
                $converted = mb_convert_encoding($text, $to, $from);
                
                // ํ•œ๊ธ€์ด ์ œ๋Œ€๋กœ ๋ณด์ด๋Š”์ง€ ํ™•์ธ
                if(preg_match('/[๊ฐ€-ํžฃ]/', $converted)) {
                    return $converted;
                }
            }
        }
        
        return $text;
    }
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
$handler = new EncodingHandler();
$html = $handler->fetchAndConvert('https://example.com');

// DOMDocument์—์„œ ์ธ์ฝ”๋”ฉ ์ฒ˜๋ฆฌ
$dom = new DOMDocument();
@$dom->loadHTML('<?xml encoding="UTF-8">' . $html);
?>

๐Ÿš€ ์Šคํฌ๋ž˜ํ•‘ ํ”„๋กœ์ ํŠธ ๋ฐฐํฌํ•˜๊ธฐ

๋กœ์ปฌ์—์„œ ์ž˜ ๋™์ž‘ํ•˜๋Š” ์Šคํฌ๋ž˜ํผ๋ฅผ ์„œ๋ฒ„์— ๋ฐฐํฌํ•ด์„œ ์ž๋™ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž! โ˜๏ธ

Cron์œผ๋กœ ์ •๊ธฐ ์‹คํ–‰

๋ฆฌ๋ˆ…์Šค ์„œ๋ฒ„์—์„œ Cron์„ ์‚ฌ์šฉํ•˜๋ฉด ์Šคํฌ๋ž˜ํผ๋ฅผ ์ •๊ธฐ์ ์œผ๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด!

# crontab -e ๋กœ ํŽธ์ง‘

# ๋งค์ผ ์˜ค์ „ 9์‹œ์— ์‹คํ–‰
0 9 * * * /usr/bin/php /path/to/scraper.php >> /var/log/scraper.log 2>&1

# ๋งค์‹œ๊ฐ„ ์‹คํ–‰
0 * * * * /usr/bin/php /path/to/scraper.php

# 5๋ถ„๋งˆ๋‹ค ์‹คํ–‰
*/5 * * * * /usr/bin/php /path/to/scraper.php

# ์›”์š”์ผ ์˜ค์ „ 10์‹œ์—๋งŒ ์‹คํ–‰
0 10 * * 1 /usr/bin/php /path/to/scraper.php

Docker๋กœ ํ™˜๊ฒฝ ๊ตฌ์„ฑ

Docker๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์–ด๋””์„œ๋“  ๋™์ผํ•œ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด!

# Dockerfile
FROM php:8.2-cli

# ํ•„์š”ํ•œ ํ™•์žฅ ์„ค์น˜
RUN apt-get update && apt-get install -y \
    libzip-dev \
    zip \
    unzip \
    git \
    curl \
    && docker-php-ext-install zip pdo pdo_mysql

# Composer ์„ค์น˜
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ •
WORKDIR /app

# ์˜์กด์„ฑ ์„ค์น˜
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader

# ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ ๋ณต์‚ฌ
COPY . .

# ์‹คํ–‰
CMD ["php", "scraper.php"]
# docker-compose.yml
version: '3.8'

services:
  scraper:
    build: .
    volumes:
      - ./data:/app/data
      - ./logs:/app/logs
    environment:
      - DB_HOST=mysql
      - DB_NAME=scraping_db
      - DB_USER=scraper
      - DB_PASS=secret
    depends_on:
      - mysql
  
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: scraping_db
      MYSQL_USER: scraper
      MYSQL_PASSWORD: secret
    volumes:
      - mysql_data:/var/lib/mysql

volumes:
  mysql_data:

์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜๋ฉด docker-compose up ๋ช…๋ น์–ด ํ•˜๋‚˜๋กœ ์ „์ฒด ํ™˜๊ฒฝ์ด ์‹คํ–‰๋ผ! ๐ŸŽ‰

๐Ÿ’ก ๋ฐฐํฌ ์‹œ ์ฒดํฌ๋ฆฌ์ŠคํŠธ
  • โœ… ์—๋Ÿฌ ๋กœ๊น… ์„ค์ •
  • โœ… ํƒ€์ž„์•„์›ƒ ์„ค์ • (์„œ๋ฒ„ ํ™˜๊ฒฝ์— ๋งž๊ฒŒ)
  • โœ… ๋ฉ”๋ชจ๋ฆฌ ์ œํ•œ ํ™•์ธ
  • โœ… ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ํ’€ ์„ค์ •
  • โœ… ์‹คํŒจ ์‹œ ์•Œ๋ฆผ ์„ค์ • (์ด๋ฉ”์ผ, Slack ๋“ฑ)
  • โœ… ๋ชจ๋‹ˆํ„ฐ๋ง ๋„๊ตฌ ์—ฐ๋™
  • โœ… ๋ฐฑ์—… ์ „๋žต ์ˆ˜๋ฆฝ

๐ŸŒˆ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์ฝ์—ˆ๋‹ค๋ฉด ๋‹น์‹ ์€ ์ด์ œ PHP ์›น ์Šคํฌ๋ž˜ํ•‘ ์ „๋ฌธ๊ฐ€์•ผ! ๐ŸŽ“ cURL์˜ ๊ฐ•๋ ฅํ•จ๋ถ€ํ„ฐ Guzzle์˜ ์šฐ์•„ํ•จ, ๊ทธ๋ฆฌ๊ณ  ์‹ค์ „์—์„œ ๋งŒ๋‚˜๋Š” ๋‹ค์–‘ํ•œ ๋ฌธ์ œ๋“ค๊นŒ์ง€ ๋ชจ๋‘ ๋‹ค๋ค˜์–ด.

์›น ์Šคํฌ๋ž˜ํ•‘์€ ์ •๋ง ์œ ์šฉํ•œ ๊ธฐ์ˆ ์ด์ง€๋งŒ, ํ•ญ์ƒ ์œค๋ฆฌ์ ์œผ๋กœ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฑธ ์žŠ์ง€ ๋งˆ! ๐Ÿ™ ๋‹ค๋ฅธ ์‚ฌ๋žŒ์˜ ์„œ๋ฒ„์— ๋ถ€๋‹ด์„ ์ฃผ์ง€ ์•Š๊ณ , ๋ฒ•์  ๋ฌธ์ œ๊ฐ€ ์—†๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ๊ฐ€๋Šฅํ•˜๋ฉด ๊ณต์‹ API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์ตœ์„ ์ด์•ผ.

ํ˜น์‹œ ์›น ์Šคํฌ๋ž˜ํ•‘ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋‹ค๊ฐ€ ๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด? ์žฌ๋Šฅ๋„ท์—์„œ ์ „๋ฌธ๊ฐ€์˜ ๋„์›€์„ ๋ฐ›์„ ์ˆ˜๋„ ์žˆ์–ด! ํ”„๋กœ๊ทธ๋ž˜๋ฐ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ ๋ถ„์„๊นŒ์ง€ ๋‹ค์–‘ํ•œ ์žฌ๋Šฅ์„ ๊ฐ€์ง„ ์‚ฌ๋žŒ๋“ค์ด ๋ชจ์—ฌ ์žˆ๊ฑฐ๋“ . ๋˜๋Š” ๋‹น์‹ ์ด ์Šคํฌ๋ž˜ํ•‘ ์ „๋ฌธ๊ฐ€๊ฐ€ ๋˜์–ด์„œ ์žฌ๋Šฅ๋„ท์—์„œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์„ ๋„์™€์ค„ ์ˆ˜๋„ ์žˆ๊ณ ! ๐Ÿ˜Š

๐ŸŽฏ ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋‚˜์•„๊ฐ€๊ธฐ 1. ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘: ๊ด€์‹ฌ ์žˆ๋Š” ์›น์‚ฌ์ดํŠธ ํ•˜๋‚˜๋ฅผ ์„ ํƒํ•ด์„œ ๊ฐ„๋‹จํ•œ ์Šคํฌ๋ž˜ํผ ๋งŒ๋“ค๊ธฐ
2. ์—๋Ÿฌ ํ•ธ๋“ค๋ง ๊ฐ•ํ™”: ๋‹ค์–‘ํ•œ ์˜ˆ์™ธ ์ƒํ™ฉ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฌ๊ณ ํ•œ ์ฝ”๋“œ ์ž‘์„ฑ
3. ์„ฑ๋Šฅ ์ตœ์ ํ™”: ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ, ์บ์‹ฑ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ตœ์ ํ™”
4. ์Šค์ผ€์ผ์—…: ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ์‹œ์Šคํ…œ ๊ตฌ์ถ•
5. ์ปค๋ฎค๋‹ˆํ‹ฐ ์ฐธ์—ฌ: GitHub์—์„œ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ ๊ธฐ์—ฌํ•˜๊ธฐ

์ž, ์ด์ œ ๋‹น์‹ ์˜ ์ฒซ ์Šคํฌ๋ž˜ํ•‘ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ? ์ฝ”๋”ฉ์€ ์ง์ ‘ ํ•ด๋ด์•ผ ์‹ค๋ ฅ์ด ๋Š˜์–ด! ๐Ÿ’ช ํ™”์ดํŒ…!

SUCCESS Happy Scraping!
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.