首页IT科技python selenium抓取网页内容(用Python抓网页的注意事项)

python selenium抓取网页内容(用Python抓网页的注意事项)

时间2025-09-19 08:23:41分类IT科技浏览7901
导读:用Python抓网页的注意事项...

用Python抓网页的注意事项

用Python编一个抓网页的程序是非常快的                 ,下面就是一个例子:

Python

import urllib2</p> <p>html = urllib2.urlopen(http://blog.raphaelzhang.com).read()

1
2
3
importurllib2
html=urllib2.urlopen(http://blog.raphaelzhang.com).read()

但是在实际工作中                        ,这种写法是远远不够的        ,至少会遇到下面几个问题:

网络会出错                 ,任何错误都可能                。例如机器宕了                        ,网线断了        ,域名出错了         ,网络超时了                        ,页面没有了                ,网站跳转了         ,服务被禁了                         ,主机负载不够了… 服务器加上了限制                ,只让常见浏览器访问 服务器加上了防盗链的限制 某些2B网站不管你HTTP请求里有没有Accept-Encoding头部,也不管你头部具体内容是什么                         ,反正总给你发gzip后的内容 URL链接千奇百怪                        ,带汉字的也罢了,有的甚至还有回车换行 某些网站HTTP头部里有一个Content-Type                 ,网页里有好几个Content-Type                        ,更过分的是        ,各个Content-Type还不一样                 ,最过分的是                        ,这些Content-Type可能都不是正文里使用的Content-Type        ,从而导致乱码 网络链接很慢         ,乘分析几千个页面的时间                        ,建议你可以好好吃顿饭去了 Python本身的接口有点糙

好吧                ,这么一大箩筐问题         ,我们来一个个搞定                         。

错误处理和服务器限制

首先是错误处理        。由于urlopen本身将大部分错误                         ,甚至包括4XX和5XX的HTTP响应                ,也搞成了异常,因此我们只需要捕捉异常就好了        。同时                         ,我们也可以获取urlopen返回的响应对象                        ,读取它的HTTP状态码                         。除此之外,我们在urlopen的时候                 ,也需要设置timeout参数                        ,以保证处理好超时                。下面是代码示例:

Python
import urllib2<br /> import socket</p> <p>try:<br /> f = urllib2.urlopen(http://blog.raphaelzhang.com, timeout = 10)<br /> code = f.getcode()<br /> if code < 200 or code >= 300:<br /> #你自己的HTTP错误处理<br /> except Exception, e:<br /> if isinstance(e, urllib2.HTTPError):<br /> print http error: {0}.format(e.code)<br /> elif isinstance(e, urllib2.URLError) and isinstance(e.reason, socket.timeout):<br /> print url error: socket timeout {0}.format(e.__str__())<br /> else:<br /> print misc error: + e.__str__()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
importurllib2
importsocket
try:
f=urllib2.urlopen(http://blog.raphaelzhang.com,timeout=10)
code=f.getcode()
ifcode<200orcode>=300:

#你自己的HTTP错误处理

exceptException,e:
ifisinstance(e,urllib2.HTTPError):
printhttp error: {0}.format(e.code)
elifisinstance(e,urllib2.URLError)andisinstance(e.reason,socket.timeout):
printurl error: socket timeout {0}.format(e.__str__())
else:
printmisc error: +e.__str__()

如果是服务器的限制        ,一般来说我们都可以通过查看真实浏览器的请求来设置对应的HTTP头部                 ,例如针对浏览器的限制我们可以设置User-Agent头部                        ,针对防盗链限制        ,我们可以设置Referer头部         ,下面是示例代码:

Python

import urllib2</p> <p>req = urllib2.Request(http://blog.raphaelzhang.com,<br /> headers = {"Referer": "http://www.baidu.com",<br /> "User-Agent": "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.68 Safari/534.24"<br /> })<br /> html = urllib2.urlopen(url = req, timeout = 10).read()

1
2
3
4
5
6
7
importurllib2
req=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=urllib2.urlopen(url=req,timeout=10).read()

有的网站用了Cookie来限制                        ,主要是涉及到登录和限流                ,这时候没有什么通用的方法         ,只能看能否做自动登录或者分析Cookie的问题了        。

URL与内容处理

URL里奇形怪状的格式只能个别分析                         ,个别处理                ,逮到一个算一个                         。例如针对URL里可能有汉字,相对路径                         ,以及回车换行之类的问题                        ,我们可以先用urlparse模块的urljoin函数处理相对路径的问题,然后去掉乱七八糟的回车换行之类的玩意                 ,最后用urllib2的quote函数处理特殊字符编码和转义的问题                        ,生成真正的URL                。

当然        ,在使用的过程中你会发现Python的urljoin和urlopen都有些问题                 ,因此具体的代码我们在后面再给。

对于那些不管三七二十一就把gzip内容丢过来的                        ,我们直接分析它的内容格式        ,不要管HTTP头部就好了                         。代码是这样的:

Python
使用 ⌘+C 复制         ,使用 ⌘+V 粘贴                         。
import urllib2<br /> import gzip, cStringIO</p> <p>html = urllib2.urlopen(http://blog.raphaelzhang.com).read()<br /> if html[:6] == \x1f\x8b\x08\x00\x00\x00:<br /> html = gzip.GzipFile(fileobj = cStringIO.StringIO(html)).read()
1
2
3
4
5
6
importurllib2
importgzip,cStringIO
html=urllib2.urlopen(http://blog.raphaelzhang.com).read()
ifhtml[:6]==\x1f\x8b\x08\x00\x00\x00:
html=gzip.GzipFile(fileobj=cStringIO.StringIO(html)).read()

好了                        ,现在又到了编码处理的痛苦时间了。由于我们是中国人                ,用的是汉字         ,因此必须要搞清楚一段文本使用的到底是什么编码(f*ck)                         ,不然就会出现传说中的乱码                。

按照一般浏览器的处理流程                ,判断网页的编码首先是根据HTTP服务器发过来的HTTP响应头部中Content-Type字段,例如text/html; charset=utf-8就表示这是一个HTML网页                         ,使用的是utf8编码                         。如果HTTP头部中没有                        ,或者网页内容的head区中有charset属性或者其http-equiv属性为Content-Type的meta元素,如果有charset属性                 ,则直接读取这个属性                        ,如果是后者        ,则读取这个元素的content属性                 ,格式与上述格式类似        。

按理来说                        ,如果大家都按规矩出牌        ,编码的问题是很好搞定的                。但是         ,问题就在于大家可能不按规矩办事                        ,走自己的捷径…(OMG)                         。首先                ,HTTP响应里不一定有Content-Type头部         ,其次某些网页里可能没有Content-Type或者有多个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覆盖的字符数太少         ,很多字                         ,例如*棣                 、陶喆                ,***这些词中都有字不在gb2312的覆盖范围内                         。

其次,chardet不是万能的                         ,如果有现成的Content-Type/charset                        ,就仍然使用Content-Type/charset                。因为chardet使用的是一种猜测方式来做的,主要是参考的老版本的火狐代码中的猜测算法                 ,就是根据某些编码的特征来猜测的        。例如gb2312编码中“的                ”这个字的对应的编码的出现频率可能比较高等                         。在我的实际使用中                        ,它出错的概率也有10%左右吧                。另外        ,因为它要分析和猜测                 ,所以时间也比较长。

检测编码的代码有点长                        ,具体可以参看这个代码里的getencoding函数的实现                         。

提高性能

网页抓取程序的主要性能瓶颈都在网络处理上                         。关于这一点        ,可以使用cProfile.run和pstats.Stats来测试一下每个函数的调用时间就可以得到验证了。一般来说         ,可以通过下面几种方法来解决:

使用threading或者multiprocess来并行处理                        ,同时抓取多个页面                ,使用都非常简单的         ,文档在这里 在HTTP请求里面加上Accept-Encoding头部                         ,将其设为gzip即可                ,表示可以接受gzip压缩的数据,绝大多数网站都支持gzip压缩                         ,可以节省70 ~ 80%的流量 如果不需要读取所有内容                        ,可以在HTTP请求里面加上Range头部(HTTP断点续传也需要这个头部的)                。例如把Range头部的值设为bytes=0-1023就是相当于请求开头1024个字节的内容,这样可以大大节省带宽                         。不过少数网站不支持这个头部                 ,这个需要注意下 调用urlopen的时候一定要设置timeout参数                        ,不然可能程序会永远等待在那里的

并行处理具体使用多线程还是多进程        ,就我现在测试来看区别不大                 ,毕竟主要瓶颈是在网络链接上        。

其实除了上述方法外                        ,在Python里还有一些可能更好的提高性能的方法                。例如使用greenlet        ,stackless         ,PyPy等支持更好多线程多进程的工具或python                        ,还可以使用异步IO                ,例如twisted或者PycURL                         。不过个人对greenlet不熟         ,觉得twisted实在太twisted                         ,PycURL不够pythonic                ,而stackless和pypy怕破坏了其他python程序,因此仍然使用urllib2 + threading方案        。当然                         ,因为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处理的结果还需要进一步处理                ,就是去掉多余的..路径         ,去掉回车换行等特殊符号                         。代码是这样的:

Python

from urlparse import urljoin</p> <p>relurl = ../../img/\nmypic.png<br /> absurl = http://blog.raphaelzhang.com/2012/<br /> url = urljoin(absurl, relurl)<br /> #url为<br /> url = reduce(lambda r,x: r.replace(x[0], x[1]), [(/../, /), (\n, ), (\r, )], url)<br /> #url为正常的http://blog.raphaelzhang.com/img/mypic.png

1
2
3
4
5
6
7
8
fromurlparseimport

urljoin

relurl=../../img/\nmypic.png
absurl=http://blog.raphaelzhang.com/2012/
url=urljoin(absurl,relurl)

#url为

url=reduce(lambdar,x:r.replace(x[0],x[1]),[(/../,/),(\n,),(\r,)],url)
#url为正常的http://blog.raphaelzhang.com/img/mypic.png

urlopen函数也有一些问题                         ,它其实对url字符串有自己的要求                。首先                ,你交给urlopen的函数需要自己做汉字等特殊字符的编码工作,使用urllib2的quote函数即可                         ,就像这样:

Python

import urllib2</p> <p>#此处的url当然是经过了上述urljoin和reduce处理过后的良好的绝对URL了<br /> url = urllib2.quote(url.split(#)[0].encode(utf8), safe = "%/:=&?~#+!$,;@()*[]")

1
2
3
4
importurllib2

#此处的url当然是经过了上述urljoin和reduce处理过后的良好的绝对URL了

url=urllib2.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才行                ,不过这个bug在Python 2.7里面已经解决了。

好了         ,Python网页抓取的上述问题告一段落了                         ,下面我们还要处理分析网页的问题                ,留待下文分解                。

声明:本站所有文章,如无特殊说明或标注                         ,均为本站原创发布                         。任何个人或组织                        ,在未征得本站同意时,禁止复制                        、盗用        、采集                 、发布本站内容到任何网站                        、书籍等各类媒体平台        。如若本站内容侵犯了原著者的合法权益                 ,可联系我们进行处理                。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
mac从哪里看系统版本(苹果Mac怎么查看系统版本?mac系统版本和配置情况查看教程图文介绍)