Парсим Википедию, фильтруя, для задач NLP в 44 строки кода

МЕНЮ


Искусственный интеллект
Поиск
Регистрация на сайте
Помощь проекту

ТЕМЫ


Новости ИИРазработка ИИВнедрение ИИРабота разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика

Авторизация



RSS


RSS новости


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

Началось все с того, что мне нужны были определения для различных терминов. Термины и их определения, как правило, являются первым предложением на каждой странице Википедии. Пойдя по самому простому пути, я извлек все статьи и регулярками быстро выцепил все, что было нужно. Проблема в том, что объем определений перевалил за 500 Мб, причем, было слишком много лишнего, например, именованные сущности, города, годы и т.д. которые мне не нужны.

Я верно предположил, что у инструмента WikiExtractor (я буду использовать другую версию, ссылка будет ниже) есть какой-то фильтр и это оказался фильтр по категориям. Категории являются тегами для статей, которые имеют иерархическую структуру для организации страниц. Я на радостях выставил категорию "Точные науки", очень наивно полагая, что все статьи, которые относятся к точным наукам будут включены в список, но чуда не случилось — у каждой страницы свой, крошечный, набор категорий и на отдельно взятой странице нет никакой информации о том, как эти категории соотносятся. Значит, если мне нужны страницы по точным наукам, я должен указать все категории, которые являются потомками для "Точных наук".

Ну не беда, сейчас найду сервис, подумал я, который запросто мне отгрузит все категории от заданного начала. К сожалению, я нашел только это, где можно просто посмотреть, как эти категории взаимосвязаны. Попытка в ручную перебрать категории тоже не увенчалась успехом, зато я "обрадовался" тому, что эти категории имеют структуру не дерева, как я думал все это время, а просто направленного графа, с циклами. Причем, сама иерархия очень сильно плывет — скажу наперед, что задав начальную точку "Математика", легко можно дойти до Александра I. В итоге, мне оставалось только восстановить этот граф локально и как-то получить список интересующих меня категорий.

Итак, задача ставится следующим образом: начиная с какой-то вершины, получить список всех категорий, которые связаны с это вершиной, имея возможность как-то ограничивать их.

Работа проводилась на машине с Ubuntu 16.04, но, полагаю, что для 18.04 следующие инструкции не вызовут проблем.

Первым делом, нам необходимо скачать все необходимые данные вот отсюда, а именно

  • ruwiki-latest-pages-articles.xml.bz2
  • ruwiki-latest-categorylinks.sql.gz
  • ruwiki-latest-category.sql.gz
  • ruwiki-latest-page.sql.gz

Таблица categorylinks содержит связи между страницей, в смысле Википедии, и ссылкой на категорию вида [[Category:Title]] в любом месте этой страницы, информация. Нас интересуют столбцы cl_from, которая содержит id страницы, и cl_to, которая содержит название категории. Для того, чтобы связать id страницы, нам нужна таблица page (информация) со столбцами page_id и page_title. Но нам не нужно знать взаимосвязь всех страниц, мы хотим только категории. Все категории, или их большинство, как я понял, имеют свою страницу, значит нам нужен перечень всех категорий, чтобы фильтровать названия страниц. Эта информацию содержится в таблице category([информация](category table)) в столбце cat_title. Файл pages-articles.xml содержит текст самих статей.

Для работы с базами данных нам необходим mysql. Установить его можно, выполнив команду

sudo apt-get install mysql-server  mysql-client

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

$ mysql -u username -p mysql> create database category; mysql> create database categorylinks; mysql> create database page;

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

$  mysql -u username -p category < ruwiki-latest-category.sql $  mysql -u username -p categorylinks < ruwiki-latest-categorylinks.sql $  mysql -u username -p page < ruwiki-latest-page.sql


Теперь нам нужно получить таблицу, в которой будет отражено как между собой связаны категории и для дальнейшей работы выгрузить таблицу в csv. Сделать это можно следующим запросом

mysql> select page_title, cl_to from categorylinks.categorylinks join page.page on cl_from = page_id  where page_title in (select cat_title from category) INTO outfile '/var/lib/mysql-files/category.csv' FIELDS terminated by ';' enclosed by '"' lines terminated by ' ';

Результат будет выглядеть следующим образом. Не забудьте вручную добавить название столбцов.

Стоит заметить, что слева у нас потомок, а справа — его предки, поэтому восстанавливать граф будем от потомков к предкам. Кроме того, есть еще очень много разных служебных категорий, которые мне лично не нужны, поэтому я их отфилтровал, сократив количество строк с примерно 1,6 миллионов до 1,1. Сделать все это можно при помощи следующего кода.

import pandas as pd import networkx as nx from tqdm.auto import tqdm, trange  #Filtering df = pd.read_csv("category.csv", sep=";", error_bad_lines=False) df = df.dropna() df_filtered = df[df.parant.str.contains("[А-Яа-я]+:") != True]  df_filtered = df_filtered[df_filtered.parant.str.contains("Страницы,_") != True] df_filtered = df_filtered[df_filtered.parant.str.contains("Статьи_проекта_") != True]  df_filtered = df_filtered[df_filtered.parant.str.contains("Хорошие_статьи") != True]  df_filtered = df_filtered[df_filtered.parant.str.contains("Перенаправления,_") != True]  df_filtered = df_filtered[df_filtered.parant.str.contains("Избранные_списки_") != True] df_filtered = df_filtered[df_filtered.parant.str.contains("Избранные_статьи_") != True] df_filtered = df_filtered[df_filtered.parant.str.contains("Списки_проекта") != True]  df_filtered = df_filtered[df_filtered.parant.str.contains("Добротные_статьи_") != True] df_filtered = df_filtered[df_filtered.parant.str.contains("Статьи") != True]   # Graph recovering G = nx.DiGraph() c = 0 for i, gr in tqdm(df_filtered.groupby('child')):      vertex = set()     edges = []     for i, r in gr.iterrows():         G.add_node(r.parant, color="white")         G.add_node(r.child, color="white")         G.add_edge(r.parant, r.child) 


Для того, чтобы пользоваться этим графом и решить поставленную в начале задачу, воспользуемся алгоритмом поиска в глубину с модификациями для обнаружения циклов, для чего мы пометили каждый узел белым цветом, и ограничения глубины поиска.

counter = 0 nodes = []  def dfs(G, node, max_depth):     global nodes, counter     G.nodes[node]['color'] = 'gray'     nodes.append(node)     counter += 1     if counter == max_depth:         counter -= 1         return     for v in G.successors(node):         if G.nodes[v]['color'] == 'white':             dfs(G, v, max_depth)         elif G.nodes[v]['color'] == 'gray':             continue     counter -= 1

В результате, в листе nodes у нас содержатся все категории начиная от указаной и до желаемой глубины от начала. Ниже представлен пример для начальной точки "Точные науки" с ограничением на глубину в 5 вершин. Всего их получилось около 2500 тысяч. Конечно, там содержатся категории, которые не относятся к точным наукам и, возможно, каких-то категорий, которые должны быть, там не окажутся, но с этим способом лучше не выйдет — либо больше покрытие и больше ненужных категорий, либо наоборот. Однако, это гораздо лучше, чем вручную отбирать эти категории.

Результат нужно сохранить построчно в файл, он нам понадобится для фильтарции.

Подкатегории с вершины Точные науки

Точные_науки Информатика CAM Авторы_учебников_информатики Архивное_дело Археографические_комиссии Археографические_комиссии_Украины Виленская_археографическая_комиссия Архивисты Архивариусы Архивисты_по_алфавиту Архивисты_по_векам Архивисты_по_странам Архивное_дело_на_Украине Архивисты_Украины  ...  Терминология_телевидения Терминология_японских_боевых_искусств Термины_для_знаменитостей Термины_и_понятия_аниме_и_манги Технические_термины Транспортная_терминология Фантастические_термины_по_их_изобретателям Филателистические_термины Философские_термины Цирковые_термины Экономические_термины Японские_исторические_термины Экономика_знаний Инкапсуляция_(программирование)  ...  Бесконечность Бесконечные_графы Единое Философы_математики Прокл_Диадох Функции Арифметические_функции Мультипликативные_функции Большие_числа Кусочно-линейные_функции Преобразования Дискретные_преобразования Интегральные_преобразования Преобразования_пространства Теория_потенциала Типы_функций Числа

Для того, чтобы применить эти категории для фильтрации для русского языка, однако, нужно кое-что подправить в исходниках. Я использовал эту версию. Сейчас там что-то новое, возможно, исправления ниже уже не актуальны. В файле WikiExtractor.py нужно заменить "Category" на "Категория" в двух местах. Области с уже исправленным вариантом представлены ниже:

 tagRE = re.compile(r'(.*?)<(/?w+)[^>]*?>(?:([^<]*)(<.*?>)?)?') #                    1     2               3      4 keyRE = re.compile(r'key="(d*)"') catRE = re.compile(r'[[Категория:([^|]+).*]].*')  # capture the category name [[Category:Category name|Sortkey]]"  def load_templates(file, output_file=None): ...

if inText:     page.append(line)     # extract categories     if line.lstrip().startswith('[[Категория:'):         mCat = catRE.search(line)         if mCat:             catSet.add(mCat.group(1))

После этого нужно запустить команду

python WikiExtractor.py --filter_category categories --output wiki_filtered ruwiki-latest-pages-articles.xml

где categories — это файл с категориями. Отфильтрованные статьи будут лежать в wiki_filtered.
На этом все. Спасибо за внимание.


Источник: habr.com

Комментарии: