NLP 项目:维基百科文章爬虫和分类 - 语料库阅读器

塞巴斯蒂安

一、说明

        自然语言处理是机器学习和人工智能的一个迷人领域。这篇博客文章启动了一个具体的 NLP 项目,涉及使用维基百科文章进行聚类、分类和知识提取。灵感和一般方法源自《Applied Text Analysis with Python》一书。

        在接下来的文章中,我将展示如何实现维基百科文章爬虫,如何将文章收集到语料库中,如何应用文本预处理、标记化、编码和矢量化,以及最后应用机器学习算法进行聚类和分类。

        本文的技术背景是Python v3.11和几个附加库,其中最重要的nltk v3.8.1是 和wikipedia-api v0.6.0。所有示例也应该适用于较新的版本。

        本文最初出现在我的博客admantium.com上。

二、项目概要

        该项目的目标是下载、处理和应用维基百科文章上的机器学习算法。首先,下载并存储来自维基百科的选定文章。其次,生成一个语料库,即所有文本文档的总和。第三,对每个文档文本进行预处理,例如通过删除停用词和符号,然后进行标记化。第四,将标记化文本转换为向量以接收数字表示。最后,应用不同的机器学习算法。

        在第一篇文章中,解释了步骤一和步骤二。

三、先决条件

我喜欢在Jupyter Notebook中工作并使用优秀的依赖管理器Poetry。在您选择的项目文件夹中运行以下命令以安装所有必需的依赖项并在浏览器中启动 Jupyter 笔记本。

# Complete the interactive project creation
poetry init# Add core dependencies
poetry add nltk@^3.8.1 jupyterlab@^4.0.0 scikit-learn@^1.2.2 wikipedia-api@^0.5.8 matplotlib@^3.7.1 numpy@^1.24.3 pandas@^2.0.1# Add NLTK dependencies
python3 -c "import nltk; \nltk.download('punkt'); \nltk.download('averaged_perceptron_tagger'); \nltk.download('reuters'); \nltk.download('stopwords');"# Start jupyterhub
poetry run jupyterlab

浏览器中应该会打开一个新的 Jupyter Notebook。

四、Python 库

在这篇博文中,将使用以下 Python 库:

维基百科-API:

  • Page代表维基百科文章及其标题、文本、类别和相关页面的对象。

NLTK

  • PlaintextCorpusReader用于提供对文档的访问、提供标记化方法并计算有关所有文件的统计信息的可遍历对象
  • sent_tokenizerword_tokenizer用于生成令牌

五、(第 1 部分)维基百科文章爬虫

        该项目从创建自定义维基百科爬虫开始。尽管我们可以使用来自各种来源的维基百科语料库数据集(例如 NLTK 中的内置语料库),但自定义爬虫提供了对文件格式、内容和内容现实的最佳控制。

        下载和处理原始 HTML 可能非常耗时,尤其是当我们还需要从中确定相关链接和类别时。一个非常方便的图书馆可以帮助您。wikipedia -api为我们完成了所有这些繁重的工作。在此基础上,我们逐步开发核心功能。

        首先,我们创建一个基类,定义它自己的 Wikipedia 对象并确定存储文章的位置。

import os
import re
import wikipediaapi as wiki_apiclass WikipediaReader():def __init__(self, dir = "articles"):self.pages = set()self.article_path = os.path.join("./", dir)self.wiki = wiki_api.Wikipedia(language = 'en',extract_format=wiki_api.ExtractFormat.WIKI)try:os.mkdir(self.article_path)except Exception as e:pass

        这还定义了pages爬虫访问的一组页面对象。该page对象非常有用,因为它可以访问文章标题、文本、类别和其他页面的链接。

        其次,我们需要接收文章名称的辅助方法,如果存在,它将page向集合中添加一个新对象。我们需要将调用包装在一个try except块中,因为某些包含特殊字符的文章无法正确处理,例如Add article 699/1000 Tomasz Imieliński. 此外,还有一些我们不需要存储的元文章。

def add_article(self, article):try:page = self.wiki.page(self._get_page_title(article))if page.exists():self.pages.add(page)return(page)except Exception as e:print(e)

第三,我们要提取一篇文章的类别。每篇维基百科文章都在页面底部的两个可见部分(请参阅以下屏幕截图)以及未呈现为 HTML 的元数据中定义类别。因此,最初的类别列表可能听起来令人困惑。看一下这个例子:

wr = WikipediaReader()
wr.add_article("Machine Learning")
ml = wr.list().pop()print(ml.categories)
# {'Category:All articles with unsourced statements': Category:All articles with unsourced statements (id: ??, ns: 14),
#  'Category:Articles with GND identifiers': Category:Articles with GND identifiers (id: ??, ns: 14),
#  'Category:Articles with J9U identifiers': Category:Articles with J9U identifiers (id: ??, ns: 14),
#  'Category:Articles with LCCN identifiers': Category:Articles with LCCN identifiers (id: ??, ns: 14),
#  'Category:Articles with NDL identifiers': Category:Articles with NDL identifiers (id: ??, ns: 14),
#  'Category:Articles with NKC identifiers': Category:Articles with NKC identifiers (id: ??, ns: 14),
#  'Category:Articles with short description': Category:Articles with short description (id: ??, ns: 14),
#  'Category:Articles with unsourced statements from May 2022': Category:Articles with unsourced statements from May 2022 (id: ??, ns: 14),
#  'Category:Commons category link from Wikidata': Category:Commons category link from Wikidata (id: ??, ns: 14),
#  'Category:Cybernetics': Category:Cybernetics (id: ??, ns: 14),
#  'Category:Learning': Category:Learning (id: ??, ns: 14),
#  'Category:Machine learning': Category:Machine learning (id: ??, ns: 14),
#  'Category:Short description is different from Wikidata': Category:Short description is different from Wikidata (id: ??, ns: 14),
#  'Category:Webarchive template wayback links': Category:Webarchive template wayback links (id: ??, ns: 14)}

因此,我们根本不通过应用多个正则表达式过滤器来存储这些特殊类别。

def get_categories(self, title):page = self.add_article(title)if page:if (list(page.categories.keys())) and (len(list(page.categories.keys())) > 0):categories = [c.replace('Category:','').lower() for c in list(page.categories.keys())if c.lower().find('articles') == -1and c.lower().find('pages') == -1and c.lower().find('wikipedia') == -1and c.lower().find('cs1') == -1and c.lower().find('webarchive') == -1and c.lower().find('dmy dates') == -1and c.lower().find('short description') == -1and c.lower().find('commons category') == -1]return dict.fromkeys(categories, 1)return {}

第四,我们现在定义抓取方法。这是一种可定制的广度优先搜索,从一篇文章开始,获取所有相关页面,将这些页面广告到页面对象,然后再次处理它们,直到文章总数耗尽或达到深度级别。说实话:我只用它爬过 1000 篇文章。


def crawl_pages(self, article, depth = 3, total_number = 1000):print(f'Crawl {total_number} :: {article}')page = self.add_article(article)childs = set()if page:for child in page.links.keys():if len(self.pages) < total_number:print(f'Add article {len(self.pages)}/{total_number} {child}')self.add_article(child)childs.add(child)depth -= 1if depth > 0:for child in sorted(childs):if len(self.pages) < total_number:self.crawl_pages(child, depth, len(self.pages))

让我们开始爬取机器学习文章:

reader = WikipediaReader()
reader.crawl_pages("Machine Learning")print(reader.list())
# Crawl 1000 :: Machine Learning
# Add article 1/1000 AAAI Conference on Artificial Intelligence
# Add article 2/1000 ACM Computing Classification System
# Add article 3/1000 ACM Computing Surveys
# Add article 4/1000 ADALINE
# Add article 5/1000 AI boom
# Add article 6/1000 AI control problem
# Add article 7/1000 AI safety
# Add article 8/1000 AI takeover
# Add article 9/1000 AI winter

        最后,当一组page对象可用时,我们提取它们的文本内容并将它们存储在文件中,其中文件名代表其标题的清理版本。需要注意的是:文件名需要保留其文章名称的投降,否则我们无法再次获取页面对象,因为使用小写文章名称的搜索不会返回结果。

def process(self, update=False):for page in self.pages:filename = re.sub('\s+', '_', f'{page.title}')filename = re.sub(r'[\(\):]','', filename)file_path = os.path.join(self.article_path, f'{filename}.txt')if update or not os.path.exists(file_path):print(f'Downloading {page.title} ...')content = page.textwith open(file_path, 'w') as file:file.write(content)else:print(f'Not updating {page.title} ...')

这是该类的完整源代码WikipediaReader

import os
import re
import wikipediaapi as wiki_apiclass WikipediaReader():def __init__(self, dir = "articles"):self.pages = set()self.article_path = os.path.join("./", dir)self.wiki = wiki_api.Wikipedia(language = 'en',extract_format=wiki_api.ExtractFormat.WIKI)try:os.mkdir(self.article_path)except Exception as e:passdef _get_page_title(self, article):return re.sub(r'\s+','_', article)def add_article(self, article):try:page = self.wiki.page(self._get_page_title(article))if page.exists():self.pages.add(page)return(page)except Exception as e:print(e)def list(self):return self.pagesdef process(self, update=False):for page in self.pages:filename = re.sub('\s+', '_', f'{page.title}')filename = re.sub(r'[\(\):]','', filename)file_path = os.path.join(self.article_path, f'{filename}.txt')if update or not os.path.exists(file_path):print(f'Downloading {page.title} ...')content = page.textwith open(file_path, 'w') as file:file.write(content)else:print(f'Not updating {page.title} ...')def crawl_pages(self, article, depth = 3, total_number = 1000):print(f'Crawl {total_number} :: {article}')page = self.add_article(article)childs = set()if page:for child in page.links.keys():if len(self.pages) < total_number:print(f'Add article {len(self.pages)}/{total_number} {child}')self.add_article(child)childs.add(child)depth -= 1if depth > 0:for child in sorted(childs):if len(self.pages) < total_number:self.crawl_pages(child, depth, len(self.pages))def get_categories(self, title):page = self.add_article(title)if page:if (list(page.categories.keys())) and (len(list(page.categories.keys())) > 0):categories = [c.replace('Category:','').lower() for c in list(page.categories.keys())if c.lower().find('articles') == -1and c.lower().find('pages') == -1and c.lower().find('wikipedia') == -1and c.lower().find('cs1') == -1and c.lower().find('webarchive') == -1and c.lower().find('dmy dates') == -1and c.lower().find('short description') == -1and c.lower().find('commons category') == -1]return dict.fromkeys(categories, 1)return {}

        让我们使用维基百科爬虫来下载与机器学习相关的文章。

reader = WikipediaReader()
reader.crawl_pages("Machine Learning")print(reader.list())
# Downloading The Register ...
# Not updating Bank ...
# Not updating Boosting (machine learning) ...
# Not updating Ian Goodfellow ...
# Downloading Statistical model ...
# Not updating Self-driving car ...
# Not updating Behaviorism ...
# Not updating Statistical classification ...
# Downloading Search algorithm ...
# Downloading Support vector machine ...
# Not updating Deep learning speech synthesis ...
# Not updating Expert system ...

六、(第 2 部分)维基百科语料库

        所有文章均以文本文件形式下载到article文件夹中。为了提供所有这些单独文件的抽象,NLTK 库提供了不同的语料库阅读器对象。该对象不仅提供对单个文件的快速访问,还可以生成统计信息,例如词汇量、单个标记的总数或单词量最多的文档。

        让我们使用该类PlaintextCorpusReader作为起点,然后初始化它,使其指向文章:

import nltk
from  nltk.corpus.reader.plaintext import PlaintextCorpusReader
from time import timeclass WikipediaCorpus(PlaintextCorpusReader):passcorpus = WikipediaCorpus('articles', r'[^\.ipynb].*', cat_pattern=r'[.*]')
print(corpus.fileids())
# ['2001_A_Space_Odyssey.txt',
#  '2001_A_Space_Odyssey_film.txt',
#  '2001_A_Space_Odyssey_novel.txt',
#  '3D_optical_data_storage.txt',
#  'A*_search_algorithm.txt',
#  'A.I._Artificial_Intelligence.txt',
#  'AAAI_Conference_on_Artificial_Intelligence.txt',
#  'ACM_Computing_Classification_System.txt',

        好的,这已经足够好了。让我们用两种方法来扩展它来计算词汇量和最大单词数。对于词汇,我们将使用 NLTK 辅助类FreqDist,它是一个包含所有单词出现的字典对象,此方法使用简单辅助类消耗所有文本corpus.words(),从中删除非文本和非数字。

def vocab(self):return nltk.FreqDist(re.sub('[^A-Za-z0-9,;\.]+', ' ', word).lower() for word in corpus.words())

        为了得到最大单词数,我们遍历所有带有 的文档fileids(),然后确定 的长度words(doc),并记录最高值

def max_words(self):max = 0for doc in self.fileids():l = len(self.words(doc))max = l if l > max else maxreturn max

        最后,我们添加一个describe生成统计信息的方法(这个想法也源于上面提到的《Applied Text Analysis with Python》一书)。

        该方法启动一个计时器来记录校园处理持续了多长时间,然后使用语料库阅读器对象的内置方法和刚刚创建的方法来计算文件数、段落数、句子数、单词数、词汇量和文档中的最大字数。

def describe(self, fileids=None, categories=None):started = time()return {'files': len(self.fileids()),'paras': len(self.paras()),'sents': len(self.sents()),'words': len(self.words()),'vocab': len(self.vocab()),'max_words': self.max_words(),'time': time()-started}pass

        这是最后一WikipediaCorpus堂课:

import nltk
from  nltk.corpus.reader.plaintext import PlaintextCorpusReader
from time import timeclass WikipediaCorpus(PlaintextCorpusReader):def vocab(self):return nltk.FreqDist(re.sub('[^A-Za-z0-9,;\.]+', ' ', word).lower() for word in corpus.words())def max_words(self):max = 0for doc in self.fileids():l = len(self.words(doc))max = l if l > max else maxreturn maxdef describe(self, fileids=None, categories=None):started = time()return {'files': len(self.fileids()),'paras': len(self.paras()),'sents': len(self.sents()),'words': len(self.words()),'vocab': len(self.vocab()),'max_words': self.max_words(),'time': time()-started}pass

        在撰写本文时,爬取维基百科有关人工智能和机器学习的文章后,可以获得以下统计数据:

corpus = WikipediaCorpus('articles', r'[^\.ipynb].*', cat_pattern=r'[.*]')corpus.describe()
{'files': 1163,'paras': 96049,'sents': 238961,'words': 4665118,'vocab': 92367,'max_words': 46528,'time': 32.60307598114014}

七、结论

        本文是 NLP 项目在维基百科文章上下载、处理和应用机器学习算法的起点。本文涵盖了两个方面。首先,创建WikipediaReader通过名称查找文章的类,并可以提取其标题、内容、类别和提到的链接。爬虫由两个变量控制:爬取的文章总数和爬取的深度。其次,WikipediaCorpusNLTK 的扩展PlaintextCorpusReader。该对象可以方便地访问单个文件、句子和单词,以及总语料库数据,例如文件数量或词汇、唯一标记的数量。下一篇文章将继续构建文本处理管道。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/145680.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android 编译插桩操纵字节码

本文讲解如何编译插桩操纵字节码。 就使用 ASM 来实现简单的编译插桩效果&#xff0c;通过插桩实现在每一个 Activity 打开时输出相应的 log 日志。实现思路 过程主要包含两步&#xff1a; 1、遍历项目中所有的 .class 文件​ 如何找到项目中编译生成的所有 .class 文件&#…

初识多线程

一、多任务 现实中太多这样同时做多件事的例子了&#xff0c;例如一边吃饭一遍刷视频&#xff0c;看起来是多个任务都在做&#xff0c;其实本质上我们的大脑在同一时间依旧只做了一件事情。 二、普通方法调用和多线程 普通方法调用只有主线程一条执行路径 多线程多条执行路径…

xPortPendSVHandler任务切换流程

__asm void xPortPendSVHandler( void ) { extern uxCriticalNesting; extern pxCurrentTCB; extern vTaskSwitchContext; PRESERVE8 mrs r0, psp isb//指令同步命令&#xff0c; ldr r3, pxCurrentTCB /* Get the location of the current TCB. */ ldr r2, [r3]//r2保存…

【面试经典150 | 矩阵】螺旋矩阵

文章目录 写在前面Tag题目来源题目解读解题思路方法一&#xff1a;模拟方法二&#xff1a;按层模拟 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于…

flink选择slot

flink选择slot 在这个类里修改 package org.apache.flink.runtime.resourcemanager.slotmanager.SlotManagerImpl; findMatchingSlot(resourceProfile)&#xff1a;找到满足要求的slot&#xff08;负责从哪个taskmanager中获取slot&#xff09;对应上图第8&#xff0c;9&…

[vue-admin-template实战笔记]

1.克隆项目 git clone gitgitee.com:panjiachen/vue-admin-template.git 2.安装依赖 npm install 3.运行项目就会自动打开网页&#xff0c;并且热部署插件 npm run dev 4.查看代码 //将vue-admin-template拖入到idea中即可查看代码 1)并且发现&#xff0c;常用的东西已经集…

从零手搓一个【消息队列】创建核心类, 数据库设计与实现

文章目录 一、创建核心类1, 交换机2, 交换机类型3, 队列4, 绑定5, 交换机转发 & 绑定规则6, 消息7, 消息属性 二、数据库设计1, 使用 SQLite2, 使用 MyBatis2.1, 创建 Interface2.2, 创建 xml 文件 三、硬盘管理 -- 数据库1, 创建 DataBaseManager 类2, init() 初始化数据库…

什么是AI客流量算法?如何应用在实际场景中?

客流量分析算法简而言之就是一种利用数据分析和机器学习技术进行人流量统计、预测和分析的算法。它能够根据不同的数据来源&#xff0c;如摄像头、传感器等&#xff0c;对特定区域内的客流量进行实时监测和分析&#xff0c;并通过对历史数据的综合分析&#xff0c;提供客流趋势…

ARM-day2

1、1到100累加 .text .global _start_start:MOV r0, #1ADD r1,r0, #1fun:ADD r0,r0,r1ADD r1,r1, #1cmp r1, #0x65moveq PC,LRbl funstop:b stop.end2、思维导图

Java基础---第十篇

系列文章目录 文章目录 系列文章目录一、说说Java 中 IO 流二、 Java IO与 NIO的区别(补充)三、java反射的作用于原理一、说说Java 中 IO 流 Java 中 IO 流分为几种? 按照流的流向分,可以分为输入流和输出流; 按照操作单元划分,可以划分为字节流和字符流; 按照流的角色…

java导出word(含图片、表格)

1.pom 引入 <!--word报告生成依赖--><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version></dependency><dependency><groupId>org.apache.poi</groupI…

【单片机】12-串口通信和RS485

1.通信有关的常见概念 区分&#xff1a;串口&#xff0c;COM口&#xff0c;UART&#xff0c;USART_usart和串口区别-CSDN博客 串口、COM口、UART口, TTL、RS-232、RS-485区别详解-CSDN博客 1.什么是通信 &#xff08;1&#xff09;人和人之间的通信&#xff1a;说话&#xff…

腾讯云 Cloud Studio 实战训练营结营活动获奖公示

点击链接了解详情 “腾讯云 Cloud Studio 实战训练营” 是由腾讯云联合 CSDN 推出的系列开发者技术实践活动&#xff0c;通过技术分享直播、动手实验项目、优秀代码评选、有奖征文活动等&#xff0c;让广大开发者沉浸式体验腾讯云开发者工具 Cloud Studio 的同时&#xff0c;实…

《数据结构、算法与应用C++语言描述》-栈的应用-开关盒布线问题

开关盒布线问题 问题描述 在开关盒布线问题中&#xff0c;给定一个矩形布线区域&#xff0c;其外围有若干管脚。两个管脚之间通过布设一条金属线路来连接。这条金属线路称为电线&#xff0c;它被限制在矩形区域内。两条电线交叉会发生电流短路。因此&#xff0c;电线不许交叉…

Python异步框架大战:FastAPI、Sanic、Tornado VS Go 的 Gin

一、前言 异步编程在构建高性能 Web 应用中起着关键作用&#xff0c;而 FastAPI、Sanic、Tornado 都声称具有卓越的性能。本文将通过性能压测对这些框架与Go的Gin框架进行全面对比&#xff0c;揭示它们之间的差异。 原文&#xff1a;Python异步框架大战&#xff1a;FastAPI、Sa…

【智能家居项目】裸机版本——项目介绍 | 输入子系统(按键) | 单元测试

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《智能家居项目》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f3c0;项目简介&#x1f3c0;输入子系统(按键)⚽应用层⚽设备层⚽ 内核层抽象层⚽…

每日一练-Q1-大数加法-20231001

目录 1.题目描述 2.输入描述 3.示例提示 4.问题分析 5.通过代码 1.题目描述 大数一直是一个c语言的一个难题。 现在我们需要你手动模拟出大数加法过程。 请你给出两个大整数加法结果。 2.输入描述 第一行输入整数n&#xff0c;第二行输入整数m。 (1<number<1e100)…

在nodejs中如何防止ssrf攻击

在nodejs中如何防止ssrf攻击 什么是ssrf攻击 ssrf&#xff08;server-side request forgery&#xff09;是服务器端请求伪造&#xff0c;指攻击者能够从易受攻击的Web应用程序发送精心设计的请求的对其他网站进行攻击。(利用一个可发起网络请求的服务当作跳板来攻击其他服务)…

10月1日作业

汇编指令合集 用select实现服务器并发代码 #include<myhead.h> #define IP "192.168.0.106" #define PORT 8888int main(int argc, const char *argv[]) {//新建套接字文件int sfd socket(AF_INET, SOCK_STREAM, 0);if(sfd < 0){ERR_MSG("socket&quo…

多线程学习笔记(一)

文章目录 1 线程基础知识复习2 CompletableFuture1、Future和Callable接口2、FutureTask3、对Future的改进4、案例精讲——电商5、常用方法6、CompetableFutureWithThreadPool【重要】 3 锁1、乐观锁和悲观锁2、synchronized 8锁案例3、公平锁和非公平锁4、可重入锁5、死锁及排…