PHP는 방대한 라이브러리와 도구 덕분에 웹 스크레이퍼 구축에 탁월한 언어입니다. 웹 개발을 위해 특별히 설계된 PHP는 웹 스크래핑 작업을 쉽고 안정적으로 처리합니다.
PHP를 사용한 웹사이트 스크래핑 방법에는 여러 가지가 있으며, 본 문서에서는 몇 가지 다른 방법을 살펴볼 것입니다. 구체적으로 curl, file_get_contents, Symfony BrowserKit, Symfony의 Panther 컴포넌트를 사용한 웹사이트 스크래핑 방법을 배우게 됩니다. 또한 웹 스크래핑 과정에서 마주칠 수 있는 일반적인 문제점과 이를 피하는 방법에 대해서도 알아볼 것입니다.
PHP를 이용한 웹 스크래핑
이 섹션에서는 기본적인 사이트부터 복잡하거나 동적인 사이트까지 웹 스크래핑에 흔히 사용되는 몇 가지 방법을 배웁니다.
참고:본 튜토리얼에서 다양한 방법을 다루지만, 이는 결코 완전한 목록이 아닙니다.
필수 조건
이 튜토리얼을 따라하려면 최신 버전의 PHP와 PHP 의존성 관리자인 Composer가 필요합니다. 이 글은 PHP 8.1.18과 Composer 2.5.5로 테스트되었습니다.
PHP와 Composer 설정이 완료되면php-web-scraping이라는디렉터리를 생성하고 해당 디렉터리로이동합니다:
mkdir php-web-scraping
cd $_
튜토리얼의 나머지 부분은 이 디렉터리에서 진행됩니다.
curl
curl은 C로 작성된 거의 보편적인 저수준 라이브러리이자 CLI 도구입니다. HTTP 또는 HTTPS를 사용하여 웹 페이지의 내용을 가져오는 데 사용할 수 있습니다. 거의 모든 플랫폼에서 PHP는 기본적으로 curl 지원이 활성화된 상태로 제공됩니다.
이 섹션에서는 유엔 추정치에 기반한 인구 순으로 국가를 나열하는 매우 기본적인 웹 페이지를 스크래핑합니다. 메뉴 내 링크와 링크 텍스트를 추출할 것입니다.
시작하려면curl.php라는파일을 생성한 후curl_init함수로 해당 파일에서 curl을 초기화하세요:
<?php
$ch = curl_init();
그런 다음 웹 페이지 가져오기 옵션을 설정합니다. 여기에는 URL 설정과curl_setopt 함수를 사용한 HTTP 메서드(GET, POST 등) 설정이 포함됩니다:
curl_setopt($ch, CURLOPT_URL, 'https://en.wikipedia.org/wiki/List_of_countries_by_population_(United_Nations)');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
이 코드에서는 대상 URL을 웹 페이지로 설정하고 메서드를GET으로 지정합니다.CURLOPT_RETURNTRANSFER는 curl이 HTML 응답을 반환하도록 지시합니다.
curl이 준비되면curl_exec를 사용하여 요청을 수행할 수 있습니다:
$response = curl_exec($ch);
HTML 데이터 가져오기는 웹 스크래핑의 첫 단계에 불과합니다. HTML 응답에서 데이터를 추출하려면 여러 기법을 사용해야 합니다. 가장 간단한 방법은 정규 표현식을 사용해 매우 기본적인 HTML 추출을 수행하는 것입니다. 다만 정규 표현식으로 임의의 HTML을 파싱할 수는 없지만, 매우 간단한 파싱에는 정규 표현식이 충분합니다.
예를 들어,href및title속성을 가지며<span>을 포함하는<a>태그를 추출하는 경우:
if(! empty($ch)) {
preg_match_all(
'/<a href="([^"]*)" title="([^"]*)"><span>([^<]*)</span></a>/',
$response, $matches, PREG_SET_ORDER
);
foreach($matches as $link) {
echo $link[1] . " => " . $link[3] . "n";
}
}
그런 다음curl_close함수를 사용하여 리소스를 해제합니다:
curl_close($ch);
다음과 같이 코드를 실행합니다:
php curl.php
curl은 HTTP/HTTPS를 통해 웹 페이지를 가져오는 방식을 매우 세밀하게 제어할 수 있게 해줍니다. 다양한 연결 속성을 미세 조정할 수 있을 뿐만 아니라 프록시 서버 (후술), 사용자 에이전트, 타임아웃 등 추가적인 설정도 적용할 수 있습니다.
또한 curl은 대부분의 운영체제에 기본 설치되어 있어 크로스 플랫폼 웹 스크레이퍼를 작성하는 데 탁월한 선택입니다.
그러나 보셨듯이 curl만으로는 충분하지 않으며, 데이터를 제대로 스크래핑하려면 HTML 파서가 필요합니다. 또한 curl은 웹 페이지에서 JavaScript를 실행할 수 없으므로, 동적 웹 페이지나 단일 페이지 애플리케이션(SPA)을 curl로 스크래핑할 수 없습니다.
file_get_contents
file_get_contents함수는 주로 파일 내용을 읽는 데 사용됩니다. 그러나 HTTP URL을 전달하면 웹 페이지에서 HTML 데이터를 가져올 수 있습니다. 즉,file_get_contents는이전 코드에서 curl 사용을 대체할 수 있습니다.
이 섹션에서는 이전과 동일한 페이지를 스크래핑하지만, 이번에는 더 발전된 스크래퍼를 사용하여 테이블에서 모든 국가의 이름을 추출할 수 있습니다.
file_get-contents.php라는 파일을 생성하고file_get_contents에 URL을 전달하는 것으로 시작합니다:
<?php
$html = file_get_contents('https://en.wikipedia.org/wiki/List_of_countries_by_population_(United_Nations)');
이제$html변수는 웹 페이지의 HTML 코드를 담고 있습니다.
이전 예제와 마찬가지로 HTML 데이터 가져오기는 첫 단계에 불과합니다. 흥미를 더하기 위해libxml을사용하여XPath선택자로 요소를 선택해 보겠습니다. 이를 위해 먼저DOMDocument를초기화하고 HTML을 로드해야 합니다:
$doc = new DOMDocument;
libxml_use_internal_errors(true);
$doc->loadHTML($html);
libxml_clear_errors();
여기서는 다음과 같은 순서로 국가를 선택합니다: 첫 번째tbody요소,tbody 내부의tr요소,tr요소 내부의 첫 번째td, 그리고td요소 내부의title속성을 가진a 요소.
다음 코드는DOMXpath클래스를 초기화하고evaluate를사용하여 XPath 선택기로 요소를 선택합니다:
$xpath = new DOMXpath($doc);
$countries = $xpath->evaluate('(//tbody)[1]/tr/td[1]//a[@title=true()]');
이제 남은 작업은 요소를 순회하며 텍스트를 출력하는 것입니다:
foreach($countries as $country) {
echo $country->textContent . "n";
}
다음과 같이 코드를 실행하세요:
php file_get_contents.php
보시다시피,file_get_contents는curl보다 사용이 간편하며 웹 페이지의 HTML 코드를 빠르게 가져오는 데 자주 사용됩니다. 그러나 curl과 동일한 단점이 있습니다—추가적인 HTML 파서가 필요하며, 동적 웹 페이지와 SPA(단일 페이지 애플리케이션)를 스크래핑할 수 없습니다. 또한 curl이 제공하는 정교한 제어 기능을 사용할 수 없습니다. 하지만 그 간편함 덕분에 기본적인 정적 사이트를 스크래핑하는 데는 좋은 선택입니다.
Symfony BrowserKit
Symfony BrowserKit은 실제 브라우저의 동작을 시뮬레이션하는 Symfony 프레임워크의 구성 요소입니다. 즉, 실제 브라우저에서처럼 웹 페이지와 상호작용할 수 있습니다. 예를 들어 버튼/링크 클릭, 양식 제출, 기록에서 앞뒤로 이동 등이 가능합니다.
이 섹션에서는Bright Data 블로그를 방문하여 검색창에 PHP를 입력하고 검색 양식을 제출합니다. 그런 다음 결과에서 기사 제목을 스크래핑합니다.
Symfony BrowserKit을 사용하려면 Composer로 BrowserKit 컴포넌트를 설치해야 합니다:
composer require symfony/browser-kit
인터넷을 통한 HTTP 요청을 수행하려면HttpClient컴포넌트도 설치해야 합니다:
composer require symfony/http-client
BrowserKit은 기본적으로 XPath 선택자를 사용한 요소 선택을 지원합니다. 이 예제에서는 CSS 선택자를 사용합니다. 이를 위해CssSelector컴포넌트도 설치해야 합니다:
composer require symfony/css-selector
symfony-browserkit.php라는 파일을 생성합니다. 이 파일에서HttpBrowser를 초기화합니다:
<?php
require "vendor/autoload.php";
use SymfonyComponentBrowserKitHttpBrowser;
$client = new HttpBrowser();
request함수를 사용하여GET요청을 수행합니다:
$crawler = $client->request('GET', 'https://brightdata.com/blog');
검색 버튼이 있는 양식을 선택하려면 버튼 자체를 선택한 후form함수를 사용하여 해당 버튼을 포함하는 양식을 가져와야 합니다. 버튼은filter함수에 ID를 전달하여 선택할 수 있습니다. 양식이 선택되면Httpbrowser클래스의submit함수를 사용하여 양식을 제출할 수 있습니다.
입력값 해시를 전달하면submit함수가 제출 전에 양식을 채울 수 있습니다. 다음 코드에서는q라는이름의 입력란에PHP 값을 할당했는데, 이는 검색창에PHP를직접 입력하는 것과 동일합니다:
$form = $crawler->filter('#blog_search')->form();
$crawler = $client->submit($form, ['q' => 'PHP']);
submit함수는 결과 페이지를 반환합니다. 여기서 CSS 선택자.col-md-4.mb-4 h5를 사용하여 기사 이름을 추출할 수 있습니다:
$crawler->filter(".col-md-4.mb-4 h5")->each(function ($node) {
echo $node->text() . "n";
});
다음 명령어로 코드를 실행하세요:
php symfony-browserkit.php
Symfony BrowserKit은 웹 페이지와의 상호작용 측면에서 이전 두 방법보다 한 단계 발전했지만, 자바스크립트를 실행할 수 없다는 한계가 있습니다. 즉, BrowserKit으로는 동적 웹사이트나 SPA(단일 페이지 애플리케이션)를 스크래핑할 수 없습니다.
Symfony Panther
Symfony Panther는 BrowserKit 컴포넌트를 감싸는 또 다른 Symfony 컴포넌트입니다. 그러나 Panther는 한 가지 주요 장점을 제공합니다: 브라우저를 시뮬레이션하는 대신 WebDriver 프로토콜을 사용하여 실제 브라우저를 원격으로 제어함으로써 실제 브라우저에서 코드를 실행합니다. 이는 동적 웹사이트와 SPA를 포함한 모든 웹사이트를 스크래핑할 수 있음을 의미합니다.
이 섹션에서는OpenWeather 홈페이지를 로드하고, 검색창에 도시 이름을 입력한 후 검색을 수행하여 해당 도시의 현재 날씨를 스크래핑합니다.
시작하려면 Composer로 Symfony Panther를 설치하세요:
composer require symfony/panther
또한dbrekelmans/browser-driver-installer를 설치해야 합니다. 이 패키지는 시스템에 설치된 브라우저를 자동으로 감지하고 해당 브라우저에 맞는 드라이버를 설치합니다. 시스템에 Firefox 기반 또는 Chromium 기반 브라우저 중 하나가 설치되어 있는지 확인하세요:
composer require dbrekelmans/bdi
적합한 드라이버를drivers디렉터리에 설치하려면bdi도구를 실행하세요:
vendor/bin/bdi detect drivers
symfony-panther.php라는 파일을 생성하고 Panther 클라이언트를 초기화하여 시작하세요:
<?php
require 'vendor/autoload.php';
use SymfonyComponentPantherClient;
$client = Client::createFirefoxClient();
참고:사용 중인 브라우저에 따라
createFirefoxClient대신createChromeClient또는createSeleniumClient를사용해야 할 수 있습니다.
Panther는 내부적으로 Symfony BrowserKit을 사용하므로, 다음 코드는 Symfony BrowserKit 섹션의 코드와 매우 유사합니다.
request함수를 사용하여 웹 페이지를 로드하는 것으로 시작합니다. 페이지가 로드되면 초기에는owm-loader클래스의div로덮여 있으며, 여기에는 로딩 진행률 표시줄이 표시됩니다. 페이지와 상호작용을 시작하기 전에 이div가사라질 때까지 기다려야 합니다. 이는 CSS 선택자를 받아 DOM에서 제거될 때까지 기다리는waitForStaleness함수를 사용하여 수행할 수 있습니다.
로딩 바가 제거된 후에는 쿠키 배너가 닫히도록 쿠키를 수락해야 합니다. 이때selectButton함수가 유용합니다. 이 함수는 텍스트로 버튼을 검색할 수 있습니다. 버튼을 찾은 후click함수로 클릭을 수행합니다:
$client->request('GET', 'https://openweathermap.org/');
try {
$crawler = $client->waitForStaleness(".owm-loader");
} catch (FacebookWebDriverExceptionNoSuchElementException $e) {
}
$crawler->selectButton('Allow all')->click();
참고:페이지 로딩 속도에 따라
waitForStaleness함수가 실행되기 전에 로딩 바가 사라질 수 있습니다. 이 경우 예외가 발생하므로 해당 라인을 try-catch 블록으로 감쌌습니다.
이제 검색창에'Kolkata'를입력할 차례입니다.필터함수로 검색창을 선택한 후sendKeys함수를 사용해 검색창에 입력을 제공합니다. 그런 다음검색버튼을 클릭합니다:
$crawler->filter('input[placeholder="Search city"]')->sendKeys('Kolkata');
$crawler->selectButton('Search')->click();
버튼이 선택되면 자동 완성 제안 상자가 나타납니다.waitForVisibility함수를 사용해 목록이 보일 때까지 기다린 후, 이전과 마찬가지로필터와클릭조합을 사용해 첫 번째 항목을 클릭할 수 있습니다:
$crawler = $client->waitForVisibility(".search-dropdown-menu li");
$crawler->filter(".search-dropdown-menu li")->first()->click();

마지막으로waitForElementToContain을사용해 결과가 로드될 때까지 대기하고,filter를 통해 현재 기온을 추출합니다:
$crawler = $client->waitForElementToContain(".orange-text+h2", "Kolkata");
$temp = $crawler->filter(".owm-weather-icon+span.orange-text+h2")->text();
echo $temp;
여기서는 선택자.orange-text+h2를가진 요소가'Kolkata'를 포함할 때까지 대기합니다. 이는 결과가 로드되었음을 나타냅니다.
다음과 같이 코드를 실행하세요:
php symfony-panther.php
웹 스크래핑의 과제와 가능한 해결책
PHP로 웹 스크레이퍼를 작성하는 것은 쉽지만, 실제 스크레이핑 프로젝트를 진행하는 것은 복잡할 수 있습니다. 데이터 구조(예:페이지네이션)나 웹사이트 소유자가 취한 안티봇 조치(예:허니팟 트랩)와 같은 요인으로 인해 다양한 문제가 발생할 수 있습니다.
이 섹션에서는 몇 가지 일반적인 문제점과 이를 해결하는 방법을 알아봅니다.
페이지네이션된 웹사이트 탐색
실제 웹사이트를 스크래핑할 때 거의 모든 경우에 데이터가 한 번에 로드되지 않는 상황을 마주하게 됩니다. 즉, 데이터가 페이지별로 나누어져 있다는 뜻입니다. 페이지 분할에는 두 가지 유형이 있습니다:
- 모든 페이지가 별도의 URL에 위치합니다. 페이지 번호는 쿼리 매개변수나 경로 매개변수를 통해 전달됩니다. 예를 들어,
example.com?page=3또는example.com/page/3.
- ‘다음’버튼을 선택하면 자바스크립트를 통해 새 페이지가 로드되는 방식입니다.
첫 번째 시나리오에서는 페이지를 반복 처리(루프)하여 각각의 웹 페이지로 스크래핑할 수 있습니다. 예를 들어,file_get_contents를 사용해 예시 사이트의 첫 10페이지를 스크래핑하는 코드는 다음과 같습니다:
for($page = 1; $page <= 10; $page++) {
$html = file_get_contents('https://example.com/page/{$page}');
// 스크래핑 수행
}
두 번째 시나리오에서는 Symfony Panther처럼 자바스크립트를 실행할 수 있는 솔루션을 사용해야 합니다. 이 예시에서는 다음 페이지를 불러오는 해당 버튼을 클릭해야 합니다. 새 페이지가 로드될 때까지 잠시 기다리는 것을 잊지 마세요:
for($page = 1; $page <= 10; $page++>) {
// 스크래핑 수행
// 다음 페이지 로드
$crawler->selectButton("Next")->click();
$client->waitForElementToContain(".current-page", $page+1)
}
참고:스크래핑하는 특정 웹사이트에 적합한 적절한 대기 로직으로 대체해야 합니다.
로테이팅 프록시
프록시 서버는 사용자의 컴퓨터와 대상 웹 서버 사이의 중개자 역할을 합니다. 웹 서버가 사용자의 IP 주소를 볼 수 없도록 하여 익명성을 유지합니다.
그러나 단일 프록시 서버는 차단될 수 있으므로 의존해서는 안 됩니다. 대신 여러 프록시 서버를 사용하고 순환해야 합니다. 다음 코드는 프록시 배열을 사용하고 그중 하나를 무작위로 선택하는 매우 기본적인 해결책을 제공합니다:
$proxy = array();
$proxy[] = '1.2.3.4';
$proxy[] = '5.6.7.8';
// 추가 프록시 등록
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://example.com");
curl_setopt($ch, CURLOPT_PROXY, $proxy[array_rand($proxy)]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
$result = curl_exec($ch);
curl_close($ch);
CAPTCHA 처리
많은 웹사이트에서 CAPTCHA를 사용하여 사용자가 봇이 아닌 사람인지 확인합니다. 안타깝게도 이는 웹 스크레이퍼가 걸릴 수 있음을 의미합니다.
CAPTCHA는 “당신은 인간입니까?”라고 묻는 간단한 체크박스처럼 매우 원시적일 수도 있고, Google의 reCAPTCHA나 hCaptcha처럼 더 진보된 알고리즘을 사용할 수도 있습니다. 기본적인 웹 페이지 조작(예:체크박스 선택)으로 원시적인 CAPTCHA는 통과할 수 있지만, 고급 CAPTCHA를 처리하려면 2Captcha 같은 전용 도구가 필요합니다. 2Captcha는 인간을 활용해 CAPTCHA를 해결합니다. 필요한 세부 정보를 2Captcha API에 전달하기만 하면, 해결된 CAPTCHA를 반환합니다.
2Captcha를 사용하려면 계정을 생성하고 API 키를 발급받아야 합니다.
Composer로 2Captcha 설치:
composer require 2captcha/2captcha
코드에서TwoCaptcha 인스턴스를 생성하세요:
$solver = new TwoCaptcha('YOUR_API_KEY');
그런 다음 2Captcha를 사용하여 CAPTCHA를 해결하세요:
// 일반 캡차
$result = $solver->normal('path/to/captcha.jpg');
// ReCaptcha
$result = $solver->recaptcha([
'sitekey' => '6Le-wvkSVVABCPBMRTvw0Q4Muexq1bi0DJwx_mJ-',
'url' => 'https://mysite.com/page/with/recaptcha',
'version' => 'v3',
]);
// hCaptcha
$result = $solver->hcaptcha([
'sitekey' => '10000000-ffff-ffff-ffff-000000000001',
'url' => 'https://www.site.com/page/',
]);
또는 Bright Data의 CAPTCHA 해결 도구를 확인할 수 있습니다.
허니팟 트랩 회피
허니팟 트랩은 서비스나 네트워크를 모방하여 스크레이퍼와 크롤러를 유인해 실제 대상에서 벗어나게 하는 안티봇 대책입니다. 허니팟은 봇 공격 방지에 유용하지만 웹 스크레이핑에는 문제가 될 수 있습니다. 스크레이퍼가 허니팟에 걸려들지 않도록 해야 합니다.
허니팟 트랩에 유인되는 것을 피하기 위해 취할 수 있는 다양한 방법이 있습니다. 예를 들어, 허니팟 링크는 실제 사용자가 볼 수 없도록 숨겨져 있지만 봇은 이를 감지할 수 있습니다. 트랩을 피하려면 숨겨진 링크(display: none또는visibility: noneCSS 속성이 적용된 링크)를 클릭하지 않도록 시도해 볼 수 있습니다.
또 다른 방법은 프록시를 순환시키는 것입니다. 이렇게 하면 프록시 서버 IP 주소 중 하나가 허니팟에 걸려 차단되더라도 다른 프록시를 통해 계속 연결할 수 있습니다.
결론
PHP의 우수한 라이브러리와 프레임워크 덕분에 웹 스크레이퍼 제작은 쉽습니다. 본 문서에서는 다음을 수행하는 방법을 배웠습니다:
- curl과 정규 표현식을 사용한 정적 웹사이트 스크래핑
file_get_contents와libxml을사용한 정적 웹사이트 스크래핑
- Symfony BrowserKit을 사용하여 정적 사이트 스크래핑 및 양식 제출
- Symfony Panther를 사용한 복잡한 동적 사이트 스크래핑
하지만 안타깝게도 이러한 방법으로 스크래핑하는 과정에서 PHP를 이용한 스크래핑에는 추가적인 복잡성이 따름을 알게 되었습니다. 예를 들어, 허니팟을 피하기 위해 여러 프록시를 준비하고 스크래퍼를 신중하게 구성해야 할 수 있습니다.
바로 여기에 Bright Data가 등장합니다…
Bright Data 프록시에 대해:
주거용 프록시: 195개국에서 1억 5천만 개 이상의 실제 IP를 보유한 Bright Data의 주거용 프록시를 사용하면 IP 차단 및 CAPTCHA를 피하면서 위치에 관계없이 모든 웹사이트 콘텐츠에 액세스할 수 있습니다.
ISP 프록시: 70만 개 이상의 ISP IP를 보유한 Bright Data의 ISP 프록시는 전 세계 어느 도시의 실제 고정 IP든 활용할 수 있습니다. ISP에서 할당하고 Bright Data가 독점적으로 임대하여 필요한 기간 동안 사용 가능합니다.
데이터센터 프록시: 770,000개 이상의 데이터센터 IP를 보유한 Bright Data의 데이터센터 프록시 네트워크는 전 세계의 다양한 IP 유형으로 구성되어 있으며, 공유 IP 풀 또는 개별 구매가 가능합니다.
모바일 프록시: 7백만 개 이상의 모바일 IP를 보유한 Bright Data의 첨단 모바일 IP 네트워크는 세계에서 가장 빠르고 가장 큰 실제 피어 3G/4G/5G IP 네트워크를 제공합니다.
가장 큰 프록시 네트워크에 가입하고 무료 프록시 평가판을 이용하세요.