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

Parsing

Как лайкать 4000 тысячи девушек в час

28.01.2021 20:21:55 | Автор: admin

Зачем

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

КАК

Ответ короткий - легко, вот ссылка на collab

Длинный ответ - тиндер обменивается открытым json с https://api.gotinder.com, а в XHR лежит наш x auth key. Остальное - дело техники и библиотеки Response.

Парсинг

Код для профиля

import datetimefrom geopy.geocoders import NominatimAuthKey = ''# Ключ пихуем сюдаTINDER_URL = "https://api.gotinder.com"geolocator = Nominatim(user_agent="auto-tinder")PROF_FILE = "./images/unclassified/profiles.txt"class Person(object):    def __init__(self, data, api):        self._api = api        self.id = data["_id"]        self.name = data.get("name", "Unknown")        self.bio = data.get("bio", "")               self.birth_date = datetime.datetime.strptime(data["birth_date"], '%Y-%m-%dT%H:%M:%S.%fZ') if data.get(            "birth_date", False) else None        self.gender = ["Male", "Female", "Unknown"][data.get("gender", 2)]        self.images = list(map(lambda photo: photo["url"], data.get("photos", [])))        self.jobs = list(            map(lambda job: {"title": job.get("title", {}).get("name"), "company": job.get("company", {}).get("name")}, data.get("jobs", [])))        self.schools = list(map(lambda school: school["name"], data.get("schools", [])))        if data.get("pos", False):            self.location = geolocator.reverse(f'{data["pos"]["lat"]}, {data["pos"]["lon"]}')        def __repr__(self):        return f"{self.id}  -  {self.name} ({self.birth_date.strftime('%d.%m.%Y')})"    def like(self):        return self._api.like(self.id)    def dislike(self):        return self._api.dislike(self.id)

Ничего интересного тут нет, просто ходим по html и тянем то что нужно

Работа с API

import requestsTINDER_URL = "https://api.gotinder.com"class tinderAPI():    def __init__(self, token):        self._token = token    def profile(self):        data = requests.get(TINDER_URL + "/v2/profile?include=account%2Cuser", headers={"X-Auth-Token": self._token}).json()        return Profile(data["data"], self)    def matches(self, limit=10):        data = requests.get(TINDER_URL + f"/v2/matches?count={limit}", headers={"X-Auth-Token": self._token}).json()        return list(map(lambda match: Person(match["person"], self), data["data"]["matches"]))    def like(self, user_id):        data = requests.get(TINDER_URL + f"/like/{user_id}", headers={"X-Auth-Token": self._token}).json()        return {            "is_match": data["match"],            "liked_remaining": data["likes_remaining"]        }    def dislike(self, user_id):        requests.get(TINDER_URL + f"/pass/{user_id}", headers={"X-Auth-Token": self._token}).json()        return True    def nearby_persons(self):        data = requests.get(TINDER_URL + "/v2/recs/core", headers={"X-Auth-Token": self._token}).json()                return list(map(lambda user: Person(user["user"], self), data["data"]["results"]))

Тут мы получаем json, разбираем его и тащим все что нам нужно(список юзеров)

Кладем все в .csv

import timeimport pandas as pddf = pd.DataFrame()prf = []prsn= []vuz = []gender = []job = []if __name__ == "__main__":    token = AuthKey    api = tinderAPI(token)    while True:        persons = api.nearby_persons()        for person in persons:           #тут нет логики, напишите сами если захотит            print(person)            time.sleep(1)            person.dislike()            prf+=[person.bio]            prsn+=[person]            vuz+=[person.schools]            job+=[person.jobs]            gender+=[person.gender]            print(person.bio)df['vuz'] = vuzdf['jobs'] = jobdf['person'] = prsndf['bio'] = prf df['gender'] = genderdf.to_csv('tinder.csv')

Когда кончаются пользователи оно вылетает и не сохраняет, по этому нужно выделять код для сохранения в отдельную ячейку кода. Благо collab позволяет не думать, а делать. Более опытные пользователи могут сказать что надо обложить в try: , но мне не хочется, да и смысла нет.

Два часа спустя получаем все что хотели!!

Спасибо за прочтение, скоро будет вторая часть про тематическое моделирование с помощью BERT

Большое спасибо я хочу сказать своим подписчикам в телеграмме

Так же в моем телеграмме есть много ноутбуков и статей про DS и не очень. Например рендер 3д моделей на https://colab.research.google.com/ А в рубрике #чтивонаночь я рассказываю про самое интересное1 что я нашел за последние время.

Код - https://colab.research.google.com/drive/1ap8pUgoYATYb5NFjiccd-1eFUIJn5ycj#scrollTo=LQVYr9HM88Ph

Подробнее..
Категории: Python , Data mining , Api , Parsing , Datamining

Как лайкать четыре тысячи девушек в час

28.01.2021 22:20:35 | Автор: admin

Зачем

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

Как

Ответ короткий - легко, вот ссылка на collab

Длинный ответ - тиндер обменивается открытым json с https://api.gotinder.com, а в XHR лежит наш x auth key. Остальное - дело техники и библиотеки Response.

Парсинг

Код для профиля

import datetimefrom geopy.geocoders import NominatimAuthKey = ''# Ключ пихуем сюдаTINDER_URL = "https://api.gotinder.com"geolocator = Nominatim(user_agent="auto-tinder")PROF_FILE = "./images/unclassified/profiles.txt"class Person(object):    def __init__(self, data, api):        self._api = api        self.id = data["_id"]        self.name = data.get("name", "Unknown")        self.bio = data.get("bio", "")               self.birth_date = datetime.datetime.strptime(data["birth_date"], '%Y-%m-%dT%H:%M:%S.%fZ') if data.get(            "birth_date", False) else None        self.gender = ["Male", "Female", "Unknown"][data.get("gender", 2)]        self.images = list(map(lambda photo: photo["url"], data.get("photos", [])))        self.jobs = list(            map(lambda job: {"title": job.get("title", {}).get("name"), "company": job.get("company", {}).get("name")}, data.get("jobs", [])))        self.schools = list(map(lambda school: school["name"], data.get("schools", [])))        if data.get("pos", False):            self.location = geolocator.reverse(f'{data["pos"]["lat"]}, {data["pos"]["lon"]}')        def __repr__(self):        return f"{self.id}  -  {self.name} ({self.birth_date.strftime('%d.%m.%Y')})"    def like(self):        return self._api.like(self.id)    def dislike(self):        return self._api.dislike(self.id)

Ничего интересного тут нет, просто ходим по html и тянем то что нужно

Работа с API

import requestsTINDER_URL = "https://api.gotinder.com"class tinderAPI():    def __init__(self, token):        self._token = token    def profile(self):        data = requests.get(TINDER_URL + "/v2/profile?include=account%2Cuser", headers={"X-Auth-Token": self._token}).json()        return Profile(data["data"], self)    def matches(self, limit=10):        data = requests.get(TINDER_URL + f"/v2/matches?count={limit}", headers={"X-Auth-Token": self._token}).json()        return list(map(lambda match: Person(match["person"], self), data["data"]["matches"]))    def like(self, user_id):        data = requests.get(TINDER_URL + f"/like/{user_id}", headers={"X-Auth-Token": self._token}).json()        return {            "is_match": data["match"],            "liked_remaining": data["likes_remaining"]        }    def dislike(self, user_id):        requests.get(TINDER_URL + f"/pass/{user_id}", headers={"X-Auth-Token": self._token}).json()        return True    def nearby_persons(self):        data = requests.get(TINDER_URL + "/v2/recs/core", headers={"X-Auth-Token": self._token}).json()                return list(map(lambda user: Person(user["user"], self), data["data"]["results"]))

Тут мы получаем json, разбираем его и тащим все что нам нужно(список юзеров)

Кладем все в .csv

import timeimport pandas as pddf = pd.DataFrame()prf = []prsn= []vuz = []gender = []job = []if __name__ == "__main__":    token = AuthKey    api = tinderAPI(token)    while True:        persons = api.nearby_persons()        for person in persons:           #тут нет логики, напишите сами если захотит            print(person)            time.sleep(1)            person.dislike()            prf+=[person.bio]            prsn+=[person]            vuz+=[person.schools]            job+=[person.jobs]            gender+=[person.gender]            print(person.bio)df['vuz'] = vuzdf['jobs'] = jobdf['person'] = prsndf['bio'] = prf df['gender'] = genderdf.to_csv('tinder.csv')

Когда кончаются пользователи оно вылетает и не сохраняет, по этому нужно выделять код для сохранения в отдельную ячейку кода. Благо collab позволяет не думать, а делать. Более опытные пользователи могут сказать что надо обложить в try: , но мне не хочется, да и смысла нет.

Два часа спустя получаем все что хотели!!

Спасибо за прочтение, скоро будет вторая часть про тематическое моделирование с помощью BERT

Большое спасибо я хочу сказать своим подписчикам в телеграмме

Так же в моем телеграмме есть много ноутбуков и статей про DS и не очень. Например рендер 3д моделей на https://colab.research.google.com/ А в рубрике #чтивонаночь я рассказываю про самое интересное, что я нашел за последние время.

Код

Подробнее..
Категории: Python , Data mining , Api , Parsing , Datamining

Конвертируем doc в docx и xml на C

23.11.2020 10:05:21 | Автор: admin

Продолжаю свой цикл статей, посвященный конвертации различных текстовых файлов с помощью решений, реализованных на языке C#.


С момента моей последней публикации Конвертация xls в xlsx и xml на C# прошло более полугода, за которые я успел сменить как работодателя, так и пересмотреть свои взгляды на некоторые аспекты коммерческой разработки. Сейчас, работая в международной компании с совершенно иным подходом к разработке ПО (ревью кода, юнит-тестирование, команда автотестеров, строгое соблюдение СМК, заботливый менеджер, очаровательная HR и прочие корпоративные плюшки), я начинаю понимать, почему некоторые из комментаторов интересовались целесообразностью предлагаемых мной велокостылей, когда на рынке есть очень достойные готовые решения, например, от e-iceblue. Но давайте не забывать, что ситуации бывают разные, компании тем более, и если потребность в решении какой-то задачи с использованием определенного инструментария возникла у одного человека, то со значительной долей вероятности она возникнет и у другого.



Итак, дано:


  1. Неопределенное множество файлов в формате .doc, которые нужно конвертировать в xml (например, для парсинга и организации автоматизированной навигации внутри текста), желательно с сохранением форматирования.
  2. На сервере памяти чуть больше, чем у рыбки, а на процессоре уже можно жарить яичницу, да и у компании нет лишней лицензии на Word, поэтому конвертация должна происходить без запуска каких-либо офисных приложений.
  3. Сервис должен быть написан на языке C# и в последующем интегрирован в код другого продукта.
  4. На решение задачи два дня и две ночи, которые истекли вчера.

Поехали!


  • Во-первых, нужно сразу уяснить, что старые офисные форматы файлов, такие как .doc и .xls, являются бинарными, и достать что-нибудь человекочитаемое из них без использования текстовых редакторов/процессоров не получится. Прочитать об этом можно в официальной документации. Если есть желание поковыряться поглубже, посчитать нолики с единичками и узнать, что они означают, то лучше сразу перейти сюда.
  • Во-вторых, несмотря на наличие бесплатных решений для работы с .doc, большинство из них написаны на Python, Ruby и чем угодно еще, но не C#.
  • В-третьих, найденное мной решение, а именно библиотека b2xtranslator, является единственным доступным бесплатным инструментом такого рода, еще и написана при поддержке Microsoft, если верить вот этому источнику. Если вдруг вы встречали какие-нибудь аналоги данной библиотеки, пожалуйста, напишите об этом в комментариях. Даже это душеспасительное решение не превратит .doc в .xml, однако поможет нам превратить его в .docx, с которым мы уже умеем работать.

Довольно слов давайте к делу


Установка b2xtranslator


Для работы нам понадобиться библиотека b2xtranslator. Ее можно подключить через менеджера пакетов NuGet.

Однако я настоятельно рекомендую скачать ее из официального git-репозитория по следующим причинам:


  • a) Библиотека представляет собой комбайн, работающий с различными бинарными офисными документами (.doc, .xls, .ppt), что может быть избыточным
  • b) Проект достаточно долго не обновляется и вам, возможно, придется доработать его напильником
  • c) Задача, с которой я столкнулся, как раз потребовала внесения некоторых изменений в работу библиотеки, а также изучения ее алгоритмов и используемых структур для успешной интеграции в свое решение
    Для дальнейшей работы нам понадобиться подключить в свое решение два проекта из библиотеки: b2xtranslator\Common\b2xtranslator.csproj и b2xtranslator\Doc\b2xtranslator.doc.csproj

Конвертация .doc в .docx


Конвертация документов строится по следующему алгоритму:


  1. Инициализация дескриптора для конвертируемого файла.
    Для этого необходимо создать экземпляр класса StructuredStorageReader, конструктор которого в качестве аргумента может принимать или путь до файла, или последовательность байтов (Stream), что делает его крайне удобным при работе с файлами, загружаемыми по сети. Также обращаю внимание, что так как библиотека b2xtranslator является комбайном для конвертации бинарных офисных форматов в современный OpenXML, то независимо от того, какой формат мы хотим конвертировать (.ppt, .xls или .doc) инициализация дескриптора всегда будет происходить с помощью указанного класса (StructuredStorageReader).
    StructuredStorageReader reader = new StructuredStorageReader(docPath);
    
  2. Парсинг бинарного .doc файла с помощью объекта класса WordDocument, конструктор которого в качестве аргумента принимает объект типа StructuredStorageReader.
    WordDocument doc = new WordDocument(reader);
    
  3. Создание объекта, который будет хранить данные для файла в формате .docx.
    Для этого используется статический метод cs public static WordprocessingDocument Create(string fileName, OpenXmlPackage.DocumentType type) класса WordprocessingDocument. В первом аргументе указываем имя нового файла (вместе с путем), а вот во втором мы должны выбрать тип файла, который должен получиться на выходе:
    a. Document (обычный документ с расширением .docx);
    b. MacroEnabledDocument (файл, содержащий макросы, с расширением .docm);
    c. Template (файл шаблонов word с расширением .dotx);
    d. MacroEnabledTemplate (файл с шаблоном word, содержащий макросы. Имеет расширение .dotm).
    WordprocessingDocument docx = WordprocessingDocument.Create(docxPath, DocumentType.Document);
    
  4. Конвертация данных из бинарного формата в формат OpenXML и их запись в объект типа WordprocessingDocument.
    За выполнение указанной процедуры отвечает статический метод
    public static void Convert(WordDocument doc, WordprocessingDocument docx)
    

    класса Converter, который заодно и записывает получившийся результат в файл.

    Converter.Convert(doc, docx);
    

    В результате у вас должен получиться вот такой код:

    using b2xtranslator.StructuredStorage.Reader;using b2xtranslator.DocFileFormat;using b2xtranslator.OpenXmlLib.WordprocessingML;using b2xtranslator.WordprocessingMLMapping;using static b2xtranslator.OpenXmlLib.OpenXmlPackage;namespace ConverterToXml.Converters{    public class DocToDocx    {        public void ConvertToDocx(string docPath, string docxPath)        {            StructuredStorageReader reader = new StructuredStorageReader(docPath);            WordDocument doc = new WordDocument(reader);            WordprocessingDocument docx = WordprocessingDocument.Create(docxPath, DocumentType.Document);            Converter.Convert(doc, docx);        }    }}
    

    Внимание!
    Если вы используете платформу .Net Core 3 и выше в своем решении, обратите внимание на целевые среды для подключенных проектов b2xtranslator. Так как библиотека была написана довольно давно и не обновляется с 2018 года, по умолчанию она собирается под .Net Core 2.
    Чтобы сменить целевую среду, щелкните правой кнопкой мыши по проекту, выберите пункт Свойства и поменяйте целевую рабочую среду. В противном случае вы можете столкнуться с проблемой невозможности конвертации файлов .doc, содержащих в себе таблицы.
    Я не стал разбираться, почему так происходит, но энтузиастам могу подсказать, что причину стоит искать в 40 строчке файла ~\b2xtranslator\Doc\WordprocessingMLMapping\MainDocumentMapping.cs в момент обработки таблицы.
    Кроме того, рекомендую собирать все проекты и само решение под 64-битную платформу во избежание всяких непонятных ошибок.



    Сохранение результата в поток байтов


    Так как моей целью при использовании данного решения была конвертация .doc в .xml, а не в .docx, предлагаю вовсе не сохранять промежуточный OpenXML файл, а записать его в виде потока байтов. К сожалению, b2xtranslator не предоставляет нам подходящих методов, но это довольно легко исправить:
    В абстрактном классе OpenXmlPackage (см. ~\b2xtranslator\Common\OpenXmlLib\OpenXmlPackage.cs) давайте создадим виртуальный метод:


    public virtual byte[] CloseWithoutSavingFile(){    var writer = new OpenXmlWriter();    MemoryStream stream = new MemoryStream();    writer.Open(stream);    this.WritePackage(writer);    writer.Close();    byte[] docxStreamArray = stream.ToArray();    return docxStreamArray;}
    

    По большому счету, данный метод будет заменять собой метод Close(). Вот его исходный код:


    public virtual void Close(){     // serialize the package on closing    var writer = new OpenXmlWriter();    writer.Open(this.FileName);    this.WritePackage(writer);    writer.Close();}
    

    Скажем спасибо разработчикам библиотеки за то, что не забыли перегрузить метод Open(), который может принимать или имя файла, или поток байтов. Однако, библиотечный метод Close(), который как раз и отвечает за запись результата в файл, вызывается в методе Dispose() в классе OpenXmlPackage. Чтобы ничего лишнего не поломать и не заморачиваться с архитектурой фабрик (тем более в чужом проекте), я предлагаю просто закомментировать код внутри метода Dispose() и вызвать метод CloseWithoutSavingFile(), но уже внутри нашего метода после вызова Converter.Convert(doc, docx).
    Для сохранения результата конвертации вызываем вместо docx.Close() метод docx.CloseWithoutSavingFile():


    public MemoryStream ConvertToDocxMemoryStream(Stream stream){    StructuredStorageReader reader = new StructuredStorageReader(stream);    WordDocument doc = new WordDocument(reader);    var docx = WordprocessingDocument.Create("docx", DocumentType.Document);    Converter.Convert(doc, docx);    return new MemoryStream(docx.CloseWithoutSavingFile());}
    

    Теперь библиотека b2xtranslator будет возвращать сконвертированный из формата .doc в .docx файл в виде потока байтов. Даже если у вас нет цели получить на выходе .xml, такой метод может оказаться более подходящим для дальнейшей работы с файлами, тем более что стрим всегда можно сохранить в виде файла там, где вам надо.
    Для тех, кому все-таки очень хочется получить на выходе .xml документ, еще и с сохраненной структурой, предлагаю дойти до кухни, сварить кофе покрепче, добавить в него рюмку коньяка и приготовиться к приключению на 20 минут.


    Конвертация .doc в .xml



    Теперь, когда, казалось бы, можно воспользоваться классом-конвертором DocxToXml, работа которого была описана вот в этой статье, нас поджидает сюрприз, связанный с особенностями работы b2xtranslator.
    Давайте посмотрим на результат работы библиотеки повнимательнее и сравним с оригинальным .docx файлом, из которого был экспортирован .doc файл для конвертации. Для этого достаточно изменить расширение сравниваемых файлов с .docx на .zip. Вот отличия, которые мы увидим, заглянув внутрь архивов:


    1. В результате конвертации в новом .docx файле (справа) отсутствуют папки customXml и docProps.
    2. Внутри папки word, мы также найдем определенные отличия, перечислять которые я, конечно же, не буду:
    3. Естественно, что и метаданные, по которым осуществляется навигация внутри документа, также отличаются. Например, на представленном скрине и далее оригинальный .docx слева, сгенерированный b2xtranslator cправа.

      Налицо явное отличие в атрибутах тега w:document, но этим отличия не заканчиваются. Всю "мощь" библиотеки мы ощутим, когда захотим обработать списки и при этом:
      a. Сохранить их нумерацию
      b. Не потерять структуру вложенности
      c. Отделить один список от другого

    Давайте сравним файлы document.xml для вот этого списка:


    1.1 Первый.Первый1.2 Первый.Второй1.2.1   Первый.Второй.Первый1.2.2   Первый.Второй.ВторойКакая-то строчка 1.2.3   Первый.Второй.Третий2.  Второй2.1 Второй.Первый
    

    Вот так будет выглядеть .xml для первого элемента списка.


    -Во-первых, мы видим, что сама структура документов несколько отличается (например, точка внутри строк рассматривается как отдельный элемент, что, как оказалось, совсем не страшно).
    -Во-вторых, у тегов остался только один атрибут (w:rsidR), а вот w:rsidR, w14:textId, w:rsidRDefault, w:paraId и w:rsidP пропали. Все эти особенности приводят к тому, что наш класс-конвертер DocxToXml(про него подробно можно почитать здесь) подавится и поднимет лапки вверх с ошибкой NullReferenceException, что указывает на отсутствие индексирования параграфов внутри документа.

    Вместе с тем, если мы попытаемся такой файл отрыть в Word, то увидим, что все хорошо отображается, а таблицы и списки покоятся на своих местах! Магия!
    В общем, когда в поисках решения я потратил N часов на чтение документации, мои красные от дебагера глаза омылись горькими слезами, а один лишь запах кофе стремился показать коллегам мой дневной рацион, решение было найдено!
    Исходя из документации к формату doc и алгоритмов работы b2xtranslator, можно сделать вывод, что исторически в бинарных офисных текстовых документах отсутствовала индексация по параграфам*. Возникает задача расставить необходимые теги в нужных местах.
    За индекс параграфа отвечает атрибут тега paraId, о чем прямо написано здесь. Данный атрибут относится к пространству имен w14, о чем можно догадаться при изучении document.xml из архива .docx. В принципе, на скринах выше вы это тоже видите. Объявление пространства имен в .xml выглядит так:


    xmlns:wp14="http://personeltest.ru/away/schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
    

    Теперь давайте заставим b2xtranslator добавлять это пространство имен и идентификатор каждому параграфу. Для этого в файле ~\b2xtranslator\Common\OpenXmlLib\ContentTypes.cs после 113 строки добавим вот эту строчку:


    public const string WordprocessingML2010 = "http://schemas.microsoft.com/office/word/2010/wordml";
    

    Кстати, если посмотрите на комментарии в коде, то увидите, что в этом блоке как раз располагаются поддерживаемые пространства имен для вордовых документов:

    Далее наша задача заставить библиотеку вставлять в начало файла ссылку на данное пространство имен. Для этого в файле ~\b2xtranslator\Doc\WordprocessingMLMapping\MainDocumentMapping.cs в 24 строке вставим код:


    this._writer.WriteAttributeString("xmlns", "w14", null, OpenXmlNamespaces.WordprocessingML2010);
    

    Разработчики библиотеки также позаботились о документации:


    Теперь дело за малым заставить b2xtranslator индексировать параграфы. В качестве индексов предлагаю использовать рандомно сгенерированные GUID может быть, это несколько тяжеловато, но зато надежно!

    Переходим в файл ~\b2xtranslator\Doc\WordprocessingMLMapping\DocumentMapping.cs и в 504 и 505 строки вставляем вот этот код:


    this._writer.WriteAttributeString("w14", "paraId", OpenXmlNamespaces.WordprocessingML2010, Guid.NewGuid().ToString());            this._writer.WriteAttributeString("w14", "textId", OpenXmlNamespaces.WordprocessingML2010, "77777777");
    

    Что касается второй строчки, в которой мы добавляем каждому тегу параграфа атрибут w14:textId = "77777777", то тут можно лишь сказать, что без этого атрибута ничего работать не будет. Для пытливых умов вот ссылка на документацию.
    Если серьезно, то, как я понимаю, атрибут используется, когда текст разделен на разные блоки, внутри которых происходит индексация тегов, которые могут иметь одинаковый Id внутри одного документа. Видимо, для этих случаев используется дополнительная индексация текстовых блоков. Однако, так как мы используем GUID, который в несколько раз больше индексов, используемых в вордовских документах по умолчанию, то генерацией отдельных индексов для текстовых блоков можно и пренебречь.


    Вот теперь мы получили .docx-файл, пригодный для дальнейшего преобразования в .xml. Подробнее о том, как работать с ним дальше, вы можете прочитать в этой статье или воспользоваться уже выложенным на github решением.


    В заключение, если у вас на проекте есть возможность воспользоваться платным надежным софтом, то этот путь скорее всего не для вас. Однако же, если вы энтузиаст, пишете свой pet-проект и уважительно относитесь к авторским правам, а также если ваш проект находится в стадии прототипирования и пока не готов к покупке дорогостоящих лицензий, а разработку продолжать надо, то, мне кажется, этот вариант может вам очень даже подойти! Тем более, что у вас есть возможность воспользоваться готовым решением и не заливать свои краснющие зенки визином, изучая документацию и особенности работы некоторых, на первый взгляд, сомнительных решений.


    Наконец, бонус для тех, кто хочет разобраться, что значат все эти бесконечные теги и их атрибуты в документах .docx и как они мапаются на бинарный .doc: советую заглянуть в файл ~\b2xtranslator\Doc\DocFileFormat\CharacterProperties.cs, а также посмотреть спецификацию для docx и doc.

Подробнее..

Работа с сложными JSON-объектами в Swift (Codable)

20.03.2021 20:11:26 | Автор: admin

Написать эту статью меня сподвиг почти случившийся нервный срыв, причиной которого стало мое желание научиться общаться с сторонними API, конкретно меня интересовал процесс декодирования JSON-докуметов! Нервного срыва я, к счастью, избежал, поэтому теперь настало время сделать вклад в сообщество и попробовать опубликовать свою первую статью на Хабре.

Почему вообще возникли проблемы с такой простой задачей?

Чтобы понять, откуда проблемы, нужно сначала рассказать об инструментарии, которым я пользовался. Для декодирования JSON-объектов я использовал относительно новый синтезированный (synthesized) протокол библиотеки Foundation - Сodable.

Codable - это встроенный протокол, позволяющий заниматься кодированием в объект текстового формата и декодированием из текстового формата. Codable - это протокол-сумма двух других протоколов: Decodable и Encodable.

Cтоит сделать оговорку, что протокол называется синтезированным тогда, когда часть его методов и свойств имеют дефолтную реализацию. Зачем такие протоколы нужны? Чтобы облегчить работу с их подписанием, как минимум уменьшив количество boilerplate-кода.

Еще такие протоколы позволяют работать с композицией, а не наследованием!

Вот теперь поговорим о проблемах:

  • Во-первых, как и все новое, этот протокол плохо описан в документации Apple. Что я имею в виду под "плохо описан"? Разбираются самые простые случаи работы с JSON объектами; очень кратко описаны методы и свойства протокола.

  • Во-вторых, проблема языкового барьера. На понимание того, как составить поисковой запрос ушло несколько часов. Нервы в это время кончались с молниеносной скоростью.

  • В-третьих, то что гуглится простыми методами не рассказывает о сложных случаях

Теперь давайте поговорим о конкретном кейсе. Кейс такой: используя API сервиса Flickr, произвести поиск N-фотографий по ключевому слову (ключевое слово: читай поисковой запрос) и вывести их на экран.

Сначала все стандартно: получаем ключ к API, ищем нужный REST-метод в документации к API, смотрим описание аргументов запроса к ресурсу, cоставляем и отправляем GET-запрос.

И тут видим это в качестве полученного от Flickr JSON объекта:

{   "photos":{      "page":1,      "pages":"11824",      "perpage":2,      "total":"23648",      "photo":[         {            "id":"50972466107",            "owner":"191126281@N@7" ,            "secret":"Q6f861f8b0",            "server":"65535",            "farm":66,            "title":"Prompt & Reliable Electrolux Oven Repairs in Gold Coast",            "ispublic":1,            "isfriend":0,            "isfamily":0         },         {            "id":"50970556873",            "owner":"49965961@NG0",            "secret":"21f7a6424b",            "server":"65535",            "farm" 66,            "title":"IMG_20210222_145514",            "ispublic":1,            "isfriend":0,            "isfamily":0         }      ]   },   "stat":"ok"}

Ага, все вроде бы хорошо. Но где же фотографии? А фотографий здесь нет, потому что это логично. Почему логично? Фото - тяжелый объект, передающийся по сети в виде последовательности байт (чем лучше качество фото, тем больше байт). Так вот из-за того, что фото громоздкие, а интерфейс приложения в идеале должен быть отзывчивым, поэтому информация о таких объектах передается в виде их местонахождения (местонахождения той самой последовательности байт) на другом ресурсе (сервере) сервиса Flickr для того, чтобы потом можно было распараллелить процесс загрузки фотографий.

Что имеем? Имеем ситуацию, где необходимо составить два последовательных GET-запроса к разным ресурсам, причем второй запрос будет использовать информацию первого! Соответственно алгоритм действий: запрос-декодирование-запрос-декодирование. И вот тут. и начались проблемы. От JSON-объекта полученного после первого запроса мне были нужен только массив с информацией о фото и то не всей, а только той, которая репрезентует информацию о его положении на сервере. Эту информацию предоставляют поля объекта "photo": "id", "secret", "server"

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

struct Photo {    let id: String    let secret: String    let server: String}let results: [Photos] = // ...

Вся остальная "мишура" на не нужна. Так вот материала, описывающего best practices обработки такого JSON-объекта очень мало.

Давайте разбираться. Сначала предположим, что никакой лишней информации нам не пришло. Таким образом я хочу ввести в курс дела тех, кто не работал с Codable. Представим, что наш объект выглядит вот так.

{   "id":"50972466107",   "owner":"191126281@N07",   "secret":"06f861f8b0"}

Здесь все предельно просто. Нужно создать структуру данных, имена свойств которой будут совпадать с ключами JSON-объекта (здесь это "id", "secret", "server"); типы свойств нашей структуры также обязаны удовлетворять типам, которым равны значения ключей (надеюсь, не запутал). Далее нужно просто подписаться на протокол Decodable, который сделает все за нас, потому что он умеет работать с вложенными типами (то есть если типы свойств являются его подписчиками, то и сам объект тоже будет по умолчанию на нее подписан). Что значит подписан? Это значит, что все методы смогут определиться со своей реализацией "по умолчанию". Далее процесс парсинга целиком. (Я декодирую из строки, которую предварительно перевожу в объект типа Data, потому что метод decode(...) объекта JSONDecoder работает с Data).

Полезные советы:

  • Используйте сервисы, чтобы тренироваться на простых примерах, например если вы не хотите работать не с чьим API - используйте сервис jsonplaceholder.typicode.com, он даст вам доступ к простейшим JSON-объектами, получаемым с помощью GET-запросов.

  • Также полезным на мой взгляд является сервис jsonformatter.curiousconcept.com . Он предоставляет доступ к функционал выравниванивания "кривых" REST объектов, которые обычно показывает нам консоль Playground Xcode.

  • Последний мощный tool - app.quicktype.io - он описывает структуру данных на Swift по конкретному JSON-объекту.

Вернемся к мучениям. Парсинг:

struct Photo: Decodable {    let id: String    let secret: String    let server: String}let json = """{   "id":"50972466107",   "owner":"191126281@N07",   "secret":"06f861f8b0"}"""let data = json.data(using: .utf8)let results: Photo = try! JSONDecoder().decode(Photo.self, from: data)

Обратите внимание на то, что любой ключ JSON-объекта, использующий следующую нотацию "key" : "sometexthere" для Decodable видится как видится как String, поэтому такой код создаст ошибку в run-time. Decodable не умеет явно coerce-ить (приводить типы).

struct Photo: Decodable {    let id: Int    let secret: String    let server: Int}let json = """{   "id":"50972466107",   "owner":"191126281@N07",   "secret":"06f861f8b0"}"""let data = json.data(using: .utf8)let results: Photo = try! JSONDecoder().decode(Photo.self, from: data)

Усложним задачу. А что если нам пришел такой объект?

    {       "id":"50972466107",       "owner":"191126281@N07",       "secret":"06f861f8b0",       "server":"65535",       "farm":66,       "title":"Prompt & Reliable Electrolux Oven Repairs in Gold Coast",       "ispublic":1,       "isfriend":0,       "isfamily":0    }

Здесь все элементарно, потому что Decodable умный протокол, который умеет парсить только те свойства, которые описывает наша структура и не "ругаться " на то, что некоторые свойства отсутствуют. Это логично, потому что хоть API и должно являться устойчивым, по мнению последователей "Чистого архитектора" Роберта Мартина, но никто не гарантирует нам то, что его разработчики баз данных не захотят, например, внести новых свойств. Если бы это не работало - наши приложения бы постоянно "крашились".

Разберем дополнительный функционал доступный из коробки. Следующий вид JSON-объекта:

[    {       "id":"50972466107",       "owner":"191126281@N07",       "secret":"06f861f8b0",       "server":"65535",       "farm":66,       "title":"Prompt & Reliable Electrolux Oven Repairs in Gold Coast",       "ispublic":1,       "isfriend":0,       "isfamily":0    },    {       "id":"50970556873",       "owner":"49965961@N00",       "secret":"21f7a6524b",       "server":"65535",       "farm":66,       "title":"IMG_20210222_145514",       "ispublic":1,       "isfriend":0,       "isfamily":0    }]

Тут придется добавить всего несколько логичных строк (или даже символов) кода. Помним, что массив объектов конкретного типа - это тоже тип!

struct Photo: Decodable {    let id: String    let secret: String    let server: String}let json = """[    {       "id":"50972466107",       "owner":"191126281@N07",       "secret":"06f861f8b0",       "server":"65535",       "farm":66,       "title":"Prompt & Reliable Electrolux Oven Repairs in Gold Coast",       "ispublic":1,       "isfriend":0,       "isfamily":0    },    {       "id":"50970556873",       "owner":"49965961@N00",       "secret":"21f7a6524b",       "server":"65535",       "farm":66,       "title":"IMG_20210222_145514",       "ispublic":1,       "isfriend":0,       "isfamily":0    }]"""let data = json.data(using: .utf8)let results: [Photo] = try! JSONDecoder().decode([Photo].self, from: data)

Добавление квадратных скобок к имени типа все сделало за нас, а все потому что [Photo] - это конкретный тип в нотации Swift. На что можно напороться с массивами: массив из одного объекта - тоже массив!

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

Сначала залезем немного "под капот" Decodable и еще раз поймем, что такое JSON.

  • JSON-объект - это текст, удовлетворяющий заданной над ним формальной грамматике. Формальная грамматика - набор правил, задающих будущий вид объектов. Формальная грамматика однозначно может определить, является ли текст JSON-объектом или нет! Придуман он для того, чтобы обеспечить кросс-платформенность, потому что такой элементарный тип как Character есть в любом языке, а значит можно его использовать для идентификации содержимого JSON-объекта и дальнейшего приведения к клиентским структурам данных.

  • JSON - ОО текстовый формат. Что же это значит? То, что он общается с программистами на уровне объектов в привычном понимании ООП, только объекты эти утрированные: методы с помощью JSON мы передать не можем (можем, но зачем). Что все это значит? Это значит то, что любая открывающая фигурная скобка открывает новый контейнер (область видимости)

  • Decodable может перемещаться по этим контейнерам и забирать оттуда только необходимую нам информацию.

Сначала поймем, каким инструментарием необходимо пользоваться. Протокол Decodable определяет generic enum CodingKeys, который сообщает парсеру (декодеру) то, как именно необходимо соотносить ключ JSON объекта с названием свойства нашей структуры данных, то есть это перечисление является ключевым для декодера объектом, с помощью него он понимает, в какое именно свойство клиентской структуры присваивать следующее значение ключа! Для чего может быть полезна перегрузка этого перечисления в топорных условиях, я думаю всем ясно. Например, для того, чтобы соблюсти стиль кодирования при парсинге: JSON-объект использует snake case для имен ключей, а Swift поощряет camel case. Как это работает?

struct Photo: Decodable {    let idInJSON: String    let secretInJSON: String    let serverInJSON: String        enum CodingKeys: String, CodingKey {        case idInJSON = "id_in_JSON"        case secretInJSON = "secret_in_JSON"        case serverInJSON = "server_in_JSON"    }}

rawValue перечисления CodingKeys говорят, как выглядят имена наших свойств в JSON-документе!

Отсюда мы и начнем наше путешествие внутрь контейнера! Еще раз посмотрим на JSON который нужно было изначально декодировать!

{   "photos":{      "page":1,      "pages":"11824",      "perpage":2,      "total":"23648",      "photo":[         {            "id":"50972466107",            "owner":"191126281@N@7" ,            "secret":"Q6f861f8b0",            "server":"65535",            "farm":66,            "title":"Prompt & Reliable Electrolux Oven Repairs in Gold Coast",            "ispublic":1,            "isfriend":0,            "isfamily":0         },         {            "id":"50970556873",            "owner":"49965961@NG0",            "secret":"21f7a6424b",            "server":"65535",            "farm" 66,            "title":"IMG_20210222_145514",            "ispublic":1,            "isfriend":0,            "isfamily":0         }      ]   },   "stat":"ok"}

Опишем контейнеры:

  • Первый контейнер определяет объект, состоящий из двух свойств: "photos", "stat"

  • Контейнер "photos" в свою очередь определяет объект состоящий из пяти свойств: "page", "pages", "perpages", "total", "photo"

  • "photo" - просто массив контейнеров, некоторый свойства из которых нам нужны.

Как можно решать задачу в лоб?

  • Объявить кучу вложенных типов и радоваться жизни. Итог пишем dummy алгоритмы ручного приведение между уже клиентскими объектами. Это нехорошо!

  • Пользоваться функционалом Decodable протокола, а точнее его перегружать дефолтную реализацию инициализатора и переопределять CodingKeys! Это хорошо! Оговорка: к сожалению Swift (по понятным причинам!) не дает определять в extension stored properties, а computed properties не видны Сodable/Encodable/Decodable, поэтому красиво работать с чистыми JSON массивами не получится.

Решая вторым способом, мы прокладываем для декодера маршрут к тем данным, которые нам нужны: говорим ему зайди в контейнера photos и забери массив из свойства photo c выбранными нами свойствами

Сразу приведу код этого решения и уже потом объясню, как он работает!

// (1) Определили объект Photo только с необходимыми к извлечению свойствами.struct Photo: Decodable {    let id: String    let secret: String    let server: String}// (2) Определяем JSONContainer, то есть описываем куда нужно идти парсеру и что забирать.struct JSONContainer: Decodable {    // (3) photos совпадает c именем ключа "photos" в JSON, но теперь мы написали, что хранить этот ключ будет не весь контейнер, а только его часть - массив, который является значением ключа photo!    let photos: [Photo]}extension JSONContainer {    // (4) Описываем CodingKeys для парсера.    enum CodingKeys: String, CodingKey {        case photos        // (5) Здесь определяем только те имена ключей, которые будут нужны нам внутри контейнера photos.        // (6) Здесь необязательно соблюдать какие-то правила именования, но название PhotosKeys - дает представление о том, что мы рассматриваем ключи внутри значения ключа photos        enum PhotosKeys: String, CodingKey {            // (7) Описываем конкретно интересующий нас ключ "photo"            case photoKey = "photo"        }    }    // (8) Дальше переопределяем инициализатор    init(from decoder: Decoder) throws {        // (9) Заходим внутрь JSON, который определяется контейнером из двух ключей, но нам из них нужно только одно - photos        let container = try decoder.container(keyedBy: CodingKeys.self)        // (10) Заходим в контейнер (nested - вложенный) ключа photos и говорим какие именно ключи смы будем там рассматривать        let photosContainer = try container.nestedContainer(keyedBy: CodingKeys.PhotosKeys.self, forKey: .photos)        // (11) Декодируем уже стандартным методом        // (12) Дословно здесь написано следующее положи в свойство photos объект-массив, который определен своим типом и лежит .photoKey (.photoKey.rawValue == "photo")        photos = try photosContainer.decode([Photo].self, forKey: .photoKey)    }}

Вот и все, теперь когда экземпляр объекта JSONDecoder. Будет вызывать decode() - под капотом он будет использовать наш инициализатор для работы с декодированием

Подводя итоги, хотелось бы сказать, что работа с сетью в iOS-разработке наполнена разнообразными "сюрпризами", поэтому если вы можете что-то добавить в комментариях - обязательно делайте это!

Всем спасибо!

P.S. По прошествии некоторого времени был сделан вывод, что мапить к итоговой структуре в коде, пользуясь только встроенным поведением Codable, нормально. Вывод был сделан после просмотра сессии WWDC, разбирающей работу с данными пришедшими из сети

Ссылка на сессию

Подробнее..

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

31.07.2020 12:10:12 | Автор: admin

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

Подробнее..

Категории

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

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru