如何使用Python进行Web数据处理
和文件操作一样,你在访问网络站点的时候也会出错。可能是服务器关机了,也可能是你的Internet连接中断了,还有可能是你查询的文件不存在。不管是什么原因造成,你需要解决这些问题。在Python中正确处理这些问题的方法是使用异常。Urlopen函数能够抛出很多不同的例外,需要掌握的两个异常,一个是HTTPError,当抛出这个异常的时候,你所连接的服务器也同时返回一个错误代码。另一个要掌握异常是URLError,这个异常在其它网络或者协议发生错误时抛出。你可以通过下面的方法捕获异常:
try:
website = urllib2.urlopen(address)
except urllib2.HTTPError, e:
print "Cannot retrieve URL: HTTP Error Code", e.code
except urllib2.URLError, e:
print "Cannot retrieve URL: " + e.reason[1]
因此,当你尝试解析一个不存在的URL的时候,你将会看到类似下面的错误信息:
% python2.4 images.py www.google.com/doesnotexist
Cannot retrieve URL: HTTP Error Code 404
通过错误代码404,我们可能会获取更多的信息,因为我们已经遇到过很多次。但是你能够解释下面这些代码所代表的含义吗?例如,错误代码407表示需要指定代理服务器,错误代码503表示服务器正在超负荷运行,不能处理当前的请求。很明显,我们需要一种更友好的方式给用户反馈错误信息。同样,Python中也提供了这个功能。这是通过BaseHTTPServer模块中定义的字典来完成。字典是Python中的基本数据类型,在其它语言中也被称之为hash表或者map,我们以后将会详细介绍字典。现在我们就简单地将它认为是一个列表,只是它不仅能按照索引顺序返回条目,也可以按照指定的任意其它标示符按序返回。其中,字典BaseHTTPRequestHandler.responses提供了一个错误编码和解释之间的映射表——-如果你对这个映射表感兴趣,可以参照HTTP 1.1 规范 RFC,所以下面的代码:
import BaseHTTPServer
print BaseHTTPServer.BaseHTTPRequestHandler.responses[404]
产生如下的输出:
('Not Found', 'Nothing matches the given URI')
我们可以使用字典输出更详细的错误信息。
使用正规式查找信息
假设我们已经有了需要处理的Web页面的文本内容,现在我们要做的就是查找其中所有的图片标签。可能有些人没有注意到,事实上,在HTML文档中,页面中的图片是以类似如下的方式进行编码的(添加或者删除一些属性):
<img src="/path/to/image.png">
我们真正感兴趣的是引号中的内容,其它的部分可以忽略。我们将使用简单正规式和findall函数来完成复杂的工作。正规式是一种规定标识符串语法规则的方法(在这里,标识符指的是字符、数字、字母或者标点符号),所有种类的字符串都可以被识别,而不需要分别指定。关于正规式有专门的文章讨论,这里我们只看几个例子:
假设你有一些文本数据,并且需要识别其中的二进制字符串,也就是只由0和1组成的字符串。对应的正规式如下所示:
(0|1)+
圆括号用来将多个字母进行组合,中间的竖线("|")意味着选择,既可以匹配竖线左边的字符,也可以匹配竖线右边的字符。最后,"+"表示“要和前面的标识符至少匹配一次或者多次”。所以这一正规式匹配的字符应该是“一个或者多个重复的 0或者1”。对于正规式来说,我们还需要掌握一些常用的标号。例如,句点(".")代表任意的单个字符,星号"*"的用法和"+"类似,但是"*"表示匹配的次数是零次或者多次。所以,一个数目多于5的任意字符串的正规式是下面的形式:
......*
对于编写正规式来识别html中的图像来说,我们的知识已经足够了。为了运行表达式,我们需要使用函数sre.findall,函数的返回值是包含字符串中所有匹配组的列表。下面的代码就完成查找图片的功能:
matches = sre.findall('<img .*src="?(.*?)"?', website_text)
这里有几个问题需要注意:首先,我们将字符串放入单引号中,将它作为路径的起始和结束标记,因为我们还希望在字符串中能够使用双引号。其次,我们使用符号'(', ')'将我们感兴趣的内容组合起来, findall函数将会自动丢弃所有不在组合中的部分。最后,我们使用"?"来完成两项工作。如果"?"紧跟在表达式之后,则匹配0个或1个由前面的正则表达式定义的片段,如果"?"是紧跟在"*"之后,则意味表达式匹配置处于‘非贪婪’模式。默认情况下,正规式匹配是贪心的,也就是说,它会匹配尽可能多的文本内容,我们想做的正好相反,只匹配到第一个"被找到。如果我们现在打开了页面http://www.builderau.com.au并且运行了正规式,我们会得到下面的结果(你们的结果可能不同,这取决于首页的图片内容):
['http://ad.au.doubleclick.net/ad/popup.builderau.com.au/;sz=1x1;ord=123456789?',
'/i/s/cov/checklist170110.jpg', '/i/s/cov/perl170110.jpg', '/i/s/cov/atwork170110.jpg',
'/i/x/blogs/brendon_chase_52x52.gif', '/i/x/blogs/chris_duckett_52x52.gif',
'http://dw-eu.com.com/clear/c.gif?ts=1163740722&sId=75']
到了这一步,我们又遇到了新问题:Web页面中的图片路径既可以表示成绝对路径也可以表示成与页面的相对路径两种形式。从上面的数据中,我们就可以看到这点,一些图片是全路径的形式,并以"http:// "起始,而另外一些只含有目录名。我们的程序应该能够在相对链接的开始添加当前页面的路径。首先,我们需要得到当前页面的路径,我们之前已经完成了很多工作,这里就可以调用urllib2来确定当前的站点地址:
dir = website_handle.geturl().rsplit('/',1)[0]
if (dir == "http:/"):
dir = website_handle.geturl()
我们使用字符串函数rsplit移除页面自身的文件名。rsplit和其兄弟函数split可以按照某个特定的字符对字符串分段,例如:
>>> "Hello World".split(' ')
['Hello', 'World']
>>> "Hello World".rsplit('l')
['He', '', 'o Wor', 'd']
>>> "Hello World".split('l',1)
['He', 'lo World']
>>> "Hello World".rsplit('l', 1)
['Hello Wor', 'd']
唯一性和排序
我们已经得到了所有有效的图片链接,下面要做的就是将结果以最好的方式输出。在这个例子中,我们要删除结果中的重复数据,并对结果进行排序。在Python语言中,使得结果唯一的最简单方式就是将数据存储在一个容器中,例如set。Set类型和List类型很相似,它们之间的区别在于Set中的数据必须是唯一的并且并不是按照特定的顺序存储。这里我们只使用set的基本特性:
>>> x = ['set', 'contents', 'hello', 'world']
>>> y = set(x)
>>> y
set(['world', 'set', 'hello', 'contents'])
>>> y.add("new item")
>>> y
set(['new item', 'world', 'set', 'hello', 'contents'])
>>> list(y)
['new item', 'world', 'set', 'hello', 'contents']
这里需要注意的重要一点就是,可以随意地在list数据和set数据间转换进行,但是会丢失list中条目的顺序性。下一步我想在将结果呈现给用户之前,对数据排序。有很多不同的排序算法,但是我们不愿意在排序的时候再去编写一个程序。基于这样的原因,Python也提供了很多排序函数。并不是在所有的环境下,这些函数都是最好的,当你需要快速排序的时候,你可能要自己亲自编写一个排序函数,但是在大多数的情况下,这些函数已经能够满足我们的需要。list.sort函数对list进行原地排序,并不产生新的list。


















文章评论
共有 位CH网友发表了评论 查看完整内容