使用NLTK通过BeautifulSoup和NaiveBayes对网站内容问题进行文档分类

时间:2014-12-05 15:21:00

标签: python nlp classification nltk document-classification

我有一个Python 2.7项目,我想根据内容对网站进行分类。我有一个数据库,其中有许多网站URL及其相关类别。有许多类别(=标签),我希望根据新内容的内容将新网站分类到相应的类别中。我一直在关注列出here的NLTK分类教程/示例,但遇到了一些我无法解释的问题。

以下是我使用的流程概述:

  1. 使用MySQLdb检索与给定网站URL关联的类别。    这将在从URL中提取数据(内容)以与其配对时使用    网站的类别(=标签)。
  2. 使用getSiteContent(站点)功能从网站中提取内容
  3. 以上功能如下:

    def getSiteContent(site):
        try:
            response = urllib2.urlopen(site, timeout = 1)
            htmlSource = response.read()
        except Exception as e: # <=== some websites may be inaccessible as list isn't up-to-date
            global errors
            errors += 1
            return ''
    
        soup = BeautifulSoup(htmlSource)
        for script in soup.find_all('script'):
            script.extract()
    
        commonWords = set(stopwords.words('english'))
        commonWords.update(['function', 'document', 'window', 'functions',     'getElementsByTagName', 'parentNode', 'getDocumentById', 'javascript', 'createElement',     'Copyright', 'Copyrights', 'respective', 'owners', 'Contact Us', 'Mobile Version', 'FAQ',     'Privacy Policy', 'Terms of Service', 'Legal Disclaimer' ])
    
        text = soup.get_text()
    
        # Remove ',', '/', '%', ':'
        re.sub(r'(\\d+[,/%:]?\\d*)', '', text)
        # Remove digits
        re.sub(r'\d+', '', text)
        # Remove non-ASCII
        re.sub(r'[^\x00-\x7F]',' ', text)
        # Remove stopwords
        for word in commonWords :
            text = text.replace(' '+word+' ', ' ')
    
        # Tokenize the site content using NLTK
        tokens = word_tokenize(text)
    
        # We collect some word statistics, i.e. how many times a given word appears in the     text
        counts = defaultdict(int)
        for token in tokens:
            counts[token] += 1
    
        features = {}
        # Get rid of words that appear less than 3 times
        for word in tokens:
            if counts[word] >= 3 :
                features['count(%s)' % word] = counts[word]
    
        return features 
    

    完成上述所有操作后,我会执行以下操作:

    train = getTrainingSet(n)
    random.shuffle(train)
    

    其中n是我希望训练模型的网站数量。

    之后,我做了:

    feature_set = []
    count = 0
    for (site, category) in train:
        result = getSiteContent(site)
        count += 1
        if result != '':
            print "%d. Got content for %s" % (count, site)
            feature_set.append((result, category))
        else  :
            print "%d. Failed to get content for %s" % (count, site)
    

    打印语句目前主要用于调试目的。完成上述操作后,feature_set包含类似于以下内容的内容:

    print feature_set
    [({u'count(import)': 22, u'count(maxim)': 22, u'count(Maxim)': 5, u'count(css)': 22, u'count(//www)': 22, u'count(;)': 22, u'count(url)': 22, u'count(Gift)': 3, u"count('')": 44, u'count(http)': 22, u'count(&)': 3, u'count(ng16ub)': 22, u'count(STYLEThe)': 3, u'count(com/modules/system/system)': 4, u'count(@)': 22, u'count(?)': 22}, 'Arts & Entertainment'), ({u'count(import)': 3, u'count(css)': 3, u'count(\u05d4\u05d9\u05d5\u05dd)': 4, u'count(\u05de\u05d9\u05dc\u05d5\u05df)': 6, u'count(;)': 3, u'count(\u05e2\u05d1\u05e8\u05d9)': 4, u'count(\u05d0\u05ea)': 3, u'count(\u05de\u05d5\u05e8\u05e4\u05d9\u05e7\u05e1)': 6, u"count('')": 6, u'count(\u05d4\u05d5\u05d0)': 3, u'count(\u05e8\u05d1\u05de\u05d9\u05dc\u05d9\u05dd)': 3, u'count(ver=01122014_4)': 3, u'count(|)': 4, u'count(``)': 4, u'count(@)': 3, u'count(?)': 7}, 'Miscellaneous')]
    

    之后,我尝试训练我的分类器,然后根据我从feature_set

    中提取的测试数据运行它
    train_set, test_set = feature_set[len(train)/2:], feature_set[:len(train)/2]
    print "Num in train_set: %d" % len(train_set)
    print "Num in test_set: %d" % len(test_set)
    classifier = nltk.NaiveBayesClassifier.train(train_set) # <=== classified declared on train_set
    print classifier.show_most_informative_features(5)
    print "=== Classifying a site ==="
    print classifier.classify(getSiteContent("http://www.mangaspoiler.com"))
    print "Non-working sites: %d" % errors
    print "Classifier accuracy: %d" % nltk.classify.accuracy(classifier, test_set)
    

    这正是NLTK文档网站上的教程如何做到的。但是,结果如下(给出一组100个网站):

    $ python classify.py
    Num in train_set: 23
    Num in test_set: 50
    Most Informative Features
                count(Pizza) = None           Arts & : Techno =      1.0 : 1.0
    None
    === Classifying a site ===
    Technology & Computing
    Non-working sites: 27
    Classifier accuracy: 0
    

    现在,显然有一些问题:

    1. 单词标记包含unicode字符,例如\u05e2\u05d1\u05e8\u05d9,因为删除它们的正则表达式只有在它们是独立的情况下才有效。这是一个小问题。

    2. 更大的问题是,即使我print feature_set,单词标记也显示为u'count(...)' = #而不是'count(...)' = #。我认为这可能是一个更大的问题,也是我的分类器失败的部分原因。

    3. 显然,分类器在某种程度上是灾难性的失败。即使我将整个数据集提供给分类器,精度也会列为0,这似乎极不可能。

    4. Most Informative Features函数表示count(Pizza) = None。但是,我声明defaultdict(int)的代码要求每个条目都与文本中的出现次数相关联。


    5. 为什么会发生这种情况,我感到很遗憾。据我所知,我的数据结构与NLTK文档在我在本问题顶部链接的网站上的教程中使用的数据相同。如果任何曾与NLTK合作的人之前已经看过这种行为,我会非常感谢有关我可能做错的任何提示。

1 个答案:

答案 0 :(得分:2)

这里可能有很多错误,但第一个也是最明显的错误在这里突出:

  

即使我将整个数据集输入分类器

,精度也会列为0

它未列为0.0?听起来像是float int的{​​{1}}。我怀疑你在某个时刻正在进行划分以进行规范化,而int/int并未转换为float

在构建统计表时,请为每个计数添加1.0,而不是1。这将解决问题的根源,并且修正将逐渐减少。

如果使用浮点数计算文档似乎很奇怪,请将每个计数视为科学意义上的度量而不是离散文档的表示。