JavaScriptを有効にしてください

【PHP】Webクローラーを作ってみた

 ·  ☕ 3 分で読めます

【PHP】Webクローラーを作ってみた

PHP で自作のWebクローラーを作ってみた。

Webクローラー説明

WebクローラーはWebを巡回するものですが今回作ったのは以下のようなものです。

  • 対象のURLからのリンクを辿る
  • 特定のURL配下のものだけしか辿らない(無限にリンク先を辿ってしまうため
  • タイトルとURLを保存する

本来のWebクローラーとは少々違いますが色々使えそうなのでブログにメモしておこうとこの記事を書いてます。

警告
Webクローラーを扱う際には接続先に負荷がかかる可能性、短期間の大量アクセスでのアクセス禁止にされたりする可能性があります。
なので使用する際はご注意ください。

本来もっと高速化しようかと考えていましたが色々な危険のため、やめました。
必要があれば sleep などを入れて調整してください。

サンプルコード

サンプルコードはこちらです。
今回はパッケージなどを入れないできるだけシンプルな形で作りました。

一応クラス化してあります。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
<?php
/**
 * Webクローラー
 */
class WebCrawl
{
    /**
     * ベースURL
     *
     * @var string
     */
    private string $baseUrl;

    /**
     * 取得URLリスト
     *
     * @var array
     */
    public array $internalLinks = [];

    /**
     * コンストラクタ
     *
     * @param string $baseUrl ベースURL
     */
    public function __construct(string $baseUrl)
    {
        $this->baseUrl = $baseUrl;
    }

    /**
     * クロール実行
     *
     * @param string $url クロール対象URL
     * @return void
     */
    public function crawl(string $url): void
    {
        $url = $this->removeFragment($url);

        if (isset($this->internalLinks[$url])) {
            return;
        }

        if (strpos($url, $this->baseUrl) !== 0) {
            return;
        }

        $html = @file_get_contents($url);

        if ($html === false) {
            return;
        }

        $this->internalLinks[$url] = '';

        $dom = new DOMDocument();
        @$dom->loadHTML($html);
        $xpath = new DOMXPath($dom);

        $title = $xpath->query('//title');
        $title = $title->item(0) ? $title->item(0)->textContent : '';
        $this->internalLinks[$url] = $title;

        $links = $xpath->query('//a/@href');
        foreach ($links as $link) {
            $href = $link->nodeValue;

            if ($this->isRelativeUrl($href)) {
                $href = $this->makeAbsoluteUrl($url, $href);
            }

            $this->crawl($href, $this->baseUrl);
        }
    }

    /**
     * URL検証
     *
     * @param string $url URL
     * @return boolean
     */
    private function isRelativeUrl(string $url): bool
    {
        return !preg_match('/^https?:\/\//', $url);
    }

    /**
     * 絶対パスを取得
     *
     * @param string $currentUrl 現在のURL
     * @param string $relativeUrl  URLパス
     * @return string 絶対URL
     */
    private function makeAbsoluteUrl(string $currentUrl, string $relativeUrl): string
    {
        if (substr($relativeUrl, 0, 1) === '/') {
            return $this->baseUrl . $relativeUrl;
        }
        return rtrim($currentUrl, '/') . '/' . ltrim($relativeUrl, '/');
    }

    /**
     * アンカーリンクを削除
     *
     * @param string $url 対象のURL
     * @return string アンカーリンク削除済みURL
     */
    private function removeFragment($url): string
    {
        $hashPosition = strpos($url, '#');
        if ($hashPosition !== false) {
            $url = substr($url, 0, $hashPosition);
        }
        return $url;
    }
}

実行部分はこちら。
色々と実行時間やメモリの計測なども行っています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 実行時間 無期限
set_time_limit(0);
$startUrl = 'https://c-a-p-engineer.github.io';
$baseUrl = 'https://c-a-p-engineer.github.io';

$internalLinks = [];

$startTime = microtime(true);

// Webクロール実行
$crawl = new WebCrawl($baseUrl);
$crawl->crawl($startUrl);
$internalLinks = $crawl->internalLinks;

$endTime = microtime(true);

// 各種情報を出力
ksort($internalLinks);

echo "Internal Links:\n";
foreach ($internalLinks as $url => $title) {
    echo $url . " - " . $title . "\n";
}
echo "Links Count:" . count($internalLinks) . "\n";
echo "Execution Time: " . ($endTime - $startTime) . " seconds\n";
echo "Memory Usage: " . (memory_get_peak_usage(true) / 1024 / 1024) . " MB\n";

自分のブログを対象にしてみましたが結果は以下のようになりました。
1秒間に3~4ページを処理している計算になります。

Links Count:2783
Execution Time: 794.42966103554 seconds
Memory Usage: 82 MB
共有

こぴぺたん
著者
こぴぺたん
Copy & Paste Engineer