IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    使用python编写简单网络爬虫技巧总结

    armsword发表于 2014-11-22 14:59:53
    love 0

    使用python写过几个小玩具,基本上都与爬虫、Web相关的,自己也积累了下经验,也看过很多文章,一直想总结下,可惜现在要忙于找工作,实验室活也比较多,也就落下了。感觉如果时间长不记录,也就懒散了,并且许多东西我写完过个两天,我自己都记不住当时怎么想的了。

    0、HTTP协议

    基本上常见的Web开发里,Web内容都是通过HTTP协议进行传输的(虽然咱不懂Web开发,但是基本的计算机网络知识还是了解的),通过TCP连接服务器的80端口,爬虫其实质就是通过模拟浏览器发送HTTP请求,至于HTTP请求相关知识,点击这里。

    1
    2
    3
    4
    HTTP通常通过创建到服务器80端口的TCP连接进行通信
    HTTP协议的内容包括请求方式(method), url,header,body,通常以纯文本方式发送
    HTTP返回内容包括状态码,header,body,通常以纯文本方式返回
    header以及body间以CRLF(\r\n)分割

    1、最基础的抓取网站内容

    使用python编写一个网络爬虫是非常简单的,如下例所示:

    1
    2
    import urllib2
    content = urllib2.urlopen('http://armsword.com').read()

    但是实际工作中,这样肯定是不行的,你会遇到各种各样的问题,如下:

    • 网络出现错误,任何错误都可能。例如机器宕了,网线断了,域名出错了,网络超时了,页面没有了,网站跳转了,服务被禁了,主机负载不够了…
    • 服务器加上了限制,只让常见浏览器访问
    • 需要登录才能抓取数据
    • IP被封掉或者IP访问次数受到限制
    • 服务器加上了防盗链的限制
    • 某些网站不管你HTTP请求里有没有Accept-Encoding头部,也不管你头部具体内容是什么,反正总给你发gzip后的内容
    • URL链接千奇百怪,带汉字的也罢了,有的甚至还有回车换行
    • 某些网站HTTP头部里有一个Content-Type,网页里有好几个Content-Type,更过分的是,各个Content-Type还不一样,最过分的是,这些Content-Type可能都不是正文里使用的Content-Type,从而导致乱码
    • 需要抓取的数据很多,怎么提高抓取效率
    • 网站内容通过AJAX请求加载的
    • 需要验证码识别
      至于怎么解决上述问题,我们一一道来。

    2、网络或网站出现错误等处理

    之前我搞过百度音乐的爬虫,因为当时百度音乐有个bug,就是可以任何人都下载百度音乐的无损、高品质音乐,我就写了个爬虫,开了几十个进程抓取下载地址(抓包解析JSON串数据)存储到数据库里,虽然百度无ip访问限制这些麻烦事,但是程序无任何错误处理的情况下,依然出现各自异常错误,所以我们只需要捕捉异常,处理异常就可以了。我们可以根据urlopen的返回值,读取它的HTTP状态码。除此之外,我们在urlopen的时候,也需要设置timeout参数,以保证处理好超时。

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

    3、服务器做了限制

    3.1、防盗连限制

    某些站点有所谓的反盗链设置,其实说穿了很简单,就是检查你发送请求的header里面,referer站点是不是他自己,我们只要把headers的referer改成该网站即可,以V2EX的领取每日奖励地址为例:

    1
    2
    3
    4
    header = {'Referer':'http://www.v2ex.com/signin'}
    req = urllib2.Request('http://www.v2ex.com',headers = header)
    response = urllib2.urlopen(url = req,timeout = 10).read
    #response = urllib2.urlopen(req)).read()

    3.2、限制浏览器登录,需伪装成浏览器

    某些网站做了限制,进制爬虫的访问,此时我们可以更改HTTP的header,与3.1相似:

    1
    2
    3
    header = {"User-Agent":"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36
    (KHTML, like Gecko) Ubuntu Chromium/31.0.1650.63 Chrome/31.0.1650.63 Safari/537.36"}
    req = urllib2.Request(url,headers=header)

    3.3、IP访问次数受到限制

    如果IP被封掉、或者访问次数受到限制,使用代理服务器比较有用,代理IP可以自己抓取一些代理网站的数据:

    1
    2
    3
    4
    5
    import urllib2
    proxy_ip = urllib2.ProxyHandler({'http':'http://XX.XX.XX.XX:XXXX'}) #XX为代理IP和端口
    opener = urllib2.build_opener(proxy_ip, urllib2.HTTPHandler)
    urllib2.install_opener(opener)
    content = urllib2.urlopen('http://XXXX.com').read()

    4、需要登录才能抓取数据

    4.1、表单的处理

    抓包,Chrome或者Firefox+Httpfox浏览器插件就能办到,查看POST数据,模拟表单就可以了,这里不再细说,这里主要注意,在GET/POST一些数据的时候,需要对GET/POST的数据做些编码,即使用urlencode(),我们以北邮校园网关登录为例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    postdata = {
    'DDDDD': uname, #用户名
    'upass': u_pass, #密码
    'R1': 0, #其他参数
    'R2': 1,
    'para': 00,
    '0MKKey': 123456
    }
    en_url = urllib.urlencode(postdata)

    4.2、Cookie处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #获取一个cookie对象
    cookie = cookielib.CookieJar()
    #构建cookie处理器
    cookie_p = urllib2.HTTPCookieProcessor(cookie)
    #装载cookie
    opener = urllib2.build_opener(cookie_p)
    #注意此处后面的install_opener(opener),安装不同的opener对象作为urlopen() 使用的全局URL opener
    urllib2.install_opener(opener)
    content = urllib2.urlopen('http://armsword.com').read()

    如果同时需要代理和Cookie,这样就可以:

    1
    opener = urllib2.build_opener(proxy_ip, cookie_p, urllib2.HTTPHandler)

    如果上面未使用install_opener(opener),则使用:

    1
    response = opener.open(url).read()

    以V2EX每日签到为例,把上面的流程走一遍:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #全局变量装载Cookie省略
    def login():
    '''
    once值每次登录都不一样,在页面上可以看到
    也必需有http header
    '''
    req = urllib2.Request(url_login)
    once = get_info(req,'name','once')['value'] #省略
    postdata = {
    'u':username,
    'p':password,
    'once':once,
    #'next':"/"
    }
    header = {'Host':'www.v2ex.com','Origin':'http://www.v2ex.com',
    'Referer':'http://www.v2ex.com/signin',
    'User-Agent':"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36
    (KHTML, like Gecko) Ubuntu Chromium/32.0.1700.107 Chrome/32.0.1700.107 Safari/537.36"}
    data = urllib.urlencode(postdata)
    req = urllib2.Request(url_login,data,header)
    response = opener.open(req)

    5、gzip/deflate支持

    某些网站不管你请求头带不带Accept-Encoding:gzip,它返回的都是gzip压缩的内容,urlopen不会处理gzip压缩的内容,所以得到的就是乱码,以下为解决方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    f = urllib2.urlopen(url)
    headers = f.info()
    rawdata = f.read()
    if ('Content-Encoding' in headers and headers['Content-Encoding']) or \
    ('content-encoding' in headers and headers['content-encoding']):
    import gzip
    import StringIO
    data = StringIO.StringIO(rawdata)
    gz = gzip.GzipFile(fileobj=data)
    rawdata = gz.read()
    gz.close()

    6、URL编码和网页编码处理
    如果URL里含有中文,要对相应的中文进行编码,可以使用urllib.quote(‘要编码的字符串’),如下所示:

    1
    2
    3
    4
    http://www.google.com/#newwindow=1&q;=你好
    query = urllib.quote('你好')
    url = 'http://www.google.com/#newwindow=1&q;='+query
    response = urllib.urlopen(url).read()

    至于处理网页编码,按照一般浏览器的处理流程,判断网页的编码首先是根据HTTP服务器发过来的HTTP响应头部中Content-Type字段,例如text/html; charset=utf-8就表示这是一个HTML网页,使用的是utf8编码。如果HTTP头部中没有,或者网页内容的head区中有charset属性或者其http-equiv属性为Content-Type的meta元素,如果有charset属性,则直接读取这个属性,如果是后者,则读取这个元素的content属性。但是实际情况是许多网页不按照规矩出牌,首先,HTTP响应里不一定有Content-Type头部,其次某些网页里可能没有Content-Type或者有多个Content-Type(例如百度搜索的缓存页面)。所以,我的经验基本是自己多猜测几次吧。其实GBK、UTF-8占了绝大部分,使用xx.decode(‘gbk’,’ignore’).encode(‘utf-8’) (或相反)试试,其中ignore是指忽略非法字符。

    7、AJAX

    AJAX 是一种用于创建快速动态网页的技术。
    通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
    传统的网页(不使用 AJAX)如果需要更新内容,必需重载整个网页面。

    AJAX依然是HTTP请求,只是来自页面发起的另一个HTTP请求。我之前做百度音乐的时候,在chrome下的network选项分析每一个选项,找到对应的ajax数据,分析json,在正则、查找之类的。

    当然,也可以通过selenium、phantomjs、mechanize等模拟浏览器引擎构建一个真实的浏览器执行JS、渲染页面(在写这篇文章时,我还没用到过这些),这应该算是终极大招了吧。

    8、效率

    目前使用过多线程、多进程,多进程注意下僵尸进程。

    异步:用twisted进行异步I/O抓取,tornado(如果拿到实习offer后,我可能会抽出几天时间学习这个,我感觉我应该学习下web编程,纠结了下flask还是tornado,其实无论哪个一周完全可以入门了,python你懂的。)

    9、验证码

    图像识别方面的知识,好吧,等我学会后再写吧。。。

    根据我的经验,网络爬虫里网络带宽是主要瓶颈,上文只说了抓取,其实网页抽取依然有很多知识要做的,我现在会的都是些皮毛,要学的还很多,等拿到dream 公司的实习offer后,有空继续折腾下,现在主要精力是弥补自己的一些知识缺点和面试长考的问题。

    参考链接:

    http://blog.binux.me/2013/09/howto-crawl-web/

    http://blog.raphaelzhang.com/2012/03/issues-in-python-crawler/

    http://yxmhero1989.blog.163.com/blog/static/112157956201311821444664/



沪ICP备19023445号-2号
友情链接