爬虫系列:数据清洗
上一期我们讲解了使用 Python 读取 CSV、PDF、Word 文档相关内容。
前面我们已经介绍了网络数据采集的一些基础知识,现在我们将进入高级数据采集部分。到目前为止,我们创建的网络爬虫都不是特别给力,如果网络服务器不能立即提供样式规范的信息,爬虫就不能采集正确的数据。如果爬虫只能采集那些显而易见的信息,不经过处理就存储起来,那么迟早要被登录表单、网页交互以及 Javascript 困住手脚。总之,目前爬虫还没有足够的实力去采集各种数据,只能处理那些愿意被采集的信息。
在高级数据采集部分就是要帮你分析原始数据,获取隐藏在数据背后的故事——网站的真实故事其实都隐藏在 Javascript、登录表单和网站反爬措施背后。
数据清洗
到目前为止,我们都没有处理过那些样式不规范的数据,要么使用的是样式规范的数据源,要么就是放弃样式不符合我们预期的数据。但在网络数据采集中,你通常无法对采集的数据样式太挑剔。
由于错误的标点符号、大小写字母不一致、断行和拼写错误等问题,凌乱的数据(dirty data)是网络中的大问题。下面我们就通过工具和技术,通过改变代码的编写方式,帮你从源头控制数据凌乱的问题,并且对已经入库的数据经行清洗。
编写代码清洗数据
和编写异常处理代码一样,你应该学会编写预防型代码来处理意外情况。
在语言学中有一个模型叫 n-gram,表示文本或语言中的 n 个连续的单词组成的序列。再进行自然语言分析时,用 n-gram 或者寻找常用词组,就可以很容易的把一句话分成若干个文字片段。
在接下来的内容我们将重点介绍如何获取格式合理的 n-gram。
下面的代码返回维基百科词条“Python programming language”的 2-gram 列表:
from utils import connection_util
class DataCleaning(object):
def __init__(self):
self._target_url = 'https://en.wikipedia.org/wiki/python_(programming_language)'
self._init_connection = connection_util.ProcessConnection()
@staticmethod
def ngrams(input_text, n):
split_result = input_text.split(' ')
output = []
for i in range(len(split_result) - n + 1):
output.append(split_result[i:i + n])
return output
def get_result(self):
# 连接目标网站,获取内容
get_content = self._init_connection.init_connection(self._target_url)
if get_content:
content = get_content.find("div", {"id": "mw-content-text"}).get_text()
ngrams = self.ngrams(content, 2)
print(ngrams)
print("2-grams count is: " + str(len(ngrams)))
if __name__ == '__main__':
DataCleaning().get_result()
ngrams 函数把一个待处理的字符串分成单词序列(假设所有单词按照空格分开),然后增加到 n-gram 模型形成以每个单词开始的二元数组。
运行程序之后,会有一些凌乱的数据,例如:
['web', 'frameworks\nBottle\nCherryPy\nCubicWeb\nDjango\nFastAPI\nFlask\nGrok\nNagare\nNevow\nPylons\nPyramid\nQuixote\nTACTIC\nTornado\nTurboGears\nTwistedWeb\nWebware\nweb2py\nZope']
另外,应为每个单词(除了最后一个单词)都要创建一个 2-gram 序列,所以这个词条里共有 11680 个 2-gram 序列。这并不是一个非常便于管理的数据集!
我们首先使用一些正则表达式来移除转义字符(\n),再把 Unicode 字符过滤掉。我们可以通过下面的函数对之前输出的内容经行清理:
import re
from utils import connection_util
class DataCleaning(object):
def __init__(self):
self._target_url = 'https://en.wikipedia.org/wiki/python_(programming_language)'
self._init_connection = connection_util.ProcessConnection()
@staticmethod
def ngrams(input, n):
input = re.sub('\n+', " ", input)
input = re.sub(' +', " ", input)
input = bytes(input, "UTF-8")
input = input.decode("ascii", "ignore")
print(input)
input = input.split(' ')
output = []
for i in range(len(input) - n + 1):
output.append(input[i:i + n])
return output
def get_result(self):
# 连接目标网站,获取内容
get_content = self._init_connection.init_connection(self._target_url)
if get_content:
content = get_content.find("div", {"id": "mw-content-text"}).get_text()
ngrams = self.ngrams(content, 2)
print(ngrams)
print("2-grams count is: " + str(len(ngrams)))
if __name__ == '__main__':
DataCleaning().get_result()
上面的代码首先将内容中的换行符(或者多个换行符)替换成空格,然后把连续的多个空格替换成一个空格,确保所有单词之间只有一个空格。最后,把内容转换成 UTF-8 格式以消除转义字符。
通过上面的几步,我们已经可以大大改善输出结果了,但是还是有一些问题:
ALGOL 68,[13] APL,[14] C,[15] C++,[16] CLU,[17] Dylan,[18] Haskell,[19] Icon,[20] Java,[21] Lisp,[22] Modula-3,[16] Perl, Standard ML[14]InfluencedApache Groovy
因此,需要增加一些规则来处理数据。我们可以定制一些规则让数据变得更规范:
-
剔除单字符的“单词”,除非这个单词是“a”或“i”;
-
剔除维基百科的引用标记(方括号包裹的数字,入[1])
-
剔除标点符号
现在“清洗任务”列表变得越来越长,让我们把规则都移出来,新建一个函数:
import re
import string
def ngrams(self, input, n):
input = self.clean_input(input)
output = []
for i in range(len(input) - n + 1):
output.append(input[i:i + n])
return output
@staticmethod
def clean_input(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")
input = input.split(' ')
clean_input = []
for item in input:
# string.punctuation 获取所有的标点符号
item = item.strip(string.punctuation)
if len(item) > 1 or (item.lower() == 'a' or item.lower() == 'i'):
clean_input.append(item)
return clean_input
这里用 import string 和 string.punctuation 来获取 Python 所有的标点符号。我们可以在 Python 命令行里面查看标点符号有哪些:
import string
print(string.punctuation)
!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
在循环体中用item.strip(string.punctuation)
对内容中的所有单词进行清洗,单词两端的任何标点符号都会被去掉,但带连字符的单词(连字符在单词内部)任然会保留。
本期关于数据清洗就是如上内容,在接下来的内容中我会讲解数据标准化,以及存储的数据如何清洗。
以上的演示源代码托管于 Gihub,地址:https://github.com/sycct/Scrape_1_1.git
如果有任何问题,欢迎大家 issue。