PHP项目论文查重如何批量比对文档内容

wen PHP项目 28

本文目录导读:

PHP项目论文查重如何批量比对文档内容

  1. 第一阶段:环境准备与文档解析
  2. 第二阶段:文本预处理与比对算法(核心)
  3. 第三阶段:批量比对逻辑
  4. 第四阶段:进阶优化(瓶颈突破)
  5. 完整调用示例
  6. 最终建议

针对PHP项目的论文查重系统,要实现批量比对文档内容,核心在于解决两个问题:

  1. 文本解析:从不同格式的文档(如PDF、Word、纯文本)中提取纯文本。
  2. 相似度算法:对提取出的文本进行高效比对。

以下是一个分阶段的实现方案,侧重于独立查重(论文库本地化)或互相比对(多文档两两比对)。


第一阶段:环境准备与文档解析

PHP处理文档格式(尤其是PDF和DOCX)比较麻烦,建议使用以下库:

安装依赖(Composer)

composer require phpoffice/phpword          # 处理 .docx
composer require smalot/pdfparser           # 处理 .pdf

对于 .doc 格式,建议用户先将文档转换为 .docx 或使用服务器上的 LibreOffice 进行转换(较复杂,不推荐纯PHP)。

文本提取核心类

use PhpOffice\PhpWord\IOFactory;
use Smalot\PdfParser\Parser;
class TextExtractor {
    public static function extract($filePath) : string {
        $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
        switch ($ext) {
            case 'docx':
                $phpWord = IOFactory::load($filePath);
                $text = '';
                foreach ($phpWord->getSections() as $section) {
                    foreach ($section->getElements() as $element) {
                        if ($element instanceof \PhpOffice\PhpWord\Element\TextRun) {
                            foreach ($element->getElements() as $piece) {
                                $text .= $piece->getText();
                            }
                        } elseif ($element instanceof \PhpOffice\PhpWord\Element\Text) {
                            $text .= $element->getText();
                        }
                    }
                }
                return $text;
            case 'pdf':
                $parser = new Parser();
                $pdf = $parser->parseFile($filePath);
                return $pdf->getText();
            case 'txt':
                return file_get_contents($filePath);
            default:
                throw new \InvalidArgumentException("不支持的文件格式: $ext");
        }
    }
}

第二阶段:文本预处理与比对算法(核心)

批量比对的核心在于分词+哈希指纹,如果全文逐字比对,性能会非常差。

选择方法(推荐 SimHash + 汉明距离)

  • 原理:将长文本压缩为一个64位或128位的哈希值(指纹),通过计算两个哈希之间不同位的数量(汉明距离)来估算相似度。
  • 优势:速度快,适合海量文档。
  • 劣势:对短文本(< 几百字)不敏感。

实现 SimHash(纯PHP版)

class SimHash {
    private int $bits = 64;
    public function hash(string $text): string {
        // 1. 分词(使用简单的正则或外部分词库,如 jieba-php)
        $words = $this->tokenize($text);
        // 初始化特征向量
        $v = array_fill(0, $this->bits, 0);
        foreach ($words as $word => $weight) {
            $hash = $this->simpleHash($word);
            for ($i = 0; $i < $this->bits; $i++) {
                $bit = ($hash >> ($this->bits - 1 - $i)) & 1;
                if ($bit === 1) {
                    $v[$i] += $weight;
                } else {
                    $v[$i] -= $weight;
                }
            }
        }
        $fingerprint = 0;
        for ($i = 0; $i < $this->bits; $i++) {
            if ($v[$i] > 0) {
                $fingerprint |= (1 << ($this->bits - 1 - $i));
            }
        }
        return $fingerprint;
    }
    // 简易分词(实际生产环境请使用 jieba-php)
    private function tokenize(string $text): array {
        // 去掉标点符号,按空格/逗号分割(中文效果较差)
        // 强烈建议:这里接入 jieba-php 进行中文分词
        $text = preg_replace('/\pP/u', ' ', $text);
        $words = preg_split('/\s+/', $text, -1, PREG_SPLIT_NO_EMPTY);
        // 返回词频作为权重
        return array_count_values($words);
    }
    // 简易哈希(32位 FNV-1a)
    private function simpleHash(string $str): int {
        $hash = 0x811c9dc5;
        $prime = 0x01000193;
        $len = strlen($str);
        for ($i = 0; $i < $len; $i++) {
            $hash ^= ord($str[$i]);
            $hash *= $prime;
        }
        // 返回64位
        return ($hash & 0xFFFFFFFF) << 32 | ($hash & 0xFFFFFFFF);
    }
    // 计算汉明距离
    public function hammingDistance(int $hash1, int $hash2): int {
        $xor = $hash1 ^ $hash2;
        $dist = 0;
        while ($xor) {
            $dist++;
            $xor &= ($xor - 1);
        }
        return $dist;
    }
    // 将距离转换为百分比
    public static function similarityPercent(int $distance, int $totalBits = 64): float {
        $threshold = $totalBits * 0.15; // 15%不同算完全相似(经典阈值)
        if ($distance <= 3) return 100.0; // 极相似
        if ($distance > $threshold) return 0.0; // 不相似
        // 线性映射(0~15%差异对应100%->0%相似度)
        return max(0, 100 - ($distance / $threshold * 100));
    }
}

第三阶段:批量比对逻辑

假设我们有一个文档列表($docFiles),需要两两比对。

离线计算指纹(缓存)

$fingerprints = [];
foreach ($docFiles as $file) {
    $text = TextExtractor::extract($file);
    $simHash = new SimHash();
    $fingerprints[$file] = $simHash->hash($text);
}

生成比对矩阵

$results = [];
$simHashObj = new SimHash();
$files = array_keys($fingerprints);
for ($i = 0; $i < count($files); $i++) {
    for ($j = $i + 1; $j < count($files); $j++) {
        $dist = $simHashObj->hammingDistance($fingerprints[$files[$i]], $fingerprints[$files[$j]]);
        $similarity = SimHash::similarityPercent($dist);
        $results[] = [
            'file1' => basename($files[$i]),
            'file2' => basename($files[$j]),
            'similarity' => round($similarity, 2) . '%',
            'hamming_distance' => $dist
        ];
    }
}

输出结果

可以以表格形式输出到浏览器、CSV或JSON。


第四阶段:进阶优化(瓶颈突破)

问题 解决方案
PHP处理大文件慢 使用 SwooleReactPHP 实现异步解析;或使用 exec() 调用 Python 脚本(推荐结合 pypdf2 + jieba)。
中文分词不准 tokenize() 中集成 jieba-phpfukuball/jieba-php)。
指纹比较O(n²) 对指纹建立 倒排索引:将64位指纹分成4个16位块,每个块作为key,相同块hash的文档大概率相似,只在这些块中比较(分桶优化)。
需要比对数据库 将论文库的SimHash存入Redis(Sorted Set),利用 SIMHASH 命令快速查找近似文档(Redis 7+支持)。

完整调用示例

$files = [
    'doc1.docx',
    'thesis.pdf',
    'paper.txt'
];
$results = batchCompare($files);
function batchCompare(array $files): array {
    // 1. 提取并计算指纹
    $fingerprints = [];
    foreach ($files as $f) {
        try {
            $text = TextExtractor::extract($f);
            $simHash = new SimHash();
            $fingerprints[$f] = $simHash->hash($text);
        } catch (Exception $e) {
            echo "处理 {$f} 失败: " . $e->getMessage() . "\n";
        }
    }
    // 2. 两两比对
    $results = [];
    $list = array_keys($fingerprints);
    $obj = new SimHash();
    for ($i = 0; $i < count($list); $i++) {
        for ($j = $i + 1; $j < count($list); $j++) {
            $d = $obj->hammingDistance($fingerprints[$list[$i]], $fingerprints[$list[$j]]);
            $s = SimHash::similarityPercent($d);
            $results[] = [
                basename($list[$i]) . ' vs ' . basename($list[$j]) => $s . '%'
            ];
        }
    }
    return $results;
}
print_r($results);

最终建议

  • 不要依赖纯PHP处理50页以上的PDF:建议使用 pdftotext(Linux)或 Python 库作为后端服务,PHP通过HTTP调用。
  • 对于1000+文档的批量比对:使用 MinHash(布隆过滤器的变种) + LSH(局部敏感哈希),将复杂度从O(n²)降到近似线性。
  • 数据库方案:将指纹存入 MySQL(BIGINT UNSIGNED 列),SELECT * FROM papers WHERE hash BIT_XOR ? HAVING BIT_COUNT(...) < 10(但性能不佳),推荐使用 Elasticsearch 的 more_like_this 查询。

抱歉,评论功能暂时关闭!