基于Python實(shí)現(xiàn)一個(gè)文件相似度檢測(cè)工具
基于文本內(nèi)容比較的相似度檢測(cè)工具
以下是一個(gè)完整的文件相似度檢測(cè)函數(shù)實(shí)現(xiàn),支持多種相似度算法和閾值判斷:
import difflib
from pathlib import Path
import re
from collections import Counter
import math
import string
def are_files_similar(
file1: str | Path,
file2: str | Path,
similarity_threshold: float = 0.8,
method: str = "cosine"
) -> bool:
"""
比較兩個(gè)文件的相似度是否超過指定閾值
參數(shù):
file1: 第一個(gè)文件路徑
file2: 第二個(gè)文件路徑
similarity_threshold: 相似度閾值 (0-1)
method: 相似度計(jì)算方法
'cosine' - 余弦相似度 (默認(rèn))
'jaccard' - Jaccard相似度
'levenshtein' - 編輯距離相似度
'sequence' - 序列匹配相似度
返回:
bool: 相似度是否超過閾值
"""
# 讀取文件內(nèi)容
content1 = _read_file(file1)
content2 = _read_file(file2)
# 空文件處理
if not content1 and not content2:
return True # 兩個(gè)空文件視為相同
# 選擇計(jì)算方法
if method == "cosine":
similarity = _cosine_similarity(content1, content2)
elif method == "jaccard":
similarity = _jaccard_similarity(content1, content2)
elif method == "levenshtein":
similarity = _levenshtein_similarity(content1, content2)
elif method == "sequence":
similarity = _sequence_similarity(content1, content2)
else:
raise ValueError(f"未知的相似度計(jì)算方法: {method}")
return similarity >= similarity_threshold
def _read_file(file_path: str | Path) -> str:
"""讀取文件內(nèi)容并進(jìn)行預(yù)處理"""
path = Path(file_path)
if not path.exists():
raise FileNotFoundError(f"文件不存在: {path}")
# 讀取文件內(nèi)容
try:
with open(path, 'r', encoding='utf-8') as f:
content = f.read()
except UnicodeDecodeError:
# 嘗試其他編碼
with open(path, 'r', encoding='latin-1') as f:
content = f.read()
# 基礎(chǔ)預(yù)處理
content = content.lower()
content = re.sub(r'\s+', ' ', content) # 合并連續(xù)空白
return content.strip()
def _cosine_similarity(text1: str, text2: str) -> float:
"""計(jì)算余弦相似度"""
# 創(chuàng)建詞頻向量
vec1 = Counter(_tokenize(text1))
vec2 = Counter(_tokenize(text2))
# 獲取所有唯一詞
words = set(vec1.keys()) | set(vec2.keys())
# 創(chuàng)建向量
vector1 = [vec1.get(word, 0) for word in words]
vector2 = [vec2.get(word, 0) for word in words]
# 計(jì)算點(diǎn)積
dot_product = sum(v1 * v2 for v1, v2 in zip(vector1, vector2))
# 計(jì)算模長
magnitude1 = math.sqrt(sum(v**2 for v in vector1))
magnitude2 = math.sqrt(sum(v**2 for v in vector2))
# 避免除以零
if magnitude1 == 0 or magnitude2 == 0:
return 0.0
return dot_product / (magnitude1 * magnitude2)
def _jaccard_similarity(text1: str, text2: str) -> float:
"""計(jì)算Jaccard相似度"""
set1 = set(_tokenize(text1))
set2 = set(_tokenize(text2))
intersection = len(set1 & set2)
union = len(set1 | set2)
if union == 0:
return 1.0 # 兩個(gè)空集
return intersection / union
def _levenshtein_similarity(text1: str, text2: str) -> float:
"""基于編輯距離的相似度"""
# 計(jì)算編輯距離
n, m = len(text1), len(text2)
if n == 0 or m == 0:
return 0.0
# 創(chuàng)建距離矩陣
d = [[0] * (m + 1) for _ in range(n + 1)]
# 初始化邊界
for i in range(n + 1):
d[i][0] = i
for j in range(m + 1):
d[0][j] = j
# 計(jì)算距離
for i in range(1, n + 1):
for j in range(1, m + 1):
cost = 0 if text1[i - 1] == text2[j - 1] else 1
d[i][j] = min(
d[i - 1][j] + 1, # 刪除
d[i][j - 1] + 1, # 插入
d[i - 1][j - 1] + cost # 替換
)
distance = d[n][m]
max_len = max(n, m)
return 1 - (distance / max_len)
def _sequence_similarity(text1: str, text2: str) -> float:
"""基于序列匹配的相似度"""
matcher = difflib.SequenceMatcher(None, text1, text2)
return matcher.ratio()
def _tokenize(text: str) -> list[str]:
"""文本分詞處理"""
# 移除標(biāo)點(diǎn)
text = text.translate(str.maketrans('', '', string.punctuation))
# 分詞
return text.split()
一、使用示例
1. 基本使用
# 比較兩個(gè)文件是否相似度超過80%
result = are_files_similar("file1.txt", "file2.txt", 0.8)
print(f"文件相似: {result}")
2. 使用不同算法
# 使用Jaccard相似度
result = are_files_similar("doc1.md", "doc2.md", method="jaccard")
# 使用編輯距離相似度
result = are_files_similar("code1.py", "code2.py", method="levenshtein")
3. 批量比較
def find_similar_files(directory, threshold=0.9):
"""查找目錄中相似的文件對(duì)"""
from itertools import combinations
files = list(Path(directory).glob("*"))
similar_pairs = []
for file1, file2 in combinations(files, 2):
if are_files_similar(file1, file2, threshold):
similar_pairs.append((file1.name, file2.name))
return similar_pairs
二、功能特點(diǎn)
1. 多算法支持
| 算法 | 適用場(chǎng)景 | 特點(diǎn) |
|---|---|---|
| 余弦相似度 | 長文檔、自然語言 | 考慮詞頻,忽略詞序 |
| Jaccard相似度 | 短文本、關(guān)鍵詞匹配 | 基于集合運(yùn)算 |
| 編輯距離相似度 | 代碼、配置文件 | 考慮字符級(jí)差異 |
| 序列匹配相似度 | 通用文本 | Python內(nèi)置算法 |
2. 預(yù)處理流程
- 統(tǒng)一小寫
- 合并連續(xù)空白
- 移除標(biāo)點(diǎn)符號(hào)(分詞時(shí))
- 標(biāo)準(zhǔn)化編碼處理
3. 健壯性設(shè)計(jì)
- 自動(dòng)處理文件編碼問題
- 空文件特殊處理
- 除零保護(hù)
- 路徑對(duì)象兼容
三、性能優(yōu)化建議
1. 大文件處理
def _read_large_file(file_path: Path) -> str:
"""分塊讀取大文件"""
content = []
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
while True:
chunk = f.read(65536) # 64KB塊
if not chunk:
break
content.append(chunk.lower())
return ' '.join(content)
2. 內(nèi)存優(yōu)化版Jaccard
def _jaccard_similarity_large(text1: str, text2: str) -> float:
"""適用于大文件的Jaccard相似度"""
# 使用最小哈希(minHash)近似計(jì)算
from datasketch import MinHash
# 創(chuàng)建MinHash對(duì)象
m1 = MinHash(num_perm=128)
m2 = MinHash(num_perm=128)
# 添加詞元
for word in set(_tokenize(text1)):
m1.update(word.encode('utf-8'))
for word in set(_tokenize(text2)):
m2.update(word.encode('utf-8'))
return m1.jaccard(m2)
3. 并行處理
from concurrent.futures import ThreadPoolExecutor
def batch_compare(file_pairs, threshold=0.8):
"""并行批量比較文件"""
results = {}
with ThreadPoolExecutor() as executor:
futures = {
(pair[0].name, pair[1].name): executor.submit(
are_files_similar, pair[0], pair[1], threshold
)
for pair in file_pairs
}
for names, future in futures.items():
results[names] = future.result()
return results
四、應(yīng)用場(chǎng)景
1. 文檔查重
def check_plagiarism(submitted_file, source_files, threshold=0.7):
"""檢查文檔抄襲"""
for source in source_files:
if are_files_similar(submitted_file, source, threshold):
print(f"檢測(cè)到與 {source} 相似")
return True
return False
2. 代碼相似度檢測(cè)
def detect_code_clones(repo_path):
"""檢測(cè)代碼庫中的相似代碼片段"""
code_files = list(Path(repo_path).rglob("*.py"))
clones = []
for file1, file2 in combinations(code_files, 2):
if are_files_similar(file1, file2, 0.85, method="levenshtein"):
clones.append((file1, file2))
return clones
3. 文件版本比較
def find_most_similar_version(target_file, versions):
"""在多個(gè)版本中查找最相似的文件"""
similarities = []
for version_file in versions:
sim = are_files_similar(target_file, version_file, method="sequence")
similarities.append((version_file, sim))
# 按相似度排序
return sorted(similarities, key=lambda x: x[1], reverse=True)[0]
五、測(cè)試用例
import unittest
import tempfile
class TestFileSimilarity(unittest.TestCase):
def setUp(self):
# 創(chuàng)建臨時(shí)文件
self.file1 = tempfile.NamedTemporaryFile(delete=False, mode='w+')
self.file2 = tempfile.NamedTemporaryFile(delete=False, mode='w+')
self.file3 = tempfile.NamedTemporaryFile(delete=False, mode='w+')
# 寫入內(nèi)容
self.file1.write("This is a test file for similarity comparison.")
self.file2.write("This is a test file for similarity comparison.")
self.file3.write("This is a completely different file content.")
# 確保寫入磁盤
self.file1.flush()
self.file2.flush()
self.file3.flush()
def test_identical_files(self):
self.assertTrue(are_files_similar(self.file1.name, self.file2.name))
def test_different_files(self):
self.assertFalse(are_files_similar(self.file1.name, self.file3.name, 0.8))
def test_empty_files(self):
with tempfile.NamedTemporaryFile(mode='w+') as empty1, \
tempfile.NamedTemporaryFile(mode='w+') as empty2:
self.assertTrue(are_files_similar(empty1.name, empty2.name))
def test_various_methods(self):
# 相同文件應(yīng)所有方法都返回高相似度
self.assertAlmostEqual(
are_files_similar(self.file1.name, self.file2.name, 0.0, "cosine"),
1.0, delta=0.01
)
self.assertAlmostEqual(
are_files_similar(self.file1.name, self.file2.name, 0.0, "jaccard"),
1.0, delta=0.01
)
self.assertAlmostEqual(
are_files_similar(self.file1.name, self.file2.name, 0.0, "levenshtein"),
1.0, delta=0.01
)
self.assertAlmostEqual(
are_files_similar(self.file1.name, self.file2.name, 0.0, "sequence"),
1.0, delta=0.01
)
def tearDown(self):
# 清理臨時(shí)文件
Path(self.file1.name).unlink()
Path(self.file2.name).unlink()
Path(self.file3.name).unlink()
if __name__ == "__main__":
unittest.main()
總結(jié)
這個(gè)文件相似度檢測(cè)函數(shù)提供了:
- 多種算法選擇:余弦、Jaccard、編輯距離、序列匹配
- 閾值判斷:靈活設(shè)置相似度閾值
- 健壯性處理:編碼處理、空文件處理
- 易用接口:支持字符串和Path對(duì)象
使用示例:
# 基本使用
result = are_files_similar("file1.txt", "file2.txt", 0.75)
# 指定算法
result = are_files_similar("doc1.md", "doc2.md", method="jaccard")
通過這個(gè)函數(shù),您可以輕松實(shí)現(xiàn):
- 文檔查重系統(tǒng)
- 代碼克隆檢測(cè)
- 文件版本比較
- 內(nèi)容相似度分析
以上就是基于Python實(shí)現(xiàn)一個(gè)文件相似度檢測(cè)工具的詳細(xì)內(nèi)容,更多關(guān)于Python文件相似度檢測(cè)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python+tkinter使用80行代碼實(shí)現(xiàn)一個(gè)計(jì)算器實(shí)例
這篇文章主要介紹了Python+tkinter使用80行代碼實(shí)現(xiàn)一個(gè)計(jì)算器實(shí)例,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01
Python3利用scapy局域網(wǎng)實(shí)現(xiàn)自動(dòng)多線程arp掃描功能
這篇文章主要介紹了Python3利用scapy局域網(wǎng)實(shí)現(xiàn)自動(dòng)多線程arp掃描功能,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
用Python徒手?jǐn)]一個(gè)股票回測(cè)框架搭建【推薦】
回測(cè)框架就是提供這樣的一個(gè)平臺(tái)讓交易策略在歷史數(shù)據(jù)中不斷交易,最終生成最終結(jié)果,通過查看結(jié)果的策略收益,年化收益,最大回測(cè)等用以評(píng)估交易策略的可行性。這篇文章主要介紹了用Python徒手?jǐn)]一個(gè)股票回測(cè)框架,需要的朋友可以參考下2019-08-08
python requests爬取高德地圖數(shù)據(jù)的實(shí)例
今天小編就為大家分享一篇python requests爬取高德地圖數(shù)據(jù)的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-11-11
python Manager 之dict KeyError問題的解決
今天小編就為大家分享一篇python Manager 之dict KeyError問題的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-12-12
Python使用struct處理二進(jìn)制(pack和unpack用法)
這篇文章主要介紹了Python使用struct處理二進(jìn)制(pack和unpack用法),幫助大家更好的理解和使用python,感興趣的朋友可以了解下2020-11-11
Python tabulate結(jié)合loguru打印出美觀方便的日志記錄
在開發(fā)過程中經(jīng)常碰到在本地環(huán)境無法完成聯(lián)調(diào)測(cè)試的情況,必須到統(tǒng)一的聯(lián)機(jī)環(huán)境對(duì)接其他系統(tǒng)測(cè)試。往往是出現(xiàn)了BUG難以查找數(shù)據(jù)記錄及時(shí)定位到錯(cuò)誤出現(xiàn)的位置。本文將利用tabulate結(jié)合loguru實(shí)現(xiàn)打印出美觀方便的日志記錄,需要的可以參考一下2022-10-10

