- 3.1 从网络和硬盘访问文本
- 电子书
- 处理 HTML
- 处理搜索引擎的结果
- 读取本地文件
- 从 PDF、MS Word 及其他二进制格式中提取文本
- 捕获用户输入
- NLP 的流程
3.1 从网络和硬盘访问文本
电子书
NLTK 语料库集合中有古腾堡项目的一小部分样例文本。然而,你可能对分析古腾堡项目的其它文本感兴趣。你可以在http://www.gutenberg.org/catalog/上浏览 25,000 本免费在线书籍的目录,获得 ASCII 码文本文件的 URL。虽然 90%的古腾堡项目的文本是英语的,它还包括超过 50 种语言的材料,包括加泰罗尼亚语、中文、荷兰语、芬兰语、法语、德语、意大利语、葡萄牙语和西班牙语(每种语言都有超过 100 个文本)。
编号 2554 的文本是 《罪与罚》 的英文翻译,我们可以如下方式访问它。
>>> from urllib import request>>> url = "http://www.gutenberg.org/files/2554/2554.txt">>> response = request.urlopen(url)>>> raw = response.read().decode('utf8')>>> type(raw)<class 'str'>>>> len(raw)1176893>>> raw[:75]'The Project Gutenberg EBook of Crime and Punishment, by Fyodor Dostoevsky\r\n'
注意
read()过程将需要几秒钟来下载这本大书。如果你使用的 Internet 代理 Python 不能正确检测出来,你可能需要在使用urlopen之前用下面的方法手动指定代理:
>>> proxies = {'http': 'http://www.someproxy.com:3128'}>>> request.ProxyHandler(proxies)
变量raw包含一个有 1,176,893 个字符的字符串。(我们使用type(raw)可以看到它是一个字符串。)这是这本书原始的内容,包括很多我们不感兴趣的细节,如空格、换行符和空行。请注意,文件中行尾的\r和\n,这是 Python 用来显示特殊的回车和换行字符的方式(这个文件一定是在 Windows 机器上创建的)。对于语言处理,我们要将字符串分解为词和标点符号,正如我们在1.中所看到的。这一步被称为分词,它产生我们所熟悉的结构,一个词汇和标点符号的列表。
>>> tokens = word_tokenize(raw)>>> type(tokens)<class 'list'>>>> len(tokens)254354>>> tokens[:10]['The', 'Project', 'Gutenberg', 'EBook', 'of', 'Crime', 'and', 'Punishment', ',', 'by']
请注意,分词需要 NLTK,但所有前面的打开一个 URL 以及读入一个字符串的任务都不需要。如果我们现在采取进一步的步骤从这个列表创建一个 NLTK 文本,我们可以进行我们在1.中看到的所有的其他语言的处理,也包括常规的列表操作例如切片:
>>> text = nltk.Text(tokens)>>> type(text)<class 'nltk.text.Text'>>>> text[1024:1062]['CHAPTER', 'I', 'On', 'an', 'exceptionally', 'hot', 'evening', 'early', 'in','July', 'a', 'young', 'man', 'came', 'out', 'of', 'the', 'garret', 'in','which', 'he', 'lodged', 'in', 'S.', 'Place', 'and', 'walked', 'slowly',',', 'as', 'though', 'in', 'hesitation', ',', 'towards', 'K.', 'bridge', '.']>>> text.collocations()Katerina Ivanovna; Pyotr Petrovitch; Pulcheria Alexandrovna; AvdotyaRomanovna; Rodion Romanovitch; Marfa Petrovna; Sofya Semyonovna; oldwoman; Project Gutenberg-tm; Porfiry Petrovitch; Amalia Ivanovna;great deal; Nikodim Fomitch; young man; Ilya Petrovitch; n't know;Project Gutenberg; Dmitri Prokofitch; Andrey Semyonovitch; Hay Market
请注意,Project Gutenberg 以一个搭配出现。这是因为从古腾堡项目下载的每个文本都包含一个首部,里面有文本的名称、作者、扫描和校对文本的人的名字、许可证等信息。有时这些信息出现在文件末尾页脚处。我们不能可靠地检测出文本内容的开始和结束,因此在从原始文本中挑出正确内容且没有其它内容之前,我们需要手工检查文件以发现标记内容开始和结尾的独特的字符串:
>>> raw.find("PART I")5338>>> raw.rfind("End of Project Gutenberg's Crime")1157743>>> raw = raw[5338:1157743] ![[1]](/projects/nlp-py-2e-zh/Images/7e6ea96aad77f3e523494b3972b5a989.jpg)>>> raw.find("PART I")0
方法find()和rfind()(反向的 find)帮助我们得到字符串切片需要用到的正确的索引值
。我们用这个切片重新给raw赋值,所以现在它以“PART I”开始一直到(但不包括)标记内容结尾的句子。
这是我们第一次接触到网络的实际内容:在网络上找到的文本可能含有不必要的内容,并没有一个自动的方法来去除它。但只需要少量的额外工作,我们就可以提取出我们需要的材料。
处理 HTML
网络上的文本大部分是 HTML 文件的形式。你可以使用网络浏览器将网页作为文本保存为本地文件,然后按照下面关于文件的小节描述的那样来访问它。不过,如果你要经常这样做,最简单的办法是直接让 Python 来做这份工作。第一步是像以前一样使用urlopen。为了好玩,我们将挑选一个被称为 Blondes to die out in 200 years 的 BBC 新闻故事,一个都市传奇被 BBC 作为确立的科学事实流传下来:
>>> url = "http://news.bbc.co.uk/2/hi/health/2284783.stm">>> html = request.urlopen(url).read().decode('utf8')>>> html[:60]'<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN'
你可以输入print(html)来查看 HTML 的全部内容,包括 meta 元标签、图像标签、map 标签、JavaScript、表单和表格。
要得到 HTML 的文本,我们将使用一个名为 BeautifulSoup 的 Python 库,可从 http://www.crummy.com/software/BeautifulSoup/ 访问︰
>>> from bs4 import BeautifulSoup>>> raw = BeautifulSoup(html).get_text()>>> tokens = word_tokenize(raw)>>> tokens['BBC', 'NEWS', '|', 'Health', '|', 'Blondes', "'to", 'die', 'out', ...]
它仍然含有不需要的内容,包括网站导航及有关报道等。通过一些尝试和出错你可以找到内容索引的开始和结尾,并选择你感兴趣的词符,按照前面讲的那样初始化一个文本。
>>> tokens = tokens[110:390]>>> text = nltk.Text(tokens)>>> text.concordance('gene')Displaying 5 of 5 matches:hey say too few people now carry the gene for blondes to last beyond the nextblonde hair is caused by a recessive gene . In order for a child to have blondhave blonde hair , it must have the gene on both sides of the family in the gere is a disadvantage of having that gene or by chance . They do n't disappeardes would disappear is if having the gene was a disadvantage and I do not thin
处理搜索引擎的结果
网络可以被看作未经标注的巨大的语料库。网络搜索引擎提供了一个有效的手段,搜索大量文本作为有关的语言学的例子。搜索引擎的主要优势是规模:因为你正在寻找这样庞大的一个文件集,会更容易找到你感兴趣语言模式。而且,你可以使用非常具体的模式,仅仅在较小的范围匹配一两个例子,但在网络上可能匹配成千上万的例子。网络搜索引擎的第二个优势是非常容易使用。因此,它是一个非常方便的工具,可以快速检查一个理论是否合理。
表 3.1:
搭配的谷歌命中次数:absolutely 或 definitely 后面跟着 adore, love, like 或 prefer 的搭配的命中次数。(Liberman, in LanguageLog, 2005)。
>>> import feedparser>>> llog = feedparser.parse("http://languagelog.ldc.upenn.edu/nll/?feed=atom")>>> llog['feed']['title']'Language Log'>>> len(llog.entries)15>>> post = llog.entries[2]>>> post.title"He's My BF">>> content = post.content[0].value>>> content[:70]'<p>Today I was chatting with three of our visiting graduate students f'>>> raw = BeautifulSoup(content).get_text()>>> word_tokenize(raw)['Today', 'I', 'was', 'chatting', 'with', 'three', 'of', 'our', 'visiting','graduate', 'students', 'from', 'the', 'PRC', '.', 'Thinking', 'that', 'I','was', 'being', 'au', 'courant', ',', 'I', 'mentioned', 'the', 'expression','DUI4XIANG4', '\u5c0d\u8c61', '("', 'boy', '/', 'girl', 'friend', '"', ...]
伴随着一些更深入的工作,我们可以编写程序创建一个博客帖子的小语料库,并以此作为我们 NLP 的工作基础。
读取本地文件
为了读取本地文件,我们需要使用 Python 内置的open()函数,然后是read()方法。假设你有一个文件document.txt,你可以像这样加载它的内容:
>>> f = open('document.txt')>>> raw = f.read()
注意
轮到你来: 使用文本编辑器创建一个名为document.txt的文件,然后输入几行文字,保存为纯文本。如果你使用 IDLE,在 File 菜单中选择 New Window 命令,在新窗口中输入所需的文本,然后在 IDLE 提供的弹出式对话框中的文件夹内保存文件为document.txt。然后在 Python 解释器中使用f = open('document.txt')打开这个文件,并使用print(f.read())检查其内容。
当你尝试这样做时可能会出各种各样的错误。如果解释器无法找到你的文件,你会看到类似这样的错误:
>>> f = open('document.txt')Traceback (most recent call last):File "<pyshell#7>", line 1, in -toplevel-f = open('document.txt')IOError: [Errno 2] No such file or directory: 'document.txt'
要检查你正试图打开的文件是否在正确的目录中,使用 IDLE File 菜单上的 Open 命令;另一种方法是在 Python 中检查当前目录:
>>> import os>>> os.listdir('.')
另一个你在访问一个文本文件时可能遇到的问题是换行的约定,这个约定因操作系统不同而不同。内置的open()函数的第二个参数用于控制如何打开文件:open('document.txt', 'rU') —— 'r'意味着以只读方式打开文件(默认),'U'表示“通用”,它让我们忽略不同的换行约定。
假设你已经打开了该文件,有几种方法可以阅读此文件。read()方法创建了一个包含整个文件内容的字符串:
>>> f.read()'Time flies like an arrow.\nFruit flies like a banana.\n'
回想一'\n'字符是换行符;这相当于按键盘上的 Enter 开始一个新行。
我们也可以使用一个for循环一次读文件中的一行:
>>> f = open('document.txt', 'rU')>>> for line in f:... print(line.strip())Time flies like an arrow.Fruit flies like a banana.
在这里,我们使用strip()方法删除输入行结尾的换行符。
NLTK 中的语料库文件也可以使用这些方法来访问。我们只需使用nltk.data.find()来获取语料库项目的文件名。然后就可以使用我们刚才讲的方式打开和阅读它:
>>> path = nltk.data.find('corpora/gutenberg/melville-moby_dick.txt')>>> raw = open(path, 'rU').read()
从 PDF、MS Word 及其他二进制格式中提取文本
ASCII 码文本和 HTML 文本是人可读的格式。文字常常以二进制格式出现,如 PDF 和 MSWord,只能使用专门的软件打开。第三方函数库如pypdf和pywin32提供了对这些格式的访问。从多列文档中提取文本是特别具有挑战性的。一次性转换几个文件,会比较简单些,用一个合适的应用程序打开文件,以文本格式保存到本地驱动器,然后以如下所述的方式访问它。如果该文档已经在网络上,你可以在 Google 的搜索框输入它的 URL。搜索结果通常包括这个文档的 HTML 版本的链接,你可以将它保存为文本。
捕获用户输入
有时我们想捕捉用户与我们的程序交互时输入的文本。调用 Python 函数input()提示用户输入一行数据。保存用户输入到一个变量后,我们可以像其他字符串那样操纵它。
>>> s = input("Enter some text: ")Enter some text: On an exceptionally hot evening early in July>>> print("You typed", len(word_tokenize(s)), "words.")You typed 8 words.
NLP 的流程
3.1总结了我们在本节涵盖的内容,包括我们在1..中所看到的建立一个词汇表的过程。(其中一个步骤,规范化,将在3.6讨论。)

图 3.1:处理流程:打开一个 URL,读里面 HTML 格式的内容,去除标记,并选择字符的切片;然后分词,是否转换为nltk.Text对象是可选择的;我们也可以将所有词汇小写并提取词汇表。
在这条流程后面还有很多操作。要正确理解它,这样有助于明确其中提到的每个变量的类型。使用type(x)我们可以找出任一 Python 对象x的类型,如type(1)是<int>因为1是一个整数。
当我们载入一个 URL 或文件的内容时,或者当我们去掉 HTML 标记时,我们正在处理字符串,也就是 Python 的<str>数据类型。(在3.2节,我们将学习更多有关字符串的内容):
>>> raw = open('document.txt').read()>>> type(raw)<class 'str'>
当我们将一个字符串分词,会产生一个(词的)列表,这是 Python 的<list>类型。规范化和排序列表产生其它列表:
>>> tokens = word_tokenize(raw)>>> type(tokens)<class 'list'>>>> words = [w.lower() for w in tokens]>>> type(words)<class 'list'>>>> vocab = sorted(set(words))>>> type(vocab)<class 'list'>
一个对象的类型决定了它可以执行哪些操作。比如我们可以追加一个链表,但不能追加一个字符串:
>>> vocab.append('blog')>>> raw.append('blog')Traceback (most recent call last):File "<stdin>", line 1, in <module>AttributeError: 'str' object has no attribute 'append'
同样的,我们可以连接字符串与字符串,列表与列表,但我们不能连接字符串与列表:
>>> query = 'Who knows?'>>> beatles = ['john', 'paul', 'george', 'ringo']>>> query + beatlesTraceback (most recent call last):File "<stdin>", line 1, in <module>TypeError: cannot concatenate 'str' and 'list' objects
