PHPでWebスクレイピングする方法について

採用はこちら

PHPでWebスクレイピングを行う場合は、基本的に「HTMLを取得する」「HTMLを解析する」「必要なデータを抽出する」「CSVやデータベースなどに保存する」という流れで進めます。

簡単なHTML取得であれば、PHP標準の file_get_contents() や cURL でも対応できます。

ただし、実務で安定して運用するなら、HTTP通信には Guzzle、HTML解析には Symfony DomCrawler を使う方法が扱いやすいです。

また、近年のWebサイトではJavaScriptでコンテンツを後から表示しているケースもあります。

その場合、通常のHTML取得だけでは必要な情報を取得できないことがあります。

そうしたサイトでは、裏側で使われているAPIを確認したり、必要に応じてブラウザ操作系のライブラリを使ったりする必要があります。

目次

Webスクレイピングを行う前に確認すべきこと

利用規約を確認する

スクレイピングを始める前に、まず対象サイトの利用規約を確認しましょう。

技術的にデータを取得できるページであっても、サイト側が自動取得やデータの二次利用を禁止している場合があります。

特に、商用利用、転載、データベース化、競合サービスへの利用などは制限されていることがあるため注意が必要です。

スクレイピングは「できるかどうか」だけでなく、「やってよいか」を確認することが重要です。

robots.txtを確認する

対象サイトに robots.txt がある場合は、クローラーに対してどのページへのアクセスを許可・拒否しているかを確認できます。

たとえば、以下のようなURLで確認できます。

https://example.com/robots.txt

robots.txt は、サイト運営者がクローラー向けにアクセス方針を示すためのものです。

ただし、robots.txt だけで法的な可否がすべて決まるわけではありません。

利用規約、著作権、個人情報、アクセス頻度、取得データの利用目的もあわせて確認しましょう。

サーバーに負荷をかけない

スクレイピングでは、短時間に大量のアクセスを送らないことが重要です。

通常の人間の閲覧ではあり得ない速度でアクセスすると、対象サイトに負荷をかけたり、アクセス制限を受けたりする可能性があります。

複数ページを取得する場合は、sleep() などで待機時間を入れ、必要以上にアクセスしない設計にしましょう。

個人情報や著作権に注意する

Web上に公開されている情報であっても、自由に収集・保存・再利用してよいとは限りません。

特に以下のような情報には注意が必要です。

・氏名
・住所
・電話番号
・メールアドレス
・口コミやレビュー本文
・画像
・記事本文
・会員限定情報
・ログイン後に表示される情報

個人情報や著作物を扱う場合は、取得・保存・利用の範囲を慎重に確認しましょう。

PHPでHTMLを取得する方法

file_get_contentsを使う方法

最も簡単な方法は、PHP標準の file_get_contents() を使う方法です。

<?php

$url = 'https://example.com/';
$html = file_get_contents($url);

echo $html;

この方法は非常にシンプルですが、実務ではやや不向きです。

理由は、タイムアウト、リダイレクト、User-Agent、HTTPヘッダー、エラー処理などを細かく制御しにくいからです。

また、PHPの設定で allow_url_fopen が無効になっている場合、外部URLを取得できません。

簡単な検証や学習用途であれば使えますが、本格的なスクレイピングでは cURL や Guzzle を使う方が安定します。

cURLを使う方法

PHPのcURL拡張を使うと、HTTPリクエストを細かく制御できます。

<?php

$url = 'https://example.com/';

$ch = curl_init();

curl_setopt_array($ch, [
    CURLOPT_URL => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_TIMEOUT => 10,
    CURLOPT_USERAGENT => 'MyScraper/1.0 (+https://your-domain.example/contact)',
]);

$html = curl_exec($ch);

if ($html === false) {
    echo 'cURL Error: ' . curl_error($ch);
}

$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

curl_close($ch);

echo $html;

cURLでは、リダイレクトの追跡、タイムアウト、User-Agent、HTTPステータスコードの確認などを設定できます。

特にスクレイピングでは、User-Agentを設定しておくことが大切です。

User-Agentとは、アクセス元のアプリケーション情報をサーバーに伝えるためのHTTPヘッダーです。

ブラウザを偽装するようなUser-Agentを使うよりも、以下のように連絡先が分かる独自のUser-Agentを設定する方が望ましいです。

'User-Agent' => 'MyScraper/1.0 (+https://your-domain.example/contact)'

GuzzleとDomCrawlerを使う方法

実務ではGuzzleとDomCrawlerがおすすめ

PHPでWebスクレイピングを行うなら、実務では GuzzleSymfony DomCrawler の組み合わせがおすすめです。

Guzzleは、PHPでHTTPリクエストを扱うためのライブラリです。

DomCrawlerは、取得したHTMLから特定の要素を抽出するためのライブラリです。

たとえば、以下のような取得ができます。

・titleタグを取得する
・h1やh2を取得する
・aタグからリンクURLを取得する
・imgタグから画像URLを取得する
・記事一覧からタイトルとURLを取得する
・商品一覧から商品名や価格を取得する

ライブラリをインストールする

Composerを使って、必要なライブラリをインストールします。

composer require guzzlehttp/guzzle symfony/dom-crawler symfony/css-selector

css-selector を入れておくと、CSSセレクタで要素を指定できるようになります。

基本的なコード

以下は、GuzzleでHTMLを取得し、DomCrawlerでタイトルを取得する例です。

<?php

require_once __DIR__ . '/vendor/autoload.php';

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

$client = new Client([
    'timeout' => 10,
    'http_errors' => false,
    'headers' => [
        'User-Agent' => 'MyScraper/1.0 (+https://your-domain.example/contact)',
    ],
]);

$url = 'https://example.com/';

try {
    $response = $client->get($url);
    $statusCode = $response->getStatusCode();

    if ($statusCode !== 200) {
        echo 'HTTP Status: ' . $statusCode;
        exit;
    }

    $html = (string) $response->getBody();

    $crawler = new Crawler($html);

    $title = $crawler->filter('title')->count()
        ? trim($crawler->filter('title')->text())
        : '';

    echo $title . PHP_EOL;

} catch (Throwable $e) {
    echo 'Error: ' . $e->getMessage();
}

ポイントは、http_errorsfalse にしていることです。

Guzzleは設定によって、404や500などのHTTPエラー時に例外を投げます。

ステータスコードを自分で確認したい場合は、http_errors => false を設定しておくと扱いやすくなります。

タイトルや見出しを取得する方法

titleタグを取得する

ページのタイトルを取得するには、title タグを指定します。

$title = $crawler->filter('title')->count()
    ? trim($crawler->filter('title')->text())
    : '';

filter('title') でtitleタグを探し、text() でテキストを取得します。

ただし、対象の要素が存在しない状態で text() を呼ぶとエラーになることがあります。

そのため、count() で要素が存在するか確認してから取得するのが安全です。

h1タグを取得する

h1タグを取得する場合は、以下のように書きます。

$h1 = $crawler->filter('h1')->count()
    ? trim($crawler->filter('h1')->text())
    : '';

ページタイトルとh1はSEO調査でもよく使う項目です。

複数ページを巡回して、title、h1、descriptionなどを一覧化する用途にも使えます。

h2タグを複数取得する

h2が複数ある場合は、each() を使います。

$h2List = $crawler->filter('h2')->each(function (Crawler $node) {
    return trim($node->text());
});

foreach ($h2List as $h2) {
    echo $h2 . PHP_EOL;
}

これにより、ページ内のh2見出しを配列として取得できます。

リンクを取得する方法

aタグからリンクURLを取得する

ページ内のリンク一覧を取得する場合は、a タグを指定します。

$links = $crawler->filter('a')->each(function (Crawler $node) {
    return [
        'text' => trim($node->text()),
        'href' => $node->attr('href'),
    ];
});

print_r($links);

このコードでは、リンクテキストと href 属性を取得しています。

相対URLに注意する

リンクURLには、以下のような形式があります。

https://example.com/page
/page
article.html
../article.html
?page=2
#section
//example.com/image.jpg

このうち、/pagearticle.html のようなURLは相対URLです。

保存や外部処理で使う場合は、絶対URLへ変換する必要があります。

ただし、相対URLの変換は意外と複雑です。

.././?page=2<base> タグなどを厳密に処理するには、専用のURI処理ライブラリを使った方が安全です。

簡単な用途であれば、以下のような関数でも対応できます。

function normalizeUrl(string $baseUrl, string $href): string
{
    if ($href === '') {
        return '';
    }

    if (preg_match('/^https?:\/\//', $href)) {
        return $href;
    }

    if (str_starts_with($href, '//')) {
        return 'https:' . $href;
    }

    $parts = parse_url($baseUrl);
    $scheme = $parts['scheme'] ?? 'https';
    $host = $parts['host'] ?? '';

    if ($host === '') {
        return $href;
    }

    if (str_starts_with($href, '/')) {
        return $scheme . '://' . $host . $href;
    }

    $path = $parts['path'] ?? '/';
    $dir = rtrim(dirname($path), '/');

    return $scheme . '://' . $host . $dir . '/' . ltrim($href, '/');
}

ただし、この関数はあくまで簡易版です。

複雑なURL正規化まで完全に対応しているわけではありません。

画像URLを取得する方法

imgタグからsrc属性を取得する

画像URLを取得する場合は、img タグの src 属性を取得します。

$images = $crawler->filter('img')->each(function (Crawler $node) {
    return [
        'alt' => $node->attr('alt'),
        'src' => $node->attr('src'),
    ];
});

print_r($images);

このコードでは、画像の代替テキストである alt と、画像URLである src を取得しています。

lazyload画像に注意する

サイトによっては、画像URLが src ではなく data-srcdata-original に入っていることがあります。

たとえば、以下のようなHTMLです。

<img data-src="/images/sample.jpg" alt="サンプル画像">

この場合は、src ではなく data-src を取得します。

$src = $node->attr('src') ?: $node->attr('data-src');

画像を取得する場合は、HTML上でどの属性に画像URLが入っているかを確認しましょう。

CSSセレクタで特定の要素を取得する方法

classを指定して取得する

DomCrawlerでは、CSSセレクタを使って要素を指定できます。

$title = $crawler->filter('.article-title')->count()
    ? trim($crawler->filter('.article-title')->text())
    : '';

.article-title は、class="article-title" の要素を指定するCSSセレクタです。

idを指定して取得する

idを指定する場合は、# を使います。

$content = $crawler->filter('#main-content')->count()
    ? trim($crawler->filter('#main-content')->text())
    : '';

#main-content は、id="main-content" の要素を指定しています。

一覧データを取得する

記事一覧や商品一覧のように、同じ構造の要素が複数並んでいる場合は、親要素を指定して each() で回します。

$items = $crawler->filter('.article-item')->each(function (Crawler $node) {
    $titleNode = $node->filter('.article-title');
    $linkNode = $node->filter('a');

    return [
        'title' => $titleNode->count() ? trim($titleNode->text()) : '',
        'url' => $linkNode->count() ? $linkNode->attr('href') : '',
    ];
});

print_r($items);

実際のCSSセレクタは、対象サイトのHTML構造に合わせて変更します。

Chromeの検証ツールなどで取得したい要素を確認し、タグ名、class名、id名を見ながら指定しましょう。

複数ページをスクレイピングする方法

URLを配列にして順番に取得する

複数ページを取得する場合は、URLを配列にして順番にアクセスします。

<?php

require_once __DIR__ . '/vendor/autoload.php';

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

$client = new Client([
    'timeout' => 10,
    'http_errors' => false,
    'headers' => [
        'User-Agent' => 'MyScraper/1.0 (+https://your-domain.example/contact)',
    ],
]);

$urls = [
    'https://example.com/page/1',
    'https://example.com/page/2',
    'https://example.com/page/3',
];

$results = [];

foreach ($urls as $url) {
    try {
        $response = $client->get($url);

        if ($response->getStatusCode() !== 200) {
            continue;
        }

        $html = (string) $response->getBody();
        $crawler = new Crawler($html);

        $items = $crawler->filter('.item')->each(function (Crawler $node) {
            return [
                'title' => $node->filter('.title')->count()
                    ? trim($node->filter('.title')->text())
                    : '',
                'url' => $node->filter('a')->count()
                    ? $node->filter('a')->attr('href')
                    : '',
            ];
        });

        $results = array_merge($results, $items);

        sleep(2);

    } catch (Throwable $e) {
        echo 'Error at ' . $url . ': ' . $e->getMessage() . PHP_EOL;
    }
}

print_r($results);

アクセス間隔を空ける

複数ページを巡回するときは、必ずアクセス間隔を空けましょう。

sleep(2);

上記は2秒待機する例です。

ただし、2秒空ければ必ず問題ないという意味ではありません。

対象サイトの規模、取得ページ数、サーバー応答速度、利用規約などに応じて、適切な頻度を設定する必要があります。

取得したデータを保存する方法

CSVに保存する

取得したデータをCSVに保存する場合は、fputcsv() を使います。

$results = [
    ['title' => '記事タイトル1', 'url' => 'https://example.com/article1'],
    ['title' => '記事タイトル2', 'url' => 'https://example.com/article2'],
];

$file = fopen('output.csv', 'w');

if ($file === false) {
    throw new RuntimeException('CSVファイルを開けませんでした。');
}

fputcsv($file, ['title', 'url']);

foreach ($results as $row) {
    fputcsv($file, [$row['title'], $row['url']]);
}

fclose($file);

Excelで開くことを想定する場合は、文字化け対策としてBOM付きUTF-8にすることがあります。

$file = fopen('output.csv', 'w');

if ($file === false) {
    throw new RuntimeException('CSVファイルを開けませんでした。');
}

fwrite($file, "\xEF\xBB\xBF");

fputcsv($file, ['タイトル', 'URL']);

JSONに保存する

WebアプリやAPI連携で使う場合は、JSON形式で保存する方法も便利です。

file_put_contents(
    'output.json',
    json_encode($results, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
);

JSON_UNESCAPED_UNICODE を指定すると、日本語がUnicodeエスケープされず、そのまま保存されます。

MySQLに保存する

取得したデータをMySQLに保存する場合は、PDOを使います。

$pdo = new PDO(
    'mysql:host=localhost;dbname=scraping;charset=utf8mb4',
    'user',
    'password',
    [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    ]
);

$sql = 'INSERT INTO articles (title, url) VALUES (:title, :url)';
$stmt = $pdo->prepare($sql);

foreach ($results as $row) {
    $stmt->execute([
        ':title' => $row['title'],
        ':url' => $row['url'],
    ]);
}

同じURLを何度も登録したくない場合は、DB側でURLカラムにユニーク制約を付けておくと安全です。

文字化けした場合の対処法

文字コードを確認する

日本語サイトでは、文字化けが発生することがあります。

最近のサイトはUTF-8が多いですが、古いサイトではShift_JISやEUC-JPが使われている場合もあります。

文字化けする場合は、取得したHTMLの文字コードを確認し、UTF-8に変換してからDomCrawlerに渡します。

$encoding = mb_detect_encoding($html, ['UTF-8', 'SJIS-win', 'EUC-JP', 'ISO-2022-JP'], true);

if ($encoding !== false && $encoding !== 'UTF-8') {
    $html = mb_convert_encoding($html, 'UTF-8', $encoding);
}

HTML-ENTITIESへの変換は避ける

古いスクレイピング記事では、以下のようなコードが紹介されていることがあります。

$html = mb_convert_encoding($html, 'HTML-ENTITIES', 'auto');

しかし、現在のPHP環境では、HTML-ENTITIESmb_convert_encoding() の変換先として使う方法は非推奨警告の原因になる場合があります。

そのため、文字コードを変換する場合は、基本的にUTF-8へ変換する形にしましょう。

JavaScriptで表示されるページを取得する方法

通常のHTTP取得ではJavaScriptを実行できない

GuzzleやcURLは、サーバーから返ってきたHTMLを取得するためのものです。

ブラウザのようにJavaScriptを実行して、画面上のDOMを更新するわけではありません。

そのため、以下のようなサイトでは、通常のHTML取得だけでは必要な情報を取得できないことがあります。

・ReactやVue.jsで描画されるページ
・Next.jsやNuxt.jsで構築されたページ
・スクロール後に追加表示されるページ
・ボタンを押すと商品一覧が表示されるページ
・APIから非同期でデータを取得しているページ

このような場合は、まずブラウザの開発者ツールで通信内容を確認しましょう。

NetworkタブでAPI通信を確認する

JavaScriptで表示されるデータは、裏側でAPIからJSONとして取得されている場合があります。

Chromeの場合は、以下の流れで確認できます。

1. 対象ページを開く
2. 開発者ツールを開く
3. Networkタブを開く
4. Fetch/XHRを確認する
5. 商品一覧や記事一覧を返している通信を探す
6. 必要に応じてAPIのレスポンスを確認する

HTMLを無理に解析するより、APIからJSONを取得した方が安定する場合があります。

ただし、NetworkタブでAPIが見つかったとしても、そのAPIを自由に使ってよいとは限りません。

利用規約、認証条件、アクセス制限を確認したうえで利用しましょう。

Symfony Pantherを使う

JavaScript実行後のHTMLを取得したい場合は、Symfony Pantherのようなブラウザ操作系ライブラリを使う方法があります。

インストール例は以下です。

composer require --dev symfony/panther

使用例は以下です。

<?php

require_once __DIR__ . '/vendor/autoload.php';

use Symfony\Component\Panther\Client;

$client = Client::createChromeClient();

$client->request('GET', 'https://example.com/');

$crawler = $client->waitFor('.item');

$items = $crawler->filter('.item')->each(function ($node) {
    return trim($node->text());
});

print_r($items);

waitFor() を使うことで、指定した要素が表示されるまで待機できます。

ただし、Pantherのようなブラウザ操作系は、GuzzleやcURLによるHTTP取得よりも処理が重くなります。

大量のページを高速に取得する用途には向いていません。

基本的には、まず通常のHTML取得で対応できるか確認し、次にAPI通信を確認し、それでも難しい場合にブラウザ操作系を検討するとよいでしょう。

Goutteは使ってもよいか

新規開発では積極的に選ばなくてよい

PHPのスクレイピングでは、以前はGoutteというライブラリがよく使われていました。

しかし、現在はGoutteを新規開発で積極的に選ぶ必要性は低くなっています。

現在のGoutteは非推奨扱いとなっており、Symfony BrowserKitのHttpBrowserへのプロキシのような位置づけになっています。

そのため、これから新しくPHPでスクレイピング処理を書くなら、以下のような構成を検討するとよいでしょう。

composer require guzzlehttp/guzzle symfony/dom-crawler symfony/css-selector

もしくは、Symfony系でまとめたい場合は、Symfony HttpClientやBrowserKitを検討する方法もあります。

古い記事のコードには注意する

スクレイピング関連の記事は、古い情報が残っていることがあります。

特に以下のような情報には注意が必要です。

・Goutte前提のコード
・古いPHPバージョン向けのコード
・HTML-ENTITIESを使った文字コード変換
・エラー処理がないサンプル
・アクセス頻度への配慮がないサンプル
・JavaScript描画ページを通常のHTML取得だけで取れるように説明している記事

学習時には、記事の公開日やライブラリのメンテナンス状況も確認しましょう。

スクレイピングでよくあるエラー

要素が取得できない

CSSセレクタを指定しても要素が取得できない場合、以下の原因が考えられます。

・CSSセレクタが間違っている
・対象要素がJavaScriptで後から表示されている
・PHPで取得したHTMLとブラウザで見ているHTMLが違う
・PCとスマホでHTML構造が違う
・ログインが必要なページを取得しようとしている
・アクセス元によってHTMLが変わっている

この場合は、まずPHPで取得したHTMLをファイルに保存して確認しましょう。

file_put_contents('debug.html', $html);

ブラウザの検証ツールで見ているHTMLと、PHPで取得したHTMLが同じとは限りません。

実際にPHPが取得したHTMLを確認することが重要です。

403 Forbiddenになる

403 Forbiddenが返ってくる場合は、サーバー側がアクセスを拒否している可能性があります。

主な原因は以下です。

・User-Agentが未設定
・アクセス頻度が高すぎる
・ログインが必要
・特定のIPが制限されている
・自動アクセスが禁止されている
・利用規約でスクレイピングが禁止されている

403が返ってきた場合、無理に回避しようとするのは避けましょう。

APIが用意されていないか、利用許可を取れるか、取得頻度を下げられるかを検討するべきです。

429 Too Many Requestsになる

429 Too Many Requestsは、短時間にリクエストを送りすぎている場合に返されることがあります。

対策としては、以下が考えられます。

・アクセス間隔を空ける
・リトライ回数を制限する
・キャッシュを使う
・差分取得にする
・同じURLを何度も取得しない
・並列アクセスを避ける

429が返っている状態でアクセスを続けると、さらに強い制限を受ける可能性があります。

取得頻度を見直しましょう。

文字化けする

文字化けする場合は、対象ページの文字コードと、PHP側の処理の文字コードが合っていない可能性があります。

特に古い日本語サイトでは、Shift_JISやEUC-JPが使われている場合があります。

取得したHTMLの文字コードを確認し、必要に応じてUTF-8へ変換しましょう。

JavaScriptの内容が取得できない

ブラウザでは表示されているのに、PHPで取得したHTMLには目的のデータが入っていない場合は、JavaScriptによって後から表示されている可能性があります。

この場合は、以下の順番で確認するとよいです。

1. PHPで取得したHTMLを保存して確認する
2. HTML内に目的のデータがあるか確認する
3. NetworkタブでAPI通信を確認する
4. APIが使えるならJSONを取得する
5. 必要に応じてPantherなどのブラウザ操作系を使う

実務向けのスクレイピング設計

キャッシュを使う

同じURLに何度もアクセスしないよう、HTMLやJSONをキャッシュしておくとサーバー負荷を減らせます。

$cacheFile = 'cache/' . md5($url) . '.html';
$cacheTtl = 3600;

if (file_exists($cacheFile) && time() - filemtime($cacheFile) < $cacheTtl) {
    $html = file_get_contents($cacheFile);
} else {
    $response = $client->get($url);
    $html = (string) $response->getBody();
    file_put_contents($cacheFile, $html);
}

この例では、1時間以内に取得したHTMLがあれば、再度アクセスせずにキャッシュを使います。

差分取得にする

毎回すべてのページを取得するのではなく、前回取得した内容と比較して、変更があったページだけ処理する設計にすると効率的です。

たとえば、記事一覧であれば、すでに取得済みのURLはスキップするようにします。

if (in_array($url, $fetchedUrls, true)) {
    continue;
}

このようにすれば、不要なアクセスや重複保存を減らせます。

ログを残す

スクレイピングを定期実行する場合は、ログを残しておくことが重要です。

error_log('[Scraping] fetched: ' . $url);

本格的に運用する場合は、以下のような情報をログに残すと管理しやすくなります。

・取得したURL
・取得日時
・HTTPステータスコード
・取得件数
・エラー内容
・処理時間

ログがないと、エラーが起きたときに原因を追跡しにくくなります。

取得件数が0件になったら検知する

スクレイピングは、対象サイトのHTML構造が変わると突然動かなくなることがあります。

たとえば、これまで .article-item で取得できていた記事一覧が、サイトリニューアルによって .post-card に変わると、取得件数が0件になります。

そのため、取得件数が0件になった場合は、エラーとして検知できるようにしておくと安心です。

if (count($items) === 0) {
    error_log('[Scraping] item not found: ' . $url);
}

定期実行する場合は、メールやチャットツールに通知する仕組みを入れておくと、トラブルに気づきやすくなります。

PHPスクレイピングに向いているケース

既存のPHPシステムと連携したい場合

PHPでスクレイピングするメリットは、既存のPHPシステムと連携しやすいことです。

たとえば、以下のようなケースではPHPが向いています。

・WordPressに取得データを取り込みたい
・Laravelの管理画面にデータを保存したい
・PHP製の既存システムに組み込みたい
・MySQLに取得結果を保存したい
・SEOチェックツールをPHPで作りたい

WordPressやLaravelを使っている場合、PHPでスクレイピング処理を書くと、既存のDBや管理画面と連携しやすくなります。

静的HTMLの取得に向いている

PHPのスクレイピングは、静的HTMLを取得して解析する用途に向いています。

たとえば、以下のような用途です。

・ページタイトルの取得
・見出し構造の確認
・リンク切れチェック
・記事一覧の取得
・店舗情報の取得
・自社サイトのSEOチェック

JavaScriptに強く依存していないページであれば、GuzzleとDomCrawlerで十分対応できることが多いです。

PHPスクレイピングに向いていないケース

大規模クロールには不向きな場合がある

数万ページ、数十万ページを対象にするような大規模クロールでは、PHPよりも専用のクローラー基盤や、Python、Node.jsなどを使った方が向いている場合があります。

もちろんPHPでも実装は可能ですが、並列処理、キュー管理、リトライ制御、分散処理、監視などを考えると、より適した技術を選んだ方がよいケースもあります。

JavaScriptが多いサイトには注意が必要

JavaScriptで画面を描画するサイトでは、GuzzleやcURLだけでは十分に取得できない場合があります。

その場合は、API通信を確認したり、Pantherなどのブラウザ操作系ライブラリを使ったりする必要があります。

ただし、ブラウザ操作系は処理が重いため、大量取得には向いていません。

PHPでWebスクレイピングする場合のおすすめ構成

静的HTMLならGuzzleとDomCrawler

これからPHPでスクレイピングを始めるなら、まずは以下の構成がおすすめです。

composer require guzzlehttp/guzzle symfony/dom-crawler symfony/css-selector

この構成であれば、HTTP取得とHTML解析を分けて扱えるため、コードの見通しがよくなります。

JavaScriptが必要ならAPI確認かPanther

JavaScriptで表示されるページの場合は、まずNetworkタブでAPI通信を確認しましょう。

APIからJSONを取得できる場合は、HTMLを解析するよりも安定します。

APIが利用できず、どうしてもブラウザ上の表示後HTMLを取得する必要がある場合は、Symfony Pantherなどを検討します。

旧来のGoutteは新規採用しなくてもよい

古い記事ではGoutteを使ったサンプルが多くありますが、現在は新規開発で積極的に選ぶ必要はあまりありません。

これから実装するなら、Guzzle + DomCrawler、またはSymfony系のHttpClientやBrowserKitを検討するとよいでしょう。

PHPでWebスクレイピングする際の注意点

無理なアクセス制限回避はしない

スクレイピング中に403や429が返ってきた場合、サイト側が自動アクセスを制限している可能性があります。

このような場合に、User-Agentを偽装したり、プロキシを大量に使ったり、CAPTCHAを突破しようとしたりするのは避けるべきです。

まずは、利用規約、APIの有無、アクセス頻度、取得対象の範囲を見直しましょう。

HTML構造の変更に備える

スクレイピングは、対象サイトのHTML構造に依存します。

そのため、サイト側のデザイン変更やリニューアルによって、急にデータが取得できなくなることがあります。

対策としては、以下が有効です。

・取得件数が0件なら通知する
・必須項目が空ならエラー扱いにする
・セレクタを設定ファイル化する
・定期的に取得結果を確認する
・APIがある場合はAPIを優先する

取得データの利用目的を明確にする

スクレイピングでは、データを取得すること自体よりも、そのデータをどのように使うかが重要です。

社内分析に使うのか、Webサイトで再公開するのか、商用データベースにするのかによって、注意すべき点が変わります。

特に、第三者の著作物や個人情報を含むデータを扱う場合は、慎重に判断しましょう。

まとめ

PHPでWebスクレイピングを行う場合は、HTMLを取得し、DOMを解析し、必要なデータを抽出する流れが基本です。

簡単な確認であれば file_get_contents() でも取得できますが、実務では cURL や Guzzle を使う方が安定します。

特に、PHPで本格的にスクレイピングするなら、HTTP通信に Guzzle、HTML解析に Symfony DomCrawler を使う構成が扱いやすいです。

JavaScriptで表示されるページでは、通常のHTML取得だけではデータを取得できないことがあります。

その場合は、まずブラウザのNetworkタブでAPI通信を確認し、APIが使えるならJSONを取得する方法を検討しましょう。

どうしてもブラウザ上でJavaScriptを実行する必要がある場合は、Symfony Pantherなどのブラウザ操作系ライブラリを使う方法もあります。

ただし、スクレイピングでは技術面だけでなく、利用規約、robots.txt、著作権、個人情報、アクセス頻度への配慮が欠かせません。

対象サイトに負荷をかけず、取得したデータの利用範囲を確認したうえで、適切に実装することが大切です。

以上、PHPでWebスクレイピングする方法についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次