python抓取网页数据,python对网页进行操作
使用Python捕捉网页的注意事项天空中的飞熊
关于用Python抓取网页的笔记由raphaelzhang于2012年3月8日发表。
用Python编写网页程序非常快。这里有一个例子:
计算机编程语言
123 import URL lib 2 html=URL lib 2 . URL open( http://blog . Raphael Zhang . com )。阅读()
但在实际工作中,这种写法远远不够,至少会遇到以下问题:
网络会出问题,任何错误都有可能。比如机器宕机、网线断了、域名错了、网络超时、页面没了、网站跳转、服务被禁、主机负载不够……服务器被限制,只允许常用浏览器访问服务器,防盗链被限制。有些2B网站,不管你的HTTP请求里有没有Accept-Encoding头,也不管你头的具体内容是什么,反正发给你gzip之后的内容的URL链接都是各种千奇百怪的,甚至有汉字的,有的甚至还有回车和换行符。一些网站在HTTP头中有一个内容类型,并且在网页中有几个内容类型。更重要的是,每个内容类型都是不同的,最糟糕的是,这些内容类型可能不是文本中使用的内容类型,这导致了乱码网络的缓慢链接。分析了几千页,建议大家可以好好吃一顿,去Python自己的接口有点粗糙。好了,这么大量的问题,我们来一个一个的修复吧。
错误处理和服务器限制首先是错误处理。由于urlopen本身将大部分错误,甚至是4XX和5XX的HTTP响应都变成了异常,所以我们只需要捕捉异常。同时我们也可以获取urlopen返回的响应对象,读取其HTTP状态码。除此之外,我们在urlopen的时候,还需要设置超时参数,保证超时处理好。下面是一个代码示例:
Python 12345678910112131415导入urllib2导入套接字try:f=URL lib 2 . URL opern( 3358 blog . Raphael Zhang . com ,time out=10)code=f . get code()if code 200 or code=300:#自己的HTTP错误处理
except Exception,e:if isinstance(e,urllib2。HTTPError):打印“http错误:{0}”。格式(e.code)elif isinstance(e,urllib2。URLError)和isinstance(e.reason,socket.timeout):打印“url error: socket timeout {0}”。format(例如__str__())else:print misc error:例如__str__()
如果是服务器限制,一般来说我们可以通过检查真实浏览器的请求来设置相应的HTTP头。例如,我们可以为浏览器限制设置User-Agent头,为防盗链限制设置Referer头。下面是示例代码:
计算机编程语言
1234567导入urllib2req=urllib2。request( http://blog . raphaelzhang . com ,headers={ Referer : http://www . Baidu . com , User-Agent : Mozilla/5.0(Windows NT 5.1)AppleWebKit/534.24(KHTML,like Gecko)Chrome/11 . 0 . 696 . 68 Safari/534.24 })html=URL lib 2 . urlopen(URL=req,timeout=10)。阅读()
有些网站使用cookies进行限制,主要涉及登录和当前限制。这个时候没有通用的方法,要看能不能做到自动登录,或者能不能分析cookies。
URL和内容处理。URL中的怪诞格式只能单独分析处理,每个catch算一个。例如,URL中可能有中文字符、相对路径以及回车和换行符。我们可以先用urlparse模块的urljoin函数来处理相对路径,然后去掉杂乱的回车和换行符,最后用urllib2的quote函数来处理特殊字符的编码和转义来生成真正的URL。
当然,在使用的过程中,你会发现Python的urljoin和urlopen存在一些问题,具体代码我们会在后面给出。
对于那些不管三七二十一就抛出gzip内容的人,让我们直接分析一下它的内容格式,就不去管HTTP头了。代码是这样的:
用Python C复制用v . 123456 import URL lib 2 import gzip,cStringIOhtml=URL lib 2 . URL open( http://blog . Raphael Zhang . com )。read()if html[:6]== \ x1f \ x8b \ x08 \ x00 \ x00 \ x00 :html=gzip。GzipFile(fileobj=cStringIO。StringIO(html))。阅读()
好了,现在又到了编码处理的痛苦时刻。由于我们是中国人,用的是汉字,所以一定要搞清楚一段文字用的是什么代码(f*ck),否则就会出现传说中的乱码。
按照一般浏览器的处理流程,判断网页编码首先是基于HTTP服务器发送的HTTP响应头中的Content-Type字段,比如text/html;Charset=utf-8表示这是一个HTML网页,使用utf8编码。如果HTTP头中没有charset属性,或者网页内容的头区域中有一个http-equiv属性为Content-Type的meta元素,如果有charset属性,则直接读取该属性,如果是后者,则读取该元素的Content属性。格式类似于上面的格式。
按理说,如果大家都按照规则来玩,编码问题就很容易解决了。但是,问题是,人不一定会遵守规则,走自己的捷径……(OMG)。首先,HTTP响应中不一定有Content-Type头。其次,在一些网页中可能没有内容类型或者有多个内容类型(比如百度搜索到的缓存页面)。这时候我们就要冒险去猜了,所以编码的一般流程是:
读取urlopen返回的对应HTTP对象的headers.dict[content-type]属性,读出charset,用正则表达式分析HTML中head区域对应的meta元素中Content-Type/charset属性的值,读出。如果上面两步得到的代码只有gb2312或者gbk,可以认为网页的代码是gbk(反正gbk是兼容gb2312的)。如果发现有多个编码,并且这些编码互不兼容,比如utf8和ios8859-1,那么我们就要使用chardet模块,调用chardet的detect函数,读取encoding属性来得到编码。使用decode方法可以将网页转码成Python中的unicode字符串,方便后续的统一处理。我的做法是,只要网页的编码是gb2312,我就假设是gbk。虽然很多网站写的是gb2312,其实是用gbk编码的。毕竟gb2312覆盖的字符数量太少,很多词,比如* Di、* * *,都没有被gb2312覆盖。
其次,chardet不是万能的。如果有现成的Content-Type/charset,您仍将使用Content-Type/charset。因为chardet用的是一种猜测的方法,主要是参考旧版Firefox代码中的猜测算法,根据一些代码的特征进行猜测。比如gb2312代码中“的”这个字对应的代码出现的频率可能比较高。在我的实际使用中,其出错的概率在10%左右。另外,需要很长时间的分析和猜测。
检测到的代码有点长,详细请参考这段代码中getencoding函数的实现。
提高性能网络爬虫的主要性能瓶颈是网络处理。在这方面,可以使用cProfile.run和pstats。Stats来测试每个函数的调用时间,可以验证。一般来说,可以通过以下方法解决:
使用线程或多进程并行处理并同时抓取多个页面非常简单。在文档中,只需在HTTP请求中添加Accept-Encoding头,并将其设置为gzip,这意味着可以接受gzip压缩的数据。大部分网站支持gzip压缩,可以节省70 ~ 80%的流量。如果不需要读取全部内容,可以在HTTP请求中添加Range头(HTTP断点续传也需要这个头)。比如将Range头的值设置为bytes=0-1023,相当于请求的前1024个字节的内容,可以大大节省带宽。但是,少数网站不支持此标题。需要注意的是,调用urlopen时,必须设置超时参数。否则,程序可能会永远在那里等待并行处理。就我现在测试来看,差别不大。毕竟主要的瓶颈还是网络链接。
事实上,除了上述方法,Python中还有一些方法可能会更好地提高性能。比如可以使用greenlet、stackless、PyPy等支持更好的多线程多进程或者python等工具,也可以使用twisted或者PycURL等异步IO。但是我对greenlet不熟悉,觉得twisted太扭了,PycURL不是python,而stackless和pypy又怕破坏其他python程序,所以还是用了urllib2线程方案。当然,因为GIL的问题,python多线程还是不够快,但是对于单线程的情况,已经节省了好几倍的时间。
Python的小问题在抓取一个网页的时候,Python暴露了一些小问题。主要是urlopen和urljoin函数的问题。
urlparse模块中的Urljoin函数,其功能是转换一个相对url,如./img/mypic.png,加上当前页面的绝对url,比如http://blog.raphaelzhang.com/apk/index.html,变成绝对URL,比如本例中的http://blog.raphaelzhang.com/img/mypic.png。但是,urljoin处理的结果需要进一步处理,即删除冗余.路径和特殊符号,如回车和换行符。代码是这样的:
计算机编程语言
12345678从urlparse导入urljoin
Url=././img/\ n mypic . png absurl= 3358blog.raphaelzhang.com/2012/URL=URL join(absurl,relurl) # URL是http://blog.raphaelzhang.com/./img/\ n typic . png
Url=reduce (lambda r,x: r.replace (x [0],x [1]),[(/./,/),( \ n ,),( \ r , )],URL) #
Urlopen函数也有一些问题。其实它对url字符串有自己的要求。首先,你给urlopen的函数需要自己做汉字等特殊字符的编码工作。只需使用urllib2的quote函数,就像这样:
计算机编程语言
234导入urllib2 #这里的url当然是经过上述urljoin和reduce处理后的好的绝对URL。
URL=URL lib 2 . quote(URL . split( # )[0]。encode(utf8 ),safe=%/:=?~# !$,@()*[])
其次,url中一定不能有#。因为理论上,#后面是fragment,是用来获取整个文档后定位的,不是用来获取文档的。但其实urlopen自己应该可以做到这一点,只是把这个任务留给了开发者。我已经用上面的URL.split (#) [0]处理过这个问题了。
最后,Python 2.6及之前的版本不能处理类似的http://blog.raphaelzhang.com?id=123这样的url会导致运行时错误,所以我们必须手动处理它,将这个url转换为普通的http://blog.raphaelzhang.com/?(id=123这样的url也可以),但这个错误在Python 2.7中已经解决了。
好了,Python网页抓取的以上问题告一段落。接下来要处理的是分析网页的问题,后面会分解。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。