Русский
Русский
English
Статистика
Реклама

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

В этой заметке я хотел бы дополнить эту статью и рассказать, как можно гибче использовать экстрактор Википедии 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 -pmysql> 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.pageon 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 '\n';

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



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


import pandas as pdimport networkx as nxfrom tqdm.auto import tqdm, trange#Filteringdf = 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 recoveringG = nx.DiGraph()c = 0for 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 = 0nodes = []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      4keyRE = 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
К списку статей
Опубликовано: 31.07.2020 12:10:12
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Big data

Data mining

Natural language processing

Wikipedia

Parsing

Категории

Последние комментарии

© 2006-2021, personeltest.ru