SEO / app.py
Misnik's picture
Update app.py
789f662 verified
Raw
History Blame Contribute Delete
9.19 kB
import gradio as gr
from keybert import KeyBERT
from sentence_transformers import SentenceTransformer
from langdetect import detect
import re
# Инициализация мультиязычной модели
# (Можно заменить на другую модель, если нужна ещё более быстрая или иная точность)
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
kw_model = KeyBERT(model)
# Стоп-слова для разных языков
STOP_WORDS = {
"en": ["the", "and", "for", "of", "a", "in", "on", "at", "to", "is"],
"es": ["en", "de", "y", "el", "la", "un", "una", "con", "es", "por"],
"fr": ["le", "la", "un", "une", "et", "de", "en", "avec", "est"],
"pt": ["em", "de", "e", "o", "a", "com", "é", "por"],
"de": ["der", "die", "das", "und", "ein", "eine", "mit", "ist", "zu"],
"ar": ["في", "من", "و", "على", "مع", "إلى", "هو", "عن"],
# Добавьте другие языки, если необходимо
}
def detect_language(text: str) -> str:
"""
Определение языка текста с помощью langdetect.
Если язык определить не удалось, возвращаем 'en' (английский) по умолчанию.
"""
try:
return detect(text)
except:
return "en"
def clean_text(text: str, lang: str) -> str:
"""
Удаление из текста стоп-слов на основе определённого языка.
"""
stop_words = STOP_WORDS.get(lang, [])
words = text.split()
cleaned_text = " ".join([word for word in words if word.lower() not in stop_words])
return cleaned_text
def segment_text(text: str, max_segment_length: int = 2000) -> list:
"""
Разделение текста на сегменты по предложениям, чтобы KeyBERT
мог обрабатывать длинные тексты по частям.
Увеличили max_segment_length до 2000 (ранее было 1000),
чтобы уменьшить общее число сегментов и улучшить скорость.
"""
sentences = re.split(r'(?<=[.!?]) +', text)
segments = []
current_segment = ""
for sentence in sentences:
if len(current_segment) + len(sentence) <= max_segment_length:
current_segment += " " + sentence
else:
segments.append(current_segment.strip())
current_segment = sentence
if current_segment:
segments.append(current_segment.strip())
return segments
def postprocess_keywords(keywords: list, min_score: float = 0.5) -> list:
"""
Постобработка ключевых слов:
- убираем дубли и слишком низкий score,
- НЕ исключаем однословные фразы (ранее исключали),
- при желании можно смягчить логику удаления "вложенных" фраз.
"""
unique_keywords = []
seen = set()
# Сортируем по длине фразы (от самых длинных к коротким),
# чтобы при проверке "вложенности" не выкидывать большие фразы первыми.
for kw in sorted(keywords, key=lambda x: len(x[0]), reverse=True):
phrase, score = kw[0], kw[1]
if phrase not in seen and score >= min_score:
# Если хотим менее агрессивно исключать частичные совпадения,
# можно убрать проверку "phrase in u_kw".
if not any(phrase in u_kw["keyword"] for u_kw in unique_keywords):
unique_keywords.append({"keyword": phrase, "score": score})
seen.add(phrase)
# Раньше здесь отсеивали короткие фразы:
# filtered_keywords = [kw for kw in unique_keywords if len(kw["keyword"].split()) > 1]
# Теперь разрешаем и однословные:
filtered_keywords = unique_keywords
return filtered_keywords
def evaluate_keywords(keywords: list, text_length: int) -> dict:
"""
Оценка качества ключевых слов:
- average_score: средний рейтинг ключевых слов
- coverage: кол-во ключевых слов на 100 символов
"""
if not keywords:
return {"average_score": 0, "coverage": 0}
avg_score = sum([kw["score"] for kw in keywords]) / len(keywords)
coverage = len(keywords) / (text_length / 100.0)
return {"average_score": avg_score, "coverage": coverage}
def calculate_keyword_density(keywords, text):
"""
Рассчитать плотность ключевых слов (тошноту).
:param keywords: Список ключевых слов [{"keyword": ..., "score": ...}, ...]
:param text: Исходный (оригинальный) текст.
:return: Список, где к каждому ключу добавляются поля count и density.
"""
# Общее число слов (разделяем по пробелу)
text_length = len(text.split())
keyword_density = []
for kw in keywords:
keyword = kw["keyword"]
count = text.lower().count(keyword.lower())
# Плотность (в процентах): (количество вхождений / общее число слов) * 100
density = 0.0
if text_length > 0:
density = (count / text_length) * 100
keyword_density.append({
"keyword": keyword,
"count": count,
"density": round(density, 2), # округляем до 2 знаков
"score": kw["score"] # оставляем оригинальный score
})
return keyword_density
def extract_keywords_interface(text: str,
top_n: int = 20,
diversity: float = 0.3,
max_ngram: int = 3) -> dict:
"""
Извлечение ключевых слов через Gradio интерфейс.
По умолчанию:
- top_n=20 (было 15), чтобы извлекать больше ключей
- diversity=0.3 (было 0.4)
- max_segment_length=2000
- min_score=0.5 для постфильтра
"""
if not text or not text.strip():
return {
"keywords": [],
"metrics": {
"average_score": 0,
"coverage": 0
}
}
# 1. Определяем язык
lang = detect_language(text)
# 2. Очищаем текст от базовых стоп-слов
cleaned_text = clean_text(text, lang)
# 3. Сегментируем текст на большие блоки
segments = segment_text(cleaned_text, max_segment_length=2000)
# 4. Извлекаем ключи из каждого сегмента
all_keywords = []
for segment in segments:
keywords = kw_model.extract_keywords(
segment,
keyphrase_ngram_range=(1, max_ngram),
top_n=top_n,
diversity=diversity
)
all_keywords.extend(keywords)
# 5. Постобработка ключевых слов (снижение min_score и прочее)
processed_keywords = postprocess_keywords(all_keywords, min_score=0.5)
# 6. Метрики: средний score + coverage
metrics = evaluate_keywords(processed_keywords, len(text))
# 7. Рассчитываем «тошноту» (Keyword Density) по оригинальному тексту
keyword_density = calculate_keyword_density(processed_keywords, text)
# Формируем итоговый ответ
return {
"keywords": [
{
"keyword": kw["keyword"],
"score": kw["score"],
"count": kw["count"],
"density": kw["density"]
}
for kw in keyword_density
],
"metrics": metrics
}
# Gradio интерфейс
iface = gr.Interface(
fn=extract_keywords_interface,
inputs=[
gr.Textbox(label="Введите текст", lines=10, placeholder="Введите текст для извлечения ключевых слов"),
gr.Slider(5, 50, value=20, step=1, label="Количество ключевых слов (top_n)"),
gr.Slider(0.0, 1.0, value=0.3, step=0.1, label="Разнообразие ключевых слов (diversity)"),
gr.Slider(1, 3, value=3, step=1, label="Максимальная длина фраз (n-gram)")
],
outputs="json",
title="KeyBERT Extractor",
description="Извлечение ключевых слов мирового уровня с поддержкой мультиязычности и расчётом «тошноты»"
)
if __name__ == "__main__":
iface.launch()