第二部分:高级抓取(第七章、清理脏数据)

第二部分:高级抓取(第七章、清理脏数据)

你已经奠定了一些网页抓取的基础:现在到了有趣的部分。在现在之前,我们的网络爬虫一直都比较愚蠢

。他们无法检索信息,除非服务器会立即呈现给他们一个很好的格式。他们收集一切信以为真的信息并且

没有任何分析的简单的存储。他们因为格式、网站的互动甚至JavaScript导致程序出错。总之,他们没有

很好的检索信息,除非该信息真的想被检索。

书的这一部分将帮助你分析原始数据来获取数据下的故事——这个故事是经常被网站隐藏在JavaScript层

,登陆表单和反爬虫措施的下面。

你将学习如何使用网页爬虫来测试你的网站,使该流程自动化并且大规模的访问互联网。在本节结束时,

你应该有工具来收集和操作任何部分的互联网上的任何类型的数据。

第七章 清理脏数据

到目前为止,在这本书中我们忽略了格式错误的数据,而使用通常格式正确的数据源,会删除完全偏离我

们期待的数据。但是网页抓取通常不能从获取数据的地方太挑剔。

由于错误的标点符号,大小写不一致,换行符和拼写错误,脏数据是网络上的一个大问题。在这一章,我

将介绍一些工具和技巧帮助你改变你的编写代码的方式,可以帮助你从源头上解决这些问题和一旦它们出

现在数据库就清理它们。

在代码中清理

正如你编写代码来处理明显的异常,你应该练习防御性的代码来处理意外。

在自然语言学,n-gram是在文本或者语音处理使用的n个字的序列。当做自然语言分析时,它往往能很方

便的从一段文字中分开寻找常用的n-gram或者重复的单词集,两者经常同时使用。

在本节中,我们将专注于获取正确格式的n-gram,而不是使用它们做任何分析。随后在第八章,你可以看

到使用2-grams,3-grams做文字总结和分析。

下面将返回维基百科中Python程序语言文章的2-grams列表:

from urllib.request import urlopen

from bs4 import BeautifulSoup

def ngrams(imput, n):

input = input.split(' ')

output = []

for i in range(len(input)-n+1):

output.append(input[i:i+1])

return output

html = urlopen("http://en.wikipedia.org/wiki/Python_(programming_language)")

bsObj = BeautifulSoup(html)

content = bsObj.fand("div", {"id":"mw-content-text"}).get_text()

ngrams = ngrams(content, 2)

print(ngrams)

print("2-grams count is: "+str(len(ngrams)))

ngrams函数把输入字符串分解成一个单词序列(假设所有单词由空格分隔),并且添加n-gram(此时为2

-gram)的每个单词到一个数组。

这从文本返回一些真正有趣的和有用的2-grams:

['of', 'free'], ['free', 'and'], ['and', 'open-source'], ['open-source', 'software']

但也有很多垃圾:

['software\nOutline\nSPDX\n\n\n\n\n\n\n\n\nOperating', 'system\nfamilies\n\n\n\n

AROS\nBSD\nDarwin\neCos\nFreeDOS\nGNU\nHaiku\nInferno\nLinux\nMach\nMINIX\nOpenS

olaris\nPlan'], ['system\nfamilies\n\n\n\nAROS\nBSD\nDarwin\neCos\nFreeDOS\nGNU\

nHaiku\nInferno\nLinux\nMach\nMINIX\nOpenSolaris\nPlan', '9\nReactOS\nTUD:OS\n\n

\n\n\n\n\n\n\nDevelopment\n\n\n\nBasic'], ['9\nReactOS\nTUD:OS\n\n\n\n\n\n\n\n\n

Development\n\n\n\nBasic', 'For']

此外,由于每遇到一个单词就创建一个2-gram(除了最后一个),在写这篇文章的时候还有7,411个2-

gram在文本中。不是一个非常易于管理的数据集。

使用正则表达式来删除转义字符(如\n)和过滤删除任何Unicode字符,我们可以清理输出:

def ngrams(input, n):

content = re.sub('\n+', ' ', content)

content = re.sub(' +', ' ', content)

content = bytes(content, 'UTF-8')

content = content.decode("ascii", "ignore")

print(content)

input = input.split(' ')

output = []

for i in range(len(input)-n+1):

output.append(input[i:i+n])

return output

第一个用空格替换所有的换行符(或多个换行符)实例,然后把一排中的空格实例用一个空格替换,确保

所有的单词之间都有一个空格。然后通过内容编码为UTF-8进行淘汰。

这些措施大大提高了函数的输出,但仍存在一些问题:

['Pythoneers.[43][44]', 'Syntax'], ['7', '/'], ['/', '3'], ['3', '=='], ['==', '2']

此时需要做一些决定以便数据处理的更加有趣。有一些更多的规则,我们可以使用更接近理想的数据。

①一个字符的“词语”应该被丢弃,除非该字符是'i'或者'a'。

②维基百科的应用标记(在括号中的数字),应该被丢弃

③标点符号应该被丢弃(注:此规则是一种简化,并且将在第九章更详细的讨论,但是在这个例子的目的

这样做是好的)

现在“清理任务”的代码越来越长,最好是把他们移出去然后放在一个单独的函数cleanInput中:

from urllib.request import urlopen

from bs4 import BeatifulSoup

import re

import string

def cleanInput(input):

input = re.sub("\n+", " ", input)

input = re.sub("\[0-9]*\", "", input)

input = re.sub(" +", " ", input)

input = bytes(input, 'UTF-8')

input = input.decode("ascii", ignore)

cleanInput = []

for item in input:

item = item.strip(string.punctuation)

if len(item) > 1 or (item.lower() == 'a' or item.lower() == 'i'):

cleanInput.append(item)

return cleanInput

def ngrams(input, n):

input = cleanInput(input)

output = []

for i in range(len(input)-n+1):

output.append(input[i:i+n])

return output

注意在Python中使用import string使用string.punctuation来获取所有标点的列表。你可以从Python的

终端查看string.punctuation的输出:

>>> import string

>>> print(string.punctuation)

!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~

通过使用item.strip(string.punctuation)在循环内迭代所有单词的内容,单词两端的任何标点字符都将

被剥离,连字符(标点字符界定是在字母的两边)的单词将保持不变。

努力之后的更干净的2-gram输出结果:

['Linux', 'Foundation'], ['Foundation', 'Mozilla'], ['Mozilla', 'Foundation'], [

'Foundation', 'Open'], ['Open', 'Knowledge'], ['Knowledge', 'Foundation'], ['Fou

ndation', 'Open'], ['Open', 'Source']

数据标准化

每个人都遇到过设计不当的web表单:“请输入您的手机号码。你的电话号码形式必须是:'XXX-XXX-

XXXX'。”

作为一个优秀的程序员,你可能会想,“为什么他们不只是在我输入那里自动去掉非数字字符?”数据标

准化是确保字符串在语言或者是逻辑上彼此等同的过程。如电话号码“(555)123-4567”和

“555.123.4567”这样显示,但至少相比是等同的。

从上一节使用的n-gram代码,我们可以添加以下数据标准化特征:

这个代码一个明显的问题就是包含许多重复的2-grams。遇到的每个2-gram都添加到列表,没有记录词语

的频率。它不仅仅是为了记录这些2-gram的频率,而不是他们的存在,而是它也可以在图表中反映清理和

数据标准化算法变化的影响。如果数据标准化的非常成功,独特的n-grams总数将会减少,在整个发现的

的n-gram中数目(即,数量唯一或者非唯一的名目被确定为一个n-gram)不会减少。换句话说,相同数量

的n-grams将有更少的“桶”。

不幸的是这个练习的目的,Python字典是无序的。“字典排序”没有任何意义,除非是你复制字典中的值

在其他的内容类型去排序。一个简单的解决办法就是OrderedDict,来自Python的集合库:

from collections import OrderedDict

...

ngrams = ngrams(content, 2)

ngrams = OrderedDict(sorted(ngrams.items(), key=lambda t: t[1], reverse=True))

print(ngrams)

在这里,我利用Python的排序函数,把项目放入一个新的OrderedDict对象中,按照值排序。结果是:

("['Software', 'Foundation']", 40), ("['Python', 'Software']", 38), ("['of', 'th

e']", 35), ("['Foundation', 'Retrieved']", 34), ("['of', 'Python']", 28), ("['in

', 'the']", 21), ("['van', 'Rossum']", 18)

在撰写本文时,共有7696个2-grams和6005个唯一的2-grams,最流行的2-gram是“Software Foundation

”,其次是“Python Software”。不过,分析结果显示,“Python Software”实际出现了两次“Python

software”。同样,无论“Van Rossum”和“van Rossum”在列表中是单独出现。

添加这一行:

input = input.upper()

到cleanInput函数保持2-grams的总数稳定到7696,降低了唯一的2-grams数目到5882。

除此之外,通常很好的是停下来考虑你消耗的标准化数据需要多大的计算能力。有多种情况,单词不同的

拼写是等价的,为了解决这个等值,需要运行一个对单个字符的检查,看它是否符合你预先编程设定的等

值。

例如,“Python 1st”和“Python first”都出现在2-grams的列表中。然而,做一个公共的规则,“所

有的‘first’,‘second’,‘third’等将会被解析为1st, 2nd,3rd等(或者反之亦然)”将导致每个

单词由额外的10个左右的检查。

同样,使用不一致的连字符(“co-ordinated”与“coordinator”),拼写错误以及其他自然语言的不

协调都会影响n-grams的分组,如果不协调是很常见的,可能会输出结果混乱。

一个解决方案,再连字符单词的情况下,可能会删除连字符对待单词作为单个字符串,这只需要一个单一

操作。然而,这也意味着复姓短语(一个太常见的现象)会被视为单个单词。去其他途径来对待连字符视

为空格可能是一个更好的选择。只是准备偶尔的“co ordinated”和“ordinated attack”出差错。

在事后清理

在代码中只有这么多你可以或者想要做的。此外,你可能要处理你没有创建的数据集,有一个挑战是甚至

不限看到这个数据集不知道如何清洁它。

许多程序员下意识的反应是“写一个脚本”,是一个很好的解决方案。然而,也有第三方工具,比如

OpenRefine,其不仅能够快速,方便的清理数据,还允许你的数据可以轻易的被使用的非程序员看到。

OpenRefine

OpenRefine是一家名叫Metaweb公司在2009年开始的一个开源项目。谷歌在2010年收购Metaweb,把项目名

称从Freebase Girdworks改为Google Refine。在2012年,谷歌放弃支持Refine,并且再次改名为

OpenRefine,欢迎任何人贡献发展这个项目。

安装

OpenRefine是不寻常的,尽管它的界面是运行在一个浏览器,它在技术上是一个桌面应用,必须下载安装

。你可以从它的网站上下载Linux,Windows和Mac OS X版本的应用程序。

注意

如果你是一个MAC用户,再打开文件时碰上任何麻烦,进入系统设置——安全隐私——常规——并选中“

允许来自任何地方的应用程序”。不幸的是,在从谷歌项目过度到一个开源项目时,OpenRefine在苹果严

重似乎已经失去了它的合法性。

为了使用OpenRefine,你需要将数据保存为CSV(如果你需要复习怎么做,文件指回第五章“数据存储”)

。另外,如果你有存储在数据库中的数据,你也可以将其导出到一个CSV文件。

使用OpenRefine

在下面的例子中,我们将使用从维基百科“文本编辑器的比较”表抓取的数据,见图7-1。尽管该表有比

较好的格式化,它包含很长一段时间内编辑的人,所以他有一些轻微的格式不一致。此外,由于它的数据

是由人阅读而不是机器,一些编程选择(例如,使用“免费”而不是“$0.00”)是不适合编程输入。

图7-1:

关于OpenRefine需要注意的第一件事就是:每一列标签旁边有一个箭头。这个箭头提供可以用来在该列过

滤,排序,转化或者删除数据的工具。

过滤

数据过滤可以用两种方法来进行:过滤器和面。过滤器使用正则表达式进行良好数据过滤;例如,“只展

示给我,在编程语言列中包含四个或者更多逗号分隔的编程语言”,见图7-2:

过滤器可以组合,编辑,并通过在有右边栏简单的操纵块。它们还可以和面结合。

面是在包括或者基于列的全部内容排除数据。(例如,“显示说有2005年以后首次发型,且使用GPL或者

MIT许可的行”如图7-3)。

它们有内置过滤器。例如,过滤一个数字值,提供了一个滑杆来选择要包括的数值范围。

然而你筛选数据,OpenRefine支持在任何时候导出各种格式类型。这包括CSV,HTML(一个HTML表),

Excel和其他几个格式。

清理

数据过滤可以在数据清理开始时候做的很成功。例如,在上一节面的例子中,文本编辑器有一个发行日期

“01-01-2006”不会被选择在“首次发行”面,将会查看值“2006”,会忽略那些不是它选择的。

OpenRefine中的数据变换使用OpenRefine表达语言(OpenRefine Expression Language),称为GREL(“G

”来自OpenRefine以前的名字Google Refine)。这种语言用来创建短的lambda函数在单元中根据简单规则

转换值。例如:

if(value.length() != 4, "invalid", value)

当此功能被应用到“第一个稳定版本”栏,它保留其中日期是一个“YYYY”的值得单元,并且标记其他列

是“无效的”。

如图7-4:

任意的GREL语句可以通过单击列标签旁边向下的箭头然后选择要编辑的单元→变换。

然而,标记所有小于理想值的全部无效使得它们很容易被识别,我们没有做的多好。如果可能我们宁愿尝

试从格式错误值中搜寻信息。这个可以通过使用GREL的匹配函数完成:

value.match(".*([0-9]{4}).*").get(0)

这个用给出的正则表达式匹配字符串值。如果正则表达式匹配到字符串,返回值是数组。通过正则表达式

(在此由括号中的表达式划分,例如“[0-9]{4}”)匹配的“捕获组”的子字符串返回为数组值。

事实上,这个代码是发现所有行中的四位十进制实例,并返回第一个。这通常足够从文本或者错误格式日

期中提取年份。它也有不存在的日期返回“null”的优势。(GERL不会抛出一个空指针,除了对一个空变

量进行操作)

许多其他数据转换通过单元编辑和GERL是可能的。一个完整的语言指导可以在OpenRefine的Github页面查

看。

转载请注明出处http://blog.csdn.net/qq_15297487

你可能感兴趣的:(翻译第七章清理脏数据,web,scrapin,with,python,web,scrapin,with,pyt,应用,python,web,清理脏数据)