phpでの巨大ファイルの扱いについて

wikiデータを利用したサービスを開発しているですが、その際に直面した問題と解決方法です。

【問題】

wikiデータのファイルが3GBを超えるサイズで、ここから指定したキーワードのみを高速でデータ抽出行い、DBに保存したい。

【解決方法】

まず、wikiデータ(wiki.dat)からtitleタグの付いたデータを

% grep -b ‘<title>’ wiki.dat > title.dat

としてタイトルのみのindexファイル(title.dat)を作成する。

— title.datの中身 —

…..

3065193478: <title>ヴァルメト</title>
3065196091: <title>鮨勘</title>
3065196442: <title>クロックマダム</title>
3065197671: <title>八木麻紗子</title>
3065200143: <title>イチロー</title>

…..

“:”をセパレータとした1フィールド目がfseekでのオフセット値になる。

キーワードリスト(keyword.txt)からデータを読み取りwiki説明データを抽出するプログラムサンプルは下記の通り。

—- サンプルソース —

#!/usr/local/bin/php -q
<?php
ini_set("display_errors", 1);
{
        $lines = file("keyword.txt");
        foreach($lines as $line){
                $keyword = trim($line);
                        $offset = checkExist($keyword);
                if (!$offset){
                        continue;               // not exists.
                }
                print getWiki($keyword, $offset);
        }
}

function checkExist($keyword)
{
        if (!$keyword){
                return;
        }
        $cmd = "grep '<title>$keyword</title>' title2.dat";
        $line = `$cmd`;
        if (!$line){
                return 0;
        }
        list($n, $dat) = explode(":", $line);
        return $n;
}

function getWiki($keyword, $offset)
{
        if (!$keyword){
                return;
        }
        $fp = fopen("wiki.dat", "r");
        $MAX = 2000000000;      // ファイルサイズが2G以上だと一度にseekできない。
        if ($offset > $MAX){
            fseek($fp, $MAX);
            $offset = $offset - $MAX;
        }
        fseek($fp, $offset, SEEK_CUR);
        $flg = 0;
        $text = "";
        while (!feof($fp)) {
                $buf = fgets($fp, 512);
                if (strpos($buf, "<title>$keyword</title>")){
                        $flg = 1;
                }
                if ($flg){
                        if (strpos($buf, "<text xml:")){
                                $flg = 2;
                        }
                        if ($flg == 2){
                                $text .= $buf;
                        }
                        if (strpos($buf, "</text>")){
                                $text = strip_tags($text);
                                fclose($fp);
                                return $text;
                        }
                }
        }
        fclose($fp);
}
?>