Обработка текста на Python / NLP with Python

Примерно то, что требуется в задании, продемонстрировано в статье на хабре по ссылке.

Продемонстрируем некоторые отдельные вопросы, которые возможно возникнут при выполнении задания.

Установка зависимостей в колабе

Необходимо использовать магические ячейки и установить некоторые библиотеки, которых в базовом наборе библиотек колаба, нет.

In [ ]:
%%sh

pip install yargy # для обработки текста 
pip install natasha # для обработки текста 
pip install wordcloud # для создания изображения с облаком тегов

Парсинг и обработка информации со страниц сайта

Можно использовать механизм, используемый в прошлой ЛР (BeautifulSoup) или что-то другое.

Предлагается использовать регулярные выражения, поскольку все новости на сайте ведут на подраздел /news/*

Извлекаем ссылки на конкретные новости и открываем каждую страницу аналогичным образом получаем текст новости полностью.

Не забыть выполнить предварительную очистку и токенизацию текста по словам. После очистки текста требуется положить его в отдельный текстовый файл или сохранить в строковую переменную. В конце — склеить весь текст в единый текстовый файл.

Затем, используя библиотеку natasha извлекаем имена упоминаемых в тексте людей и добавляем их в json-файл. Далее, для имён требуется сериализовать данные из json-файла, убрав имена и оставив только фамилии. После этого сделать из фамилий текст и визуализировать информацию о статистики встречаемости в виде облака тегов. Аналогично, сделать облако тегов для ключевых понятий.

In [ ]:
import re
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen('https://www.herzen.spb.ru/main/news/')
bs = BeautifulSoup(html, "html.parser")
# news3.jpg
div = bs.find('div', {'class':'news_container'})

print(div)
# news_p_array = image.parent.parent.children
<div class="news_container" style="padding:0 0 20 0"><b>11 октября 2022</b><br/><span class="news_header"><p><strong>Проект «Александрова дорога»: <a href="https://www.youtube.com/watch?v=WXz56pOSdyo">1 СЕРИЯ. ПЕРЕСЛАВЛЬ-ЗАЛЕССКИЙ. НАЧАЛО ПУТИ</a></strong></p>
</span></div>

Построение изображения с облаком тегов на Python

Для примера визуализируем наиболее часто встречаемые слова из текста А. С. Пушкина "Метель".

In [ ]:
%%capture
!wget https://raw.githubusercontent.com/Metafiz/nlp-course-20/master/pushkin-metel.txt
In [ ]:
%matplotlib inline
In [ ]:
from wordcloud import WordCloud
import matplotlib.pyplot as plt

f = open('pushkin-metel.txt', "r", encoding="utf-8")
text = f.read()
# wordcloud = WordCloud().generate(text)

text2 = """
Lorem ipsum dolor sit amet, 
Lorem ipsum dolor 
Lorem ipsum 
Lorem 

    
    """
wordcloud = WordCloud().generate(text)
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")
Out[ ]:
(-0.5, 399.5, 199.5, -0.5)

Получилось плохо, потому что мы не предобработали текст (в частности: не убрали стоп-слова, не провели лемматизацию текста, не сегментировали текст).

Сделаем это и визуализируем с помощью облака слов содержимое текста еще раз.

In [ ]:
%%sh

pip install natasha # для обработки текста
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Requirement already satisfied: natasha in /usr/local/lib/python3.7/dist-packages (1.4.0)
Requirement already satisfied: yargy>=0.14.0 in /usr/local/lib/python3.7/dist-packages (from natasha) (0.15.0)
Requirement already satisfied: navec>=0.9.0 in /usr/local/lib/python3.7/dist-packages (from natasha) (0.10.0)
Requirement already satisfied: slovnet>=0.3.0 in /usr/local/lib/python3.7/dist-packages (from natasha) (0.5.0)
Requirement already satisfied: pymorphy2 in /usr/local/lib/python3.7/dist-packages (from natasha) (0.9.1)
Requirement already satisfied: razdel>=0.5.0 in /usr/local/lib/python3.7/dist-packages (from natasha) (0.5.0)
Requirement already satisfied: ipymarkup>=0.8.0 in /usr/local/lib/python3.7/dist-packages (from natasha) (0.9.0)
Requirement already satisfied: intervaltree>=3 in /usr/local/lib/python3.7/dist-packages (from ipymarkup>=0.8.0->natasha) (3.1.0)
Requirement already satisfied: sortedcontainers<3.0,>=2.0 in /usr/local/lib/python3.7/dist-packages (from intervaltree>=3->ipymarkup>=0.8.0->natasha) (2.4.0)
Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from navec>=0.9.0->natasha) (1.21.6)
Requirement already satisfied: pymorphy2-dicts-ru<3.0,>=2.4 in /usr/local/lib/python3.7/dist-packages (from pymorphy2->natasha) (2.4.417127.4579844)
Requirement already satisfied: dawg-python>=0.7.1 in /usr/local/lib/python3.7/dist-packages (from pymorphy2->natasha) (0.7.2)
Requirement already satisfied: docopt>=0.6 in /usr/local/lib/python3.7/dist-packages (from pymorphy2->natasha) (0.6.2)
In [ ]:
%%capture
!wget https://raw.githubusercontent.com/Metafiz/nlp-course-20/master/pushkin-metel.txt
In [ ]:
from natasha import (
    Segmenter,
    MorphVocab,
    
    NewsEmbedding,
    NewsMorphTagger,
    NewsSyntaxParser,
    NewsNERTagger,
    
    PER,
    NamesExtractor,

    Doc
)

segmenter = Segmenter()
morph_vocab = MorphVocab()

emb = NewsEmbedding()
morph_tagger = NewsMorphTagger(emb)
syntax_parser = NewsSyntaxParser(emb)
ner_tagger = NewsNERTagger(emb)

names_extractor = NamesExtractor(morph_vocab)

f = open('pushkin-metel.txt', "r", encoding="utf-8")
text = f.read()


doc = Doc(text)
doc.segment(segmenter)
doc.tag_morph(morph_tagger)
doc.parse_syntax(syntax_parser)


doc.tag_ner(ner_tagger)

for span in doc.spans:
    span.normalize(morph_vocab)
    
# print({_.text: _.normal for _ in doc.spans})


for span in doc.spans:
    if span.type == PER:
        span.extract_fact(names_extractor)
    
{_.normal: _.fact.as_dict for _ in doc.spans if _.fact}
Out[ ]:
{'Ненарадове': {'last': 'Ненарадове'},
 'Гаврила Гаврилович Р**': {'first': 'Гаврила', 'last': 'Гаврилович'},
 'Прасковьей Петровною': {'first': 'Прасковьей', 'last': 'Петровною'},
 'Марью Гавриловну': {'first': 'Марью', 'last': 'Гавриловну'},
 'Марья Гавриловна': {'first': 'Марья', 'last': 'Гавриловна'},
 'Марьи Гавриловны': {'first': 'Марьи', 'last': 'Гавриловны'},
 'Владимир Николаевич': {'first': 'Владимир', 'last': 'Николаевич'},
 'Владимир': {'first': 'Владимир'},
 'Маша': {'first': 'Маша'},
 'Пришед': {'first': 'Пришед'},
 'Терешки-кучера': {'last': 'Терешки'},
 'Дравин': {'first': 'Дравин'},
 'Шмит': {'last': 'Шмит'},
 'Терешку': {'last': 'Терешку'},
 'Жадрина': {'first': 'Жадрина'},
 'Жадрин': {'first': 'Жадрин'},
 'Али': {'first': 'Али'},
 'Гаврила Гаврилович': {'first': 'Гаврила', 'last': 'Гаврилович'},
 'Прасковья Петровна': {'first': 'Прасковья', 'last': 'Петровна'},
 'Терешка-кучер': {'last': 'Терешка'},
 'Артемизы': {'first': 'Артемизы'},
 'Н. Изоар': {'first': 'Н', 'last': 'Изоар'},
 'А. Грибоедов': {'first': 'А', 'last': 'Грибоедов'},
 'Чацкий': {'last': 'Чацкий'},
 'Бурмин': {'last': 'Бурмин'},
 'Георгий': {'first': 'Георгий'},
 'Петрарка': {'last': 'Петрарка'},
 'Марьей Гавриловной': {'first': 'Марьей', 'last': 'Гавриловной'},
 'Марьи Гавриловна': {'first': 'Марьи', 'last': 'Гавриловна'},
 'Марье Гавриловне': {'first': 'Марье', 'last': 'Гавриловне'},
 'Ж.-Ж. Руссо': {'last': 'Ж'},
 'Юлия': {'first': 'Юлия'}}

Семестр7-ЛР6

In [ ]:
%%sh

pip install yargy # для обработки текста 
pip install natasha # для обработки текста 
pip install wordcloud # для создания изображения с облаком тегов
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Collecting yargy
  Downloading yargy-0.15.0-py3-none-any.whl (41 kB)
Collecting pymorphy2
  Downloading pymorphy2-0.9.1-py3-none-any.whl (55 kB)
Collecting docopt>=0.6
  Downloading docopt-0.6.2.tar.gz (25 kB)
Collecting dawg-python>=0.7.1
  Downloading DAWG_Python-0.7.2-py2.py3-none-any.whl (11 kB)
Collecting pymorphy2-dicts-ru<3.0,>=2.4
  Downloading pymorphy2_dicts_ru-2.4.417127.4579844-py2.py3-none-any.whl (8.2 MB)
Building wheels for collected packages: docopt
  Building wheel for docopt (setup.py): started
  Building wheel for docopt (setup.py): finished with status 'done'
  Created wheel for docopt: filename=docopt-0.6.2-py2.py3-none-any.whl size=13723 sha256=e182d70885567b75cccca851a8a0f20e84a9ed5785d29d9f688cac0b14d68cc3
  Stored in directory: /root/.cache/pip/wheels/72/b0/3f/1d95f96ff986c7dfffe46ce2be4062f38ebd04b506c77c81b9
Successfully built docopt
Installing collected packages: pymorphy2-dicts-ru, docopt, dawg-python, pymorphy2, yargy
Successfully installed dawg-python-0.7.2 docopt-0.6.2 pymorphy2-0.9.1 pymorphy2-dicts-ru-2.4.417127.4579844 yargy-0.15.0
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Collecting natasha
  Downloading natasha-1.4.0-py3-none-any.whl (34.4 MB)
Collecting slovnet>=0.3.0
  Downloading slovnet-0.5.0-py3-none-any.whl (49 kB)
Collecting razdel>=0.5.0
  Downloading razdel-0.5.0-py3-none-any.whl (21 kB)
Requirement already satisfied: pymorphy2 in /usr/local/lib/python3.7/dist-packages (from natasha) (0.9.1)
Collecting ipymarkup>=0.8.0
  Downloading ipymarkup-0.9.0-py3-none-any.whl (14 kB)
Collecting navec>=0.9.0
  Downloading navec-0.10.0-py3-none-any.whl (23 kB)
Requirement already satisfied: yargy>=0.14.0 in /usr/local/lib/python3.7/dist-packages (from natasha) (0.15.0)
Collecting intervaltree>=3
  Downloading intervaltree-3.1.0.tar.gz (32 kB)
Requirement already satisfied: sortedcontainers<3.0,>=2.0 in /usr/local/lib/python3.7/dist-packages (from intervaltree>=3->ipymarkup>=0.8.0->natasha) (2.4.0)
Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from navec>=0.9.0->natasha) (1.21.6)
Requirement already satisfied: dawg-python>=0.7.1 in /usr/local/lib/python3.7/dist-packages (from pymorphy2->natasha) (0.7.2)
Requirement already satisfied: pymorphy2-dicts-ru<3.0,>=2.4 in /usr/local/lib/python3.7/dist-packages (from pymorphy2->natasha) (2.4.417127.4579844)
Requirement already satisfied: docopt>=0.6 in /usr/local/lib/python3.7/dist-packages (from pymorphy2->natasha) (0.6.2)
Building wheels for collected packages: intervaltree
  Building wheel for intervaltree (setup.py): started
  Building wheel for intervaltree (setup.py): finished with status 'done'
  Created wheel for intervaltree: filename=intervaltree-3.1.0-py2.py3-none-any.whl size=26119 sha256=28bf84179cbcd85692ef96f6c2cee4c2a2e884c9cb4d812ae635be6f5fe3ed9f
  Stored in directory: /root/.cache/pip/wheels/16/85/bd/1001cbb46dcfb71c2001cd7401c6fb250392f22a81ce3722f7
Successfully built intervaltree
Installing collected packages: razdel, navec, intervaltree, slovnet, ipymarkup, natasha
  Attempting uninstall: intervaltree
    Found existing installation: intervaltree 2.1.0
    Uninstalling intervaltree-2.1.0:
      Successfully uninstalled intervaltree-2.1.0
Successfully installed intervaltree-3.1.0 ipymarkup-0.9.0 natasha-1.4.0 navec-0.10.0 razdel-0.5.0 slovnet-0.5.0
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Requirement already satisfied: wordcloud in /usr/local/lib/python3.7/dist-packages (1.8.2.2)
Requirement already satisfied: matplotlib in /usr/local/lib/python3.7/dist-packages (from wordcloud) (3.2.2)
Requirement already satisfied: pillow in /usr/local/lib/python3.7/dist-packages (from wordcloud) (7.1.2)
Requirement already satisfied: numpy>=1.6.1 in /usr/local/lib/python3.7/dist-packages (from wordcloud) (1.21.6)
Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->wordcloud) (2.8.2)
Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/dist-packages (from matplotlib->wordcloud) (0.11.0)
Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->wordcloud) (1.4.4)
Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->wordcloud) (3.0.9)
Requirement already satisfied: typing-extensions in /usr/local/lib/python3.7/dist-packages (from kiwisolver>=1.0.1->matplotlib->wordcloud) (4.1.1)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.1->matplotlib->wordcloud) (1.15.0)
In [ ]:
HERZEN_MAIN = 'https://www.herzen.spb.ru'
import re
from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen('https://www.herzen.spb.ru/main/news/')
bs = BeautifulSoup(html, "html.parser")
# news3.jpg
blockwh = bs.find('td', {'class':'blockwh'}).find_all('a', {'class':'news_header_link'}, limit=30) # 30 = the amount of news on one page.

# print(blockwh)
# news_p_array = image.parent.parent.children
links_list = []
for el in blockwh:
  links_list.append(HERZEN_MAIN + el['href'])
print(links_list)
['https://www.herzen.spb.ru/news/17-10-2022_2/', 'https://www.herzen.spb.ru/news/17-10-2022_1/', 'https://www.herzen.spb.ru/news/17-10-2022/', 'https://www.herzen.spb.ru/news/16-10-2022/', 'https://www.herzen.spb.ru/news/14-10-2022_2/', 'https://www.herzen.spb.ru/news/14-10-2022_1/', 'https://www.herzen.spb.ru/news/14-10-2022/', 'https://www.herzen.spb.ru/news/13-10-2022_6/', 'https://www.herzen.spb.ru/news/13-10-2022_5/', 'https://www.herzen.spb.ru/news/13-10-2022_4/', 'https://www.herzen.spb.ru/news/13-10-2022_3/', 'https://www.herzen.spb.ru/news/13-10-2022_2/', 'https://www.herzen.spb.ru/news/13-10-2022_1/', 'https://www.herzen.spb.ru/news/13-10-2022/', 'https://www.herzen.spb.ru/news/12-10-2022/', 'https://www.herzen.spb.ru/news/11-10-2022/', 'https://www.herzen.spb.ru/news/10-10-2022_5/', 'https://www.herzen.spb.ru/news/10-10-2022_4/', 'https://www.herzen.spb.ru/news/10-10-2022_2/', 'https://www.herzen.spb.ru/news/10-10-2022_1/', 'https://www.herzen.spb.ru/news/08-10-2022/', 'https://www.herzen.spb.ru/news/07-10-2022_4/', 'https://www.herzen.spb.ru/news/07-10-2022_3/', 'https://www.herzen.spb.ru/news/07-10-2022_2/', 'https://www.herzen.spb.ru/news/07-10-2022_1/', 'https://www.herzen.spb.ru/news/07-10-2022/', 'https://www.herzen.spb.ru/news/06-10-2022_4/', 'https://www.herzen.spb.ru/news/06-10-2022_3/', 'https://www.herzen.spb.ru/news/06-10-2022_2/', 'https://www.herzen.spb.ru/news/06-10-2022_1/']
In [ ]:
complete_news_page_list = []
for el in links_list:
  temp_html = urlopen(el)
  temp_bs = BeautifulSoup(temp_html, "html.parser")
  another_blockwh = temp_bs.find('td', {'class':'blockwh'})
  another_blockwh_title = another_blockwh.h1.get_text()
  another_blockwh_contentblock = another_blockwh.find_all('p')
  another_blockwh_content_stripped = another_blockwh_title
  for el in another_blockwh_contentblock:
    stripped = el.strings
    for inner_el in stripped:
      another_blockwh_content_stripped += inner_el
    # print(inner_el)
  complete_news_page_list.append(another_blockwh_content_stripped)
# print(another_blockwh_content_stripped[0])
In [ ]:
print(complete_news_page_list[0])
Актуальные вопросы в повестке очередного заседания ректората «Ковид-19» снизил свою активность, но окончательно сдавать свои позиции не торопится. Меры по санитарно-эпидемиологической защите сотрудников и студентов были в центре внимания заседания ректората, которое провел сегодня, 17 октября, в дистанционном режиме ректор РГПУ им. А. И. Герцена Сергей Тарасов.С сообщением на эту тему выступила директор вузовского медико-оздоровительного центра Елена Ивазова, отметившая, что накануне нового учебного года вышел приказ ректора, в котором определены конкретные меры по профилактике острых респираторных вирусных инфекций, гриппа и новой короновирусной инфекции. В целом, ситуация находится под контролем, но, чтобы не допустить ее ухудшения, нужно тщательно походить к профилактике заболеваний.При проведении массовых мероприятий предложено сохранить соблюдение масочного режима. В обязательном порядке должна осуществляться ежедневная термометрия работников и обучающихся. Всем, кто почувствует недомогание, рекомендовано оставаться дома и обращаться за помощью в местные поликлиники, чтобы предотвратить распространение инфекции в вузе. Подчеркнута важность своевременной ревакцинации, широкой разъяснительной работы в соцсетях и при личном общении по всем вопросам, касающимся профилактики заболеваний, акцентирования внимания к этим проблемам руководителей институтов и факультетов.Об итогах приема в аспирантуру участников заседания проинформировала проректор по научной работе и инновационной деятельности Светлана Писарева.В принятом решении намечено обсудить результаты приемной кампании в аспирантуру на совещании с деканами факультетов и директорами институтов, учесть их при формировании правил ее проведения в следующем году и определить актуальные задачи и меры по их реализации для оптимизации приемной кампании 2023 г.С сообщениями о предстоящих форумах, в проведении которых Герценовский университет играет ключевую роль, выступили проректор по воспитательной деятельности и молодёжной политике Александр Низов, рассказавший о подготовке к форуму классных руководителей начальных школ, и проректор по научной работе и инновационной деятельности Светлана Писарева, ответственная за российско-узбекский образовательный форум.Форум «Первый классный» пройдет в стенах Герценовского университета 26-29 октября и, как отметил Александр Низов, подготовка к нему практически завершена, решены все организационные вопросы и вносятся последние штрихи в программу мероприятия.В порядке контроля ректорат по традиции заслушал исполнение комплексного перечня мероприятий по модернизации материально-технической базы РГПУ в 2022 г.Проректор по персоналу Ирина Баева проинформировала ректорат о мобилизационных моментах, касающихся РГПУ. Сергей Тарасов поручил проработать вопрос материальной помощи семьям мобилизованных сотрудников.
In [ ]:
from natasha import (
    Segmenter,
    MorphVocab,
    
    NewsEmbedding,
    NewsMorphTagger,
    NewsSyntaxParser,
    NewsNERTagger,
    
    PER,
    NamesExtractor,

    Doc
)

segmenter = Segmenter()
morph_vocab = MorphVocab()

emb = NewsEmbedding()
morph_tagger = NewsMorphTagger(emb)
syntax_parser = NewsSyntaxParser(emb)
ner_tagger = NewsNERTagger(emb)

names_extractor = NamesExtractor(morph_vocab)
In [ ]:
%matplotlib inline
from wordcloud import WordCloud
import matplotlib.pyplot as plt
In [ ]:
plt.figure(figsize=(2, 2), dpi=1000)
i = 1
full_wordcloud = ''
for el in complete_news_page_list:
  doc = Doc(el)
  doc.segment(segmenter)
  doc.tag_morph(morph_tagger)
  doc.parse_syntax(syntax_parser)
  doc.tag_ner(ner_tagger)
  for token in doc.tokens:
    token.lemmatize(morph_vocab)
  for span in doc.spans:
    span.normalize(morph_vocab)
  words_for_cloud_str = ''
  for span in doc.spans:
    words_for_cloud_str += span.normal + ' '
  
  full_wordcloud += words_for_cloud_str + ' '
  wordcloud = WordCloud().generate(words_for_cloud_str)
  plt.subplot(5, 6, i)
  i += 1
  plt.imshow(wordcloud, interpolation='bilinear')
  plt.axis("off")
In [ ]:
plt.figure(figsize=(2, 2), dpi=2000)
full_wordcloud_cloud = WordCloud().generate(full_wordcloud)
plt.imshow(full_wordcloud_cloud, interpolation='bilinear')
plt.axis("off")
Out[ ]:
(-0.5, 399.5, 199.5, -0.5)