[PYTHON] Webseitenübersicht (Vorverarbeitung)

Vorwort

Dieser Artikel basiert auf ❷ von Sammlung und Klassifizierung von Informationen zum maschinellen Lernen (Konzept).

Automatische Klassifizierung von Webseiten, die durch Crawlen erfasst wurden, wie zuvor in Klassifizierung mehrerer Labels nach zufälligen Wäldern mit Scikit-Learn, zufälligen Wäldern und Es ist möglich, in einer lokalen Umgebung mit einem Algorithmus wie einem Entscheidungsbaum zu klassifizieren, andererseits jedoch mit Anwendungsbeispiel für Watson Natural Language Classifier von Ruby. Es ist auch möglich, Dienste in der Cloud zu nutzen.

Die Vorverarbeitung, wie unter Arten der Vorverarbeitung in der Verarbeitung natürlicher Sprache und ihre Leistungsfähigkeit beschrieben, ist sehr wichtig.

Wenn Sie jedoch einen Dienst in der Cloud verwenden möchten, um Webseiten automatisch zu klassifizieren, müssen Sie eine Vorverarbeitung durchführen, die in diesem Artikel nicht erwähnt wird.

Das ist die "Webseitenübersicht".

Im Allgemeinen ist die Datenmenge pro Datensatz für Dienste in der Cloud begrenzt. Beispiel für die Verwendung des Watson Natural Language Classifier von Ruby hat eine Beschränkung von 1024 Zeichen pro zu klassifizierendem Artikel. Schneiden Sie daher bis zu 1024 Byte vor dem Blog-Artikel aus. Ich habe versucht, es an den Dienst weiterzugeben. Dies ist auch eine rudimentäre „Zusammenfassung“.

Bei Webseiten gibt es keine Begrenzung für die Datenmenge. Daher müssen Möglichkeiten entwickelt werden, um die Datenmenge zu reduzieren, die an Dienste in der Cloud übergeben wird, ohne die für die Klassifizierung erforderlichen Informationen zu löschen.

Ich denke, die orthodoxste Technik dafür ist die "Zusammenfassung von Webseiten".

Allgemeine Zusammenfassungstechniken

Ich konnte nicht viele aktuelle Trends in der Zusammenfassungstechnologie finden, aber vor ungefähr drei Jahren Trends in der Forschung zur automatischen Zusammenfassungstechnologie War hilfreich.

Verwendung von LexRank als konkrete Implementierung des Codes Ich werde Donald Trumps Rede mit dem automatischen Zusammenfassungsalgorithmus LexRank in drei Zeilen zusammenfassen ) Und Zusammenfassung des Produktwerts der EC-Site unter Verwendung des automatischen Zusammenfassungsalgorithmus LexRank werden auf Qiita veröffentlicht.

Diese Implementierungen fassen Sätze mit der Politik zusammen, Sätze als eine Menge von "Sätzen" zu behandeln, "Sätzen" Bedeutung zuzuweisen und aus den wichtigsten "Sätzen" in der Reihenfolge zu extrahieren.

Die Frage hier ist, wie man eine Webseite in "Sätze" unterteilt.

Diesbezüglich konnte ich den Implementierungscode nicht finden, selbst wenn ich das Netz durchsuchte, und die einzige Logik, die implementiert werden konnte, war ["HTML-Textteilung für die Zusammenfassung von Webseiten durch Extrahieren wichtiger Sätze"](http: / /harp.lib.hiroshima-u.ac.jp/hiroshima-cu/metadata/5532) war das Papier [^ 1].

Schließlich konnte ich den Implementierungscode nicht finden, und dieses Mal entschied ich mich, den Code [^ 2] mit der Logik dieses Dokuments zu implementieren.

Spezifische Logik

Die spezifische Logik besteht aus den folgenden drei Stufen.

(1) Teilen Sie das Ganze in Texteinheiten (Satzkandidaten) Die Pause ist unten

(2) Klassifizieren Sie Texteinheiten in Satzeinheiten und Nicht-Satzeinheiten Der Satz muss drei oder mehr der folgenden Bedingungen erfüllen --C1. Die Anzahl der unabhängigen Wörter beträgt 7 oder mehr --C2. Das Verhältnis der Anzahl unabhängiger Wörter zur Gesamtzahl der Wörter beträgt 0,64 oder weniger --C3. Das Verhältnis der Anzahl der angehängten Wörter zur Anzahl der unabhängigen Wörter beträgt 0,22 oder mehr [^ 3] --C4. Das Verhältnis der Anzahl der Hilfswörter zur Anzahl der unabhängigen Wörter beträgt 0,26 oder mehr. --C5. Das Verhältnis der Anzahl der Hilfsverben zur Anzahl der unabhängigen Wörter beträgt 0,06 oder mehr.

(3) Kombinieren und teilen Sie Nicht-Satz-Einheiten basierend auf der Anzahl unabhängiger Wörter

Der Einfachheit halber wird die Nicht-Anweisungseinheit dieses Mal nur verbunden und implementiert keine Aufteilung.

Der folgende Code wird auch implementiert, um nur den Hauptteil von HTML zu extrahieren, da davon ausgegangen wird, dass der Inhalt des title-Elements für den Dateinamen der heruntergeladenen HTML-Datei verwendet wird. Im Allgemeinen ist es eine gute Idee, den Inhalt des Titelelements auch in der Zusammenfassung zu verwenden.

Aufteilen von HTML in Texteinheiten und Konvertieren in einfachen Text

html2plaintext_part_1.py


import codecs
import re

class Article:

    #Versuchen Sie es mit Zeichencodes in dieser Reihenfolge
    encodings = [
        "utf-8",
        "cp932",
        "euc-jp",
        "iso-2022-jp",
        "latin_1"
    ]

    #Normaler Ausdruck für Elementextraktion auf Blockebene
    block_level_tags = re.compile("(?i)</?(" + "|".join([
        "address", "blockquote", "center", "dir", "div", "dl",
        "fieldset", "form", "h[1-6]", "hr", "isindex", "menu",
        "noframes", "noscript", "ol", "pre", "p", "table", "ul",
        "dd", "dt", "frameset", "li", "tbody", "td", "tfoot",
        "th", "thead", "tr"
        ]) + ")(>|[^a-z].*?>)")

    def __init__(self,path):
        print(path)
        self.path = path
        self.contents = self.get_contents()

    def get_contents(self):
        for encoding in self.encodings:
            try:
                lines = ' '.join([line.rstrip('\r\n') for line in codecs.open(self.path, 'r', encoding)])
                parts = re.split("(?i)<(?:body|frame).*?>", lines, 1)
                if len(parts) == 2:
                    head, body = parts
                else:
                    print('Cannot split ' + self.path)
                    body = lines
                body = re.sub(r"(?i)<(script|style|select).*?>.*?</\1\s*>"," ", body)
                body = re.sub(self.block_level_tags, ' _BLOCK_LEVEL_TAG_ ', body)
                body = re.sub(r"(?i)<a\s.+?>",' _ANCHOR_LEFT_TAG_ ', body)
                body = re.sub("(?i)</a>",' _ANCHOR_RIGHT_TAG_ ', body)
                body = re.sub("(?i)<[/a-z].*?>", " ", body)
                blocks = []
                for block in body.split("_BLOCK_LEVEL_TAG_"):
                    units = []
                    for unit in block.split("。"):
                        unit = re.sub("_ANCHOR_LEFT_TAG_ +_ANCHOR_RIGHT_TAG_", " ", unit) #Schließen Sie Links zu Bildern aus
                        if not re.match(r"^ *$", unit):
                            for fragment in re.split("((?:_ANCHOR_LEFT_TAG_ .+?_ANCHOR_LEFT_TAG_ ){2,})", unit):
                                fragment = re.sub("_ANCHOR_(LEFT|RIGHT)_TAG_", ' ', fragment)
                                if not re.match(r"^ *$", fragment):
                                    if TextUnit(fragment).is_sentence():
                                        #Anweisungseinheiten enden mit "."
                                        if len(units) > 0 and units[-1] == '―':
                                            units.append('。\n')
                                        units.append(fragment)
                                        units.append(' 。\n')
                                    else:
                                        #Einheiten ohne Satz enden mit "-".
                                        # (Zwang)Im Gegensatz zum Papier werden Nicht-Satz-Einheiten nur kombiniert und nicht geteilt.
                                        units.append(fragment)
                                        units.append('―')
                    if len(units) > 0 and units[-1] == '―':
                       units.append('。\n')
                    blocks += units
                return re.sub(" +", " ", "".join(blocks))
            except UnicodeDecodeError:
                continue
        print('Cannot detect encoding of ' + self.path)
        return None

Unterscheiden Sie zwischen Anweisungseinheiten und Nicht-Anweisungseinheiten

html2plaintext_part_2.py


from janome.tokenizer import Tokenizer
from collections import defaultdict
import mojimoji
#import re

class TextUnit:

    tokenizer = Tokenizer("user_dic.csv", udic_type="simpledic", udic_enc="utf8")

    def __init__(self,fragment):
        self.fragment   = fragment
        self.categories = defaultdict(int)
        for token in self.tokenizer.tokenize(self.preprocess(self.fragment)):
            self.categories[self.categorize(token.part_of_speech)] += 1

    def categorize(self,part_of_speech):
        if re.match("^Substantiv,(Allgemeines|代Substantiv|固有Substantiv|Verbindung ändern|[^,]+Stengel)", part_of_speech):
            return 'Unabhängigkeit'
        if re.match("^Verb", part_of_speech) and not re.match("Sa seltsam", part_of_speech):
            return 'Unabhängigkeit'
        if re.match("^Adjektiv,Unabhängigkeit", part_of_speech):
            return 'Unabhängigkeit'
        if re.match("^Partikel", part_of_speech):
            return 'Partikel'
        if re.match("^Hilfsverb", part_of_speech):
            return 'Hilfsverb'
        return 'Andere'

    def is_sentence(self):
        if self.categories['Unabhängigkeit'] == 0:
            return False
        match = 0
        if self.categories['Unabhängigkeit'] >= 7:
            match += 1
        if 100 * self.categories['Unabhängigkeit'] / sum(self.categories.values()) <= 64:
            match += 1
        if 100 * (self.categories['Partikel'] + self.categories['Hilfsverb']) / self.categories['Unabhängigkeit'] >= 22:
            #Interpretiert als "angehängtes Wort = Hilfsverb ⋃ Hilfsverb" gemäß dem Papier(Abweichend von der üblichen Definition)
            match += 1
        if 100 * self.categories['Partikel'] / self.categories['Unabhängigkeit'] >= 26:
            match += 1
        if 100 * self.categories['Hilfsverb'] / self.categories['Unabhängigkeit'] >= 6:
            match += 1
        return match >= 3

    def preprocess(self, text):
        text = re.sub("&[^;]+;",  " ", text)
        text = mojimoji.han_to_zen(text, digit=False)
        text = re.sub('(\t | )+', " ", text)
        return text

Konvertieren Sie eine HTML-Datei in eine reine Textdatei

html2plaintext_part_3.py


if __name__ == '__main__':
    import glob
    import os

    path_pattern = ''/home/samba/example/links/bookmarks.crawled/**/*.html'
    # The converted plaintext is put as '/home/samba/example/links/bookmarks.plaintext/**/*.txt'
    for path in glob.glob(path_pattern, recursive=True):
        article = Article(path)
        plaintext_path = re.sub("(?i)html?$", "txt", path.replace('.crawled', '.plaintext'))
        plaintext_dir  = re.sub("/[^/]+$", "", plaintext_path)
        if not os.path.exists(plaintext_dir):
            os.makedirs(plaintext_dir)
        with open(plaintext_path, 'w') as f:
            f.write(article.contents)

Ich bin mit Python nicht vertraut, daher denke ich, dass es ein ungeschickter Code ist, aber ich würde es begrüßen, wenn Sie auf Verbesserungen hinweisen könnten.

Nur-Text-Zusammenfassung

Der auf diese Weise erzeugte Klartext sollte mit LexRank zusammengefasst werden.

In diesem Beispiel wurde janome der Einfachheit halber für die morphologische Analyse verwendet. Da jedoch "HTML-> Klartext" und "Klartext-> Zusammenfassung" separate Schritte haben, ist "Klartext-> Zusammenfassung" völlig unterschiedlich. Sie können auch das morphologische Analysewerkzeug von verwenden.

[^ 1]: Die ursprüngliche Forschung war von 2002 bis 2004, und möglicherweise wurde kürzlich eine effizientere Logik vorgeschlagen.

Recommended Posts

Webseitenübersicht (Vorverarbeitung)
Flask-Python-Realisierung
Kolbenartikel-Zusammenfassungsseite
Zusammenfassung des Python-Webprogrammierartikels
[Persönlicher Hinweis] Scraping von Webseiten in Python3
Überwachen Sie Webseitenaktualisierungen mit LINE BOT
WEB-Scraping mit BeautifulSoup4 (Seriennummernseite)