-
-
Notifications
You must be signed in to change notification settings - Fork 32
/
summary.py
271 lines (185 loc) · 7.57 KB
/
summary.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
"""
This script extracts and ranks the sentences and words of an article.
IT is inspired by the tf-idf algorithm.
"""
from collections import Counter
import spacy
# The stop words files.
ES_STOPWORDS_FILE = "./assets/stopwords-es.txt"
EN_STOPWORDS_FILE = "./assets/stopwords-en.txt"
# The number of sentences we need.
NUMBER_OF_SENTENCES = 5
# The number of top words we need.
NUMBER_OF_TOP_WORDS = 5
# Multiplier for uppercase and long words.
IMPORTANT_WORDS_MULTIPLIER = 2.5
# Financial sentences often are more important than others.
FINANCIAL_SENTENCE_MULTIPLIER = 1.5
# The minimum number of characters needed for a line to be valid.
LINE_LENGTH_THRESHOLD = 150
# It is very important to add spaces on these words.
# Otherwise it will take into account partial words.
COMMON_WORDS = {
" ", " ", "\xa0", "#", ",", "|", "-", "‘", "’", ";", "(", ")", ".", ":", "¿", "?", '“', "/",
'”', '"', "'", "%", "•", "«", "»", "foto", "photo", "video", "redacción", "nueve", "diez", "cien",
"mil", "miles", "ciento", "cientos", "millones", "vale"
}
# These words increase the score of a sentence. They don't require whitespaces around them.
FINANCIAL_WORDS = ["$", "€", "£", "pesos", "dólar", "libras", "euros",
"dollar", "pound", "mdp", "mdd"]
# Don't forget to specify the correct model for your language.
NLP = spacy.load("es_core_news_sm")
def add_extra_words():
"""Adds the title and uppercase forms of all words to COMMON_WORDS.
We parse local copies of stop words downloaded from the following repositories:
https://github.com/stopwords-iso/stopwords-es
https://github.com/stopwords-iso/stopwords-en
"""
with open(ES_STOPWORDS_FILE, "r", encoding="utf-8") as temp_file:
for word in temp_file.read().splitlines():
COMMON_WORDS.add(word)
with open(EN_STOPWORDS_FILE, "r", encoding="utf-8") as temp_file:
for word in temp_file.read().splitlines():
COMMON_WORDS.add(word)
add_extra_words()
def get_summary(article):
"""Generates the top words and sentences from the article text.
Parameters
----------
article : str
The article text.
Returns
-------
dict
A dict containing the title of the article, reduction percentage, top words and the top scored sentences.
"""
# Now we prepare the article for scoring.
cleaned_article = clean_article(article)
# We start the NLP process.
doc = NLP(cleaned_article)
article_sentences = [sent for sent in doc.sents]
words_of_interest = [
token.text for token in doc if token.lower_ not in COMMON_WORDS]
# We use the Counter class to count all words ocurrences.
scored_words = Counter(words_of_interest)
for word in scored_words:
# We add bonus points to words starting in uppercase and are equal or longer than 4 characters.
if word[0].isupper() and len(word) >= 4:
scored_words[word] *= IMPORTANT_WORDS_MULTIPLIER
# If the word is a number we punish it by settings its points to 0.
if word.isdigit():
scored_words[word] = 0
top_sentences = get_top_sentences(article_sentences, scored_words)
top_sentences_length = sum([len(sentence) for sentence in top_sentences])
reduction = 100 - (top_sentences_length / len(cleaned_article)) * 100
summary_dict = {
"top_words": get_top_words(scored_words),
"top_sentences": top_sentences,
"reduction": reduction,
"article_words": " ".join(words_of_interest)
}
return summary_dict
def clean_article(article_text):
"""Cleans and reformats the article text.
Parameters
----------
article_text : str
The article string.
Returns
-------
str
The cleaned up article.
"""
# We divide the script into lines, this is to remove unnecessary whitespaces.
lines_list = list()
for line in article_text.split("\n"):
# We remove whitespaces.
stripped_line = line.strip()
# If the line is too short we ignore it.
if len(stripped_line) >= LINE_LENGTH_THRESHOLD:
lines_list.append(stripped_line)
# Now we have the article fully cleaned.
return " ".join(lines_list)
def get_top_words(scored_words):
"""Gets the top scored words from the prepared article.
Parameters
----------
scored_words : collections.Counter
A Counter containing the article words and their scores.
Returns
-------
list
An ordered list with the top words.
"""
# Once we have our words scored it's time to get top ones.
top_words = list()
for word, score in scored_words.most_common():
add_to_list = True
# We avoid duplicates by checking if the word already is in the top_words list.
if word.upper() not in [item.upper() for item in top_words]:
# Sometimes we have the same word but in plural form, we skip the word when that happens.
for item in top_words:
if word.upper() in item.upper() or item.upper() in word.upper():
add_to_list = False
if add_to_list:
top_words.append(word)
return top_words[0:NUMBER_OF_TOP_WORDS]
def get_top_sentences(article_sentences, scored_words):
"""Gets the top scored sentences from the cleaned article.
Parameters
----------
cleaned_article : str
The original article after it has been cleaned and reformatted.
scored_words : collections.Counter
A Counter containing the article words and their scores.
Returns
-------
list
An ordered list with the top sentences.
"""
# Now its time to score each sentence.
scored_sentences = list()
# We take a reference of the order of the sentences, this will be used later.
for index, sent in enumerate(article_sentences):
# In some edge cases we have duplicated sentences, we make sure that doesn't happen.
if sent.text not in [sent for score, index, sent in scored_sentences]:
scored_sentences.append(
[score_line(sent, scored_words), index, sent.text])
top_sentences = list()
counter = 0
for score, index, sentence in sorted(scored_sentences, reverse=True):
if counter >= NUMBER_OF_SENTENCES:
break
# When the article is too small the sentences may come empty.
if len(sentence) >= 3:
# We clean the sentence and its index so we can sort in chronological order.
top_sentences.append([index, sentence])
counter += 1
return [sentence for index, sentence in sorted(top_sentences)]
def score_line(line, scored_words):
"""Calculates the score of the given line using the word scores.
Parameters
----------
line : spacy.tokens.span.Span
A tokenized sentence from the article.
scored_words : collections.Counter
A Counter containing the article words and their scores.
Returns
-------
int
The total score of all the words in the sentence.
"""
# We remove the common words.
cleaned_line = [
token.text for token in line if token.lower_ not in COMMON_WORDS]
# We now sum the total number of ocurrences for all words.
temp_score = 0
for word in cleaned_line:
temp_score += scored_words[word]
# We apply a bonus score to sentences that contain financial information.
line_lowercase = line.text.lower()
for word in FINANCIAL_WORDS:
if word in line_lowercase:
temp_score *= FINANCIAL_SENTENCE_MULTIPLIER
break
return temp_score