Introduction

编程水平太菜了,所以就想着最近一段时间系统学习下python爬虫,顺便自己编写下常见漏洞的poc脚本和AWD比赛用到的自动化脚本,一举多得。
参照大佬们的学习笔记和博客,很多都是硬搬来的,大佬勿喷。
从python最基础的库开始学习,本篇学习urllib模块和urllib2模块。

urllib、urllib & requests

先说一下urllib、urllib2、requests三者的关系,urllib和urllib2是python标准库,就是安装了python,这两个库就已经可以直接使用了;requests是第三方库,需要pip install 安装,但是功能是很强大的。

urllib和urllib2的区别:

  • urllib2的url参数不仅可以接受一个字符串url,还可以接受一个Request对象,并以此可以来设置一个URL的headers,但是urllib只接收一个URL,所以不能伪装用户代理。
  • urllib模块提供urlencode方法,该方法用于GET查询字符串的生成,urllib2的不具有这样的方法。所以urllib与urllib2经常在一起使用。

虽然requests模块容易安装,功能强大,上手简单,但是学习爬虫关于urllib和urllib模块的基础内容还是需要了解的,就从这两个模块开始python爬虫之旅。

urllib 模块

  • urllib.urlopen(url[,data[,proxies]])

打开一个url的方法,返回一个文件对象,然后可以进行类似文件对象的操作。
data: 表示用post的方式提交到url的数据
proxies: 用于设置代理

urlopen返回对象提供的方法:
read(),readline(),readlines(),fileno(),close(): 这些方法的使用方式和文件对象完全一样

info(): 返回一个httplib.HTTPMessage对象,表示远程服务器返回的头信息,和headers一样,响应头

getcode: 返回Http状态码。如果是http请求,200请求成功完成,404网址未找到

geturl: 返回请求的url

#! /usr/bin/env python
# -*-coding:utf-8
import urllib

url = 'http://www.baidu.com'
respones = urllib.urlopen(url)

print respones.geturl()
print respones.info()
print respones.getcode()
print respones.readline()
  • urllib.urlretrieve(url[,filename[,reporthook[,data]]])

urlretrieve:方法将url定位到的html文件下载到你本地的硬盘中。
如果不指定filename,则会存为临时文件。

reporthook:是一个回调函数,当连接上服务器、以及相应的数据块传输完毕时会触发该回调,可以利用这个回调函数来显示当前的下载进度。

data:指post到服务器的数据,默认是get,data必须是application/x-www-form-urlencoded格式。

urlretrieve():返回一个二元组(filename,mine_hdrs)。

#! /usr/bin/evn python
#! coding=utf-8
import urllib
def callbackfunc(blocknum, blocksize, totalsize):
    '''回调函数
    @blocknum:已下载的数据块数量
    @blocksize:数据块大小
    @totalsize:远程文件的大小
    '''
    percent = 100 * blocknum * blocksize / totalsize

    if percent > 100:
        percent = 100
    print '%.2f%%' % percent

url = 'http://www.baidu.com'
local = '.\\baidu.html'
urllib.urlretrieve(url, local, callbackfunc)
  • urllib.urlcleanup()

清除由于urllib.urlretrieve()所产生的缓存

  • urllib.quote(url)urllib.quote_plus(url)

将url数据获取之后,并将其编码,从而适用于url字符串中,使其能被打印和被web服务器接受。

  • urllib.unquote(url)urllib.unquote_plus(url)

解码和上面相反

  • urllib.urlencode(query)

将url中的键值对以连接符&划分
与urlopen结合可以实现post 和 get 方法

#! /usr/bin/evn python
#! coding=utf-8
import urllib

params = urllib.urlencode({"p1":"1","p2":"2","p3":"3"})
url = 'http://www.baidu.com/?%s' % params

print url
//http://www.baidu.com/?p2=2&p3=3&p1=1

urllib2 模块

urllib2模块定义的函数和类用来获取URL(主要是HTTP的),提供一些复制的接口用于处理

基本认证,重定向,Cookies等

  •  urllib2.urlopen(url[,data][,timeout])

这是最简单的传递url的方法,用法和urllib里的urlopen一样。

url:可以是一个字符串url或者是一个Request对象。

data:可以是指定的发给服务器的一条字符串类型的数据,当data参数提供时,HTTP的请求会自动变为POST。data是一个标准application/x-www-form-urlencoded格式的缓存。

timeout:阻塞操作以秒为单位,如尝试连接(如果没有指定,使用设置的全局默认timeout值),仅适用于HTTP,HTTPS,FTP。

#! /usr/bin/evn python
#! coding=utf-8

import urllib2
response = urllib2.urlopen('http://www.baidu.com/')
html = response.read()
print html

以上是最简单的直接传入url的方式,再看传入Request对象的例子:

#! /usr/bin/evn python
#! coding=utf-8

import urllib2
req = urllib2.Request('http://www.baidu.com/')
response = urllib2.urlopen(req)
html = response.read()
print html

返回效果是一样的,当然这个例子只实例化了对象,没有加入其他参数。

  • urllib2.Request(url[,data][,headers][,origin_req_host][,unverifiable])

Request类是一个抽象的URL请求。

url:是一个有效的url字符串。

data:该参数同上,该参数值数据标准是'application/x-www-form-urlencoded'格式,所以向一个URL发送数据时,这些数据需要被以标准的格式编码(encode),即调用urllib.urlencode(),然后作为一个数据参数传送给Request对象,然后再把对象传递给urlopen函数。

#! /usr/bin/evn python
#! coding=utf-8

import urllib
import urllib2

url = 'http://www.baidu.com'
values = {'p1':'a', 'p2':'b', 'p3':'c'}
data = urllib.urlencode(values)
req = urllib2.Request(url, data)
respones = urllib2.urlopen(req)
html = respones.read()

headers:字典类型,头字典可以作为参数在request时直接传入,也可以把每个键和值作为参数调用add_header()方法来添加。

#! /usr/bin/evn python
#! coding=utf-8

import urllib
import urllib2

url = 'http://www.baidu.com'
user_agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0'
values = {'p1':'a', 'p2':'b', 'p3':'c'}
headers = {'User-Agent':user_agent}
data = urllib.urlencode(values)
req = urllib2.Request(url, data,headers)
respones = urllib2.urlopen(req)
html = response.read()

标准的headers组成是(Content-Length, Content-Type and Host),只有在Request对象调用urlopen()(上面的例子也属于这个情况)或者OpenerDirector.open()时加入。以上例子是先初始化header再实例化对象,再看以下例子,先实例化对象,再利用add_header(key, val)方法加入header。

#! /usr/bin/evn python
#! coding=utf-8

import urllib
import urllib2

req = urllib2.Request('http://www.baidu.com')
req.add_header('Referer', 'http://www.example.com')
res = urllib2.urlopen(req)

OpenerDirector对象为每一个Request自动加上一个User-Agent header。urllib2.build_opener()会返回一个OpenerDirector对象。

#!/usr/bin/env python
# coding=utf-8

import urllib2
opener = urllib2.build_opener()
opener.addheaders = [('User-Agent', 'Mozilla/5.0')]
opener.open('http://www.baidu.com')

最后两个参数仅仅是对正确操作第三方HTTP cookies 感兴趣,很少用到:  

origin_req_host——是RFC2965定义的源交互的request-host。默认的取值是cookielib.request_host(self)。这是由用户发起的原始请求的主机名或IP地址。例如,如果请求的是一个HTML文档中的图像,这应该是包含该图像的页面请求的request-host。  

unverifiable ——代表请求是否是无法验证的,它也是由RFC2965定义的。默认值为false。一个无法验证的请求是,其用户的URL没有足够的权限来被接受。例如,如果请求的是在HTML文档中的图像,但是用户没有自动抓取图像的权限,unverifiable的值就应该是true。

  • urllib2.install_opener(opener)urllib2.build_opener([handler,...])

install_openerbuild_opener这两个方法通常在一起使用,有时候也用build_opener单独使用来获得OpenerDirector对象。

install_opener实例化会得到OpenerDirector对象,用来赋予全局变量opener。如果想用这个opener来调用urlopen,那么就必须实例化得到OpenerDirector,这样就可以简单的调用OpenerDirector.open()来代替urlopen()。

build_opener实例化也会得到OpenerDirector对象,其中参数handlers可以被BaseHandler或他的子类实例化。子类中可以通过以下实例化:ProxyHandler(如果检测代理设置用)扫描代理也会用到,UnknownHandler, HTTPHandler, HTTPDefaultErrorHandler, HTTPRedirectHandler, FTPHandler, FileHandler, HTTPErrorProcessor。

#!/usr/bin/env python
# coding=utf-8

import urllib2

r = urllib2.Request('http://www.baidu.com')
opener = urllib2.build_opener()
urllib2.install_opener(opener)
f = opener.open(r)

如上使用 urllib2.install_opener()设置 urllib2 的全局 opener。这样后面的使用会很方便,但不能做更细粒度的控制,比如想在程序中使用两个不同的 Proxy 设置等。比较好的做法是不使用 install_opener 去更改全局的设置,而只是直接调用 opener的open 方法代替全局的 urlopen 方法。

关于Opener和Handler之间的关系,当获取一个URL时,可以使用一个opener(一个urllib2.OpenerDirector实例对象,可以由build_opener实例化生成)。正常情况下程序一直通过urlopen使用默认的opener(也就是说当使用urlopen方法时,是在隐式的使用默认的opener 对象),但也可以创建自定义的openers(通过操作handlers创建的opener实例)。所有的重活和麻烦都交给这些handlers来做。每一个handler知道如何以一种特定的协议(http,ftp等等)打开url,或者如何处理打开url发生的HTTP重定向,或者包含的HTTPCookie。创建openers时如果想要安装特别的handlers来实现获取url(如获取一个处理cookie的opener,或者一个不处理重定向的opener)的话,先实例一个OpenerDirector对象,然后多次调用.add_handler(some_handler_instance)来创建一个opener。或者可以用build_opener,这是一个很方便的创建opener对象的函数,它只有一个函数调用 。build_opener默认会加入许多handlers,它提供了一个快速的方法添加更多东西和使默认的handler 失效。

install_opener如上所述也能用于创建一个opener对象,但是这个对象是(全局)默认的opener。这意味着调用urlopen将会用到你刚创建的opener。也就是说上面的代码可以等同于下面这段。这段代码最终还是使用的默认opener。一般情况下我们用build_opener为的是生成自定义opener,没有必要调用install_opener,除非是为了方便。

#!/usr/bin/env python
# coding=utf-8

import urllib2

r = urllib2.Request('http://www.baidu.com')
#创建opener对象
opener=urllib2.build_opener()
#定义全局默认opener
urllib2.install_opener(opener)
#urlopen使用默认opener,但是install_opener#已经把opener设为全局默认了,这里便是使用上面的建立的opener
f = urllib2.urlopen(r)

异常处理

当调用urllib2.urlopen的时候不会总是这么顺利,就像浏览器打开url时有时也会报错,所以就需要有应对异常的处理。说到异常,先来了解返回的response对象的几个常用的方法:

geturl() — 返回检索的URL资源,这个是返回的真正url,通常是用来鉴定是否重定向的

info() — 返回页面的原信息就像一个字段的对象, 如headers,它以mimetools.Message实例为格式(可以参考HTTP Headers说明)。

getcode() — 返回响应的HTTP状态代码,运行下面代码可以得到code=200 

#! /usr/bin/evn python
#! coding=utf-8

import urllib
import urllib2

req = urllib2.Request('http://www.baidu.com/')
response=urllib2.urlopen(req)
url=response.geturl()
info=response.info()
code=response.getcode()

当不能处理一个response时,urlopen抛出一个URLError(对于python APIs,内建异常如,ValueError, TypeError 等也会被抛出。)

HTTPError是HTTP URL在特别的情况下被抛出的URLError的一个子类。下面就详细说说URLErrorHTTPError

URLError——handlers当运行出现问题时(通常是因为没有网络连接也就是没有路由到指定的服务器,或在指定的服务器不存在)

抛出这个异常.它是IOError的子类.这个抛出的异常包括一个‘reason’ 属性,他包含一个错误编码和一个错误文字描述。如下面代码,request请求的是一个无法访问的地址,捕获到异常后我们打印reason对象可以看到错误编码和文字描述。

#! /usr/bin/evn python
#! coding=utf-8

import urllib2
req = urllib2.Request('http://www.baidu1.com/')
try:
    response=urllib2.urlopen(req)
except urllib2.URLError,e:
    print e.reason
    print e.reason[0]
    print e.reason[1]
  
 //[Errno 11002] getaddrinfo failed
//11002
//getaddrinfo failed

HTTPError——HTTPError是URLError的子类。每个来自服务器HTTP的response都包含status code. 有时status code不能处理这个request. 默认的处理程序将处理这些异常的responses。
例如,urllib2发现response的URL与你请求的URL不同时也就是发生了重定向时,会自动处理。对于不能处理的请求, urlopen将抛出 - - - HTTPError异常. 典型的错误包含‘404’ (没有找到页面), ‘403’ (禁止请求),‘401’ (需要验证)等。它包含2个重要的属性reason和code。

程序对于重定向时是默认处理的。

当一个错误被抛出的时候,服务器返回一个HTTP错误代码和一个错误页。你可以使用返回的HTTP错误示例。这意味着它不但具有code和reason属性,而且同时具有read,geturl,和info等方法,如下代码和运行结果。

#! /usr/bin/evn python
#! coding=utf-8

import urllib2
req = urllib2.Request('https://www.virtua1.cn/virtua1.html')
try:
    response=urllib2.urlopen(req)
except urllib2.HTTPError,e:
    print e.code
    print e.reason
    print e.geturl()
    print e.read()
    
 //404
//Not Found
//https://www.virtua1.cn/virtua1.html
//……