Files
ArticleReplaceBatch/plagiarismdetecto.py

301 lines
14 KiB
Python
Raw Permalink Normal View History

2026-03-25 15:17:18 +08:00
"""
最终版二创文章相似度检测器
精确调整的算法
"""
import re
import jieba
from typing import Dict, List
import difflib
class FinalPlagiarismDetector:
"""最终版相似度检测器"""
def __init__(self):
"""初始化检测器"""
self.stopwords = {
'', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '没有', '', '',
'自己', '', '', '', '', '', '', '如果', '但是', '因为', '所以'
}
def analyze_similarity(self, original_text: str, modified_text: str) -> Dict:
"""
分析两篇文章的相似度
"""
# 预处理
clean_original = self._preprocess(original_text)
clean_modified = self._preprocess(modified_text)
# 基础文本相似度
text_similarity = self._calculate_text_similarity(clean_original, clean_modified)
# 关键词相似度
keyword_similarity = self._calculate_keyword_similarity(clean_original, clean_modified)
# 人物相似度
character_similarity = self._calculate_character_similarity(clean_original, clean_modified)
# 情节相似度
plot_similarity = self._calculate_plot_similarity(clean_original, clean_modified)
# 综合相似度(更严格的计算)
dimensions = {
'text': text_similarity,
'keyword': keyword_similarity,
'character': character_similarity,
'plot': plot_similarity
}
# 权重调整
weights = {'text': 0.4, 'keyword': 0.3, 'character': 0.2, 'plot': 0.1}
overall = sum(dimensions[dim] * weights[dim] for dim in dimensions)
# 应用严格阈值
overall = self._apply_strict_threshold(overall, dimensions)
# 剽窃等级
level = self._determine_plagiarism_level(overall)
return {
'overall_similarity': overall,
'plagiarism_level': level,
'dimensions': dimensions,
'detailed_analysis': self._generate_analysis(dimensions)
}
def _preprocess(self, text: str) -> str:
"""预处理文本"""
text = re.sub(r'\s+', ' ', text)
return text.strip()
def _calculate_text_similarity(self, text1: str, text2: str) -> float:
"""计算文本基础相似度"""
if not text1 or not text2:
return 0.0
# 使用SequenceMatcher计算整体相似度
matcher = difflib.SequenceMatcher(None, text1, text2)
base_similarity = matcher.ratio()
# 提取公共子串
common_substrings = self._find_common_substrings(text1, text2)
substring_bonus = len(common_substrings) * 0.02 # 每个公共子串加2%
return min(1.0, base_similarity + substring_bonus)
def _find_common_substrings(self, text1: str, text2: str) -> List[str]:
"""查找公共子串"""
substrings = []
# 查找长度大于5的公共子串
for i in range(len(text1) - 5):
for j in range(i + 5, len(text1)):
substring = text1[i:j]
if substring in text2 and len(substring) > 5:
substrings.append(substring)
return substrings
def _calculate_keyword_similarity(self, text1: str, text2: str) -> float:
"""计算关键词相似度"""
keywords1 = self._extract_keywords(text1)
keywords2 = self._extract_keywords(text2)
if not keywords1 and not keywords2:
return 1.0
if not keywords1 or not keywords2:
return 0.0
# 计算交集和并集
intersection = set(keywords1) & set(keywords2)
union = set(keywords1) | set(keywords2)
# Jaccard系数
jaccard = len(intersection) / len(union) if union else 0.0
# 加权计算(考虑出现频率)
weight1 = len(intersection) / len(keywords1) if keywords1 else 0
weight2 = len(intersection) / len(keywords2) if keywords2 else 0
return (jaccard * 0.6 + (weight1 + weight2) * 0.2)
def _extract_keywords(self, text: str) -> List[str]:
"""提取关键词"""
words = jieba.cut(text)
keywords = []
for w in words:
if len(w) > 1 and w not in self.stopwords and not w.isdigit():
keywords.append(w)
return keywords
def _calculate_character_similarity(self, text1: str, text2: str) -> float:
"""计算人物相似度"""
chars1 = self._find_names(text1)
chars2 = self._find_names(text2)
if not chars1 and not chars2:
return 1.0
if not chars1 or not chars2:
return 0.0
# 计算人物匹配度
common_chars = set(chars1) & set(chars2)
total_chars = set(chars1) | set(chars2)
char_similarity = len(common_chars) / len(total_chars) if total_chars else 0.0
# 如果人物完全不同相似度为0
if not common_chars:
return 0.0
return char_similarity
def _find_names(self, text: str) -> List[str]:
"""提取人名"""
# 简化的中文姓名识别
pattern = r'[王李张刘陈杨赵黄周吴徐孙胡朱高林何郭马罗梁宋郑谢韩唐冯于董萧程曹袁邓许傅沈曾彭吕苏卢蒋蔡贾丁魏薛叶阎余潘杜戴夏锺汪田任姜范方石姚谭廖邹熊金陆郝孔白崔康毛邱秦江史顾侯邵孟龙万段漕钱汤尹黎易常武乔贺赖龚文][一-龥]{1,3}'
names = re.findall(pattern, text)
return list(set(names))
def _calculate_plot_similarity(self, text1: str, text2: str) -> float:
"""计算情节相似度"""
events1 = self._extract_events(text1)
events2 = self._extract_events(text2)
if not events1 and not events2:
return 1.0
if not events1 or not events2:
return 0.0
# 计算事件匹配度
common_events = 0
for event1 in events1:
for event2 in events2:
# 如果事件描述相似
if self._events_similar(event1, event2):
common_events += 1
max_events = max(len(events1), len(events2))
return common_events / max_events if max_events > 0 else 0.0
def _extract_events(self, text: str) -> List[str]:
"""提取关键事件"""
# 提取包含关键动作的句子
action_patterns = [
r'([^。!?.]*发现[^。!?.]*[。!?.])',
r'([^。!?.]*决定[^。!?.]*[。!?.])',
r'([^。!?.]*帮助[^。!?.]*[。!?.])',
r'([^。!?.]*发生[^。!?.]*[。!?.])'
]
events = []
for pattern in action_patterns:
matches = re.findall(pattern, text)
events.extend(matches)
return events
def _events_similar(self, event1: str, event2: str) -> bool:
"""判断两个事件是否相似"""
# 简单的相似度判断
words1 = set(jieba.cut(event1))
words2 = set(jieba.cut(event2))
common_words = words1 & words2
total_words = words1 | words2
if not total_words:
return False
similarity = len(common_words) / len(total_words)
return similarity > 0.6 # 相似度超过60%认为相似
def _apply_strict_threshold(self, similarity: float, dimensions: Dict) -> float:
"""应用严格阈值"""
# 如果文本本身相似度很低,大幅降低总分
if dimensions['text'] < 0.3:
similarity *= 0.3
elif dimensions['text'] < 0.5:
similarity *= 0.6
# 如果关键词相似度很低,进一步降低
if dimensions['keyword'] < 0.2:
similarity *= 0.4
# 如果人物完全不同且情节也不同,大幅降低
if dimensions['character'] == 0.0 and dimensions['plot'] < 0.2:
similarity *= 0.2
return similarity
def _determine_plagiarism_level(self, similarity: float) -> str:
"""判断剽窃等级"""
if similarity < 0.05: # 5%
return "原创"
elif similarity < 0.30: # 30%
return "抄袭(轻微)"
else:
return "剽窃(严重)"
def _generate_analysis(self, dimensions: Dict) -> str:
"""生成详细分析"""
analysis = "各维度相似度分析:\n"
dim_names = {
'text': '文本相似度',
'keyword': '关键词相似度',
'character': '人物相似度',
'plot': '情节相似度'
}
for dim, score in dimensions.items():
analysis += f" - {dim_names[dim]}: {score:.2%}\n"
return analysis
# 便捷函数
def detect_plagiarism(original: str, modified: str) -> Dict:
"""检测二创文章与原文的相似度"""
detector = FinalPlagiarismDetector()
return detector.analyze_similarity(original, modified)
if __name__ == "__main__":
# 测试
detector = FinalPlagiarismDetector()
original = """
12 22 日晚天王嫂 方媛在个人社交平台晒出视频分享近况从方媛分享来看生完三胎女儿的她在月子中心坐满 42 天月子后已经回到郭富城在香港的半山别墅
回家后的方媛不用操心带娃问题毕竟娘家父母都在身边帮忙照顾孩子还有阿姨那作为三个孩子妈妈的方媛还是很清闲的
别墅里已经布置好圣诞树满满节日气氛视频中身着瑜伽服出镜的方媛身材曲线明显完全看不出是刚生完孩子的人
对于自己现在的状态方媛本人还是比较满意的她不忘分享自己产后保持好状态的妙招方媛透露别看她现在肚子平坦腰身纤细但恢复到这种状态也是需要一定时间而为了让肚子尽快收回去生完三胎后的方媛有绑收腹带她透露不仅白天绑晚上偶尔也会绑还有就是搭配好饮食以及运动了
方媛还晒出了在家带娃画面只见方媛半蹲在婴儿车旁一脸宠溺看向躺在车上的小女儿在家带娃的方媛并没有化妆素颜状态下的她气色很不错而且皮肤白净好水灵好美的妈咪
除了肚子外还有宝妈询问方媛生完孩子后胯部如何回到原来的状态对此方媛有回应她直言胯部不如肚子好恢复她前两胎都是半年至一年才回去的
再看方媛生完三胎后的近照身着牛仔裤的她胯部很明显还没收回去对比之前状态要宽很多
当然37 岁生下三胎已经是高龄产妇的方媛产后恢复的可以说是相当不错了她现在体重看着都没 100 作为三个孩子的妈妈这体重属实令人羡慕
产后恢复的超好的方媛她已经复工就在今日方媛还晒出和温碧霞身着古装一起走秀合照两个人都好仙好美网友看后纷纷直呼简直就是一场视觉盛宴啊
和郭富城育有三女的方媛三个女儿虽说都没露过全脸但从女儿们露出的五官来看有遗传到爸爸妈妈的高颜值有三个漂亮女儿的郭富城方媛也是很令人羡慕了
"""
modified = """
俗话说得好想要人前风光就得人后吃苦
12 22 号晚上方媛发了个视频我看完直接愣住了这是三个娃的妈生完三胎才 42 月子刚坐完人就回到半山别墅了视频里圣诞树挂满了灯节日味道很浓但我眼睛一直盯着她看
说真的这恢复速度太吓人了她穿着紧身瑜伽服站在那儿肚子平平的一点肉都没有腰细得不像话哪像刚生完孩子的人这身材管理我服了
不过方媛挺实在没装她自己说了这身材不是白来的为了收肚子她下了狠劲白天绑收腹带晚上有时候也不敢摘还得管住嘴迈开腿听着就觉得难受
说实话她这日子确实让人羡慕回家不用操心带娃娘家爸妈都在还有阿姨帮忙她就负责美美地逗逗孩子这才是阔太太的生活视频里她蹲在婴儿车旁边看着小闺女眼神温柔得很
那天她没化妆素颜出镜脸干干净净的皮肤白得发亮气色特别好一点都不累虽然已经 37 岁了但状态比二十多岁的姑娘还好这月子坐得真到位
但仔细看还是能看出点东西肚子是平了可胯比以前宽了不少她穿牛仔裤的时候线条很明显下半身有点妈妈的样子了这就是生孩子的代价骨盆开了一时半会回不去
方媛也没躲着大方承认了她说肚子好收胯是老大难没个半年一年回不去前两胎也是这么熬过来的这话听着心酸当妈的都不容易就算有钱身体的苦也得自己扛
现在的方媛估计不到一百斤看着她细胳膊细腿我都替她担心太瘦了不过人家是真拼刚出月子就开始干活了这就叫比你有钱的人比你还努力咱还有啥理由躺着
就在今天她还去走了个秀跟温碧霞一起俩人都穿古装那场面太好看了方媛一出来又仙又大气网友都说是视觉盛宴评价很高
三个闺女都没露全脸但基因骗不了人看那露出来的五官个个都漂亮像爹也像妈郭富城家以后就是四个大美女想想那画面老郭这辈子值了
不得不说方媛挺有韧劲的从小网红到天王嫂靠的不光是脸三年生俩现在又是三胎肚子没怎么歇过关键人家还能保持状态该美美该工作工作这才是狠人
咱们普通人看个热闹就行没必要跟人家比人家有保姆有营养师咱比不了但她那股劲儿对自己的严格倒是值得学学生活是自己的好坏都得自己扛
女人这辈子不管嫁给谁爱自己才是真的别拿孩子当借口放弃对美的追求你看方媛绑着带子也要美这就是态度不管多大岁数不管生几个娃精气神不能丢日子过得好不好全写在脸上愿每个妈妈都能活成自己想要的样子
"""
result = detector.analyze_similarity(original, modified)
print(f"综合相似度: {result['overall_similarity']:.2%}")
print(f"剽窃等级: {result['plagiarism_level']}")
print(result['detailed_analysis'])