urlib2库的基本使用

所谓⽹⻚抓取,就是把 URL 地址中指定的⽹络资源从⽹络流中读取出来,保存到本地。 在 Python 中,我们使⽤ urllib2 这个组件来抓取⽹⻚。

urllib2 是 Python2.7 ⾃带的模块(不需要下载),
是 Python 的⼀个获取URLs(Uniform Resource Locators)的重要组件。

urllib2 官⽅⽂档:https://docs.python.org/2/library/urllib2.html

urllib2 源码:https://hg.python.org/cpython/file/2.7/Lib/urllib2.py

urllib2 在 python3.x 中被改为 urllib.request

1.urlopen

我们先来段代码:

1
2
3
4
5
# urllib2_baidu.py
import urllib2
response = urllib2.urlopen("http://www.baidu.com")
html = response.read()
print html

So Easy! 最简单的获取⼀个 url 的⻚⾯代码居然只需要 4⾏! 执⾏写的
python 代码:

1
Power@PowerMac ~$: python urllib2_baidu.py

会看到以下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html><!--STATUS OK--><html><head><meta http-equiv="conten
t-type" content="text/html;charset=utf-8"><meta http-equiv="X-UA-Co
mpatible" content="IE=Edge"><meta content="always" name="referrer">
<meta name="theme-color" content="#2932e1"><link rel="shortcut ico
n" href="/favicon.ico" type="image/x-icon" /><link rel="search" typ
e="application/opensearchdescription+xml" href="/content-search.xm
l" title="百度搜索" /><link rel="icon" sizes="any" mask href="//www.
b
aidu.com/img/baidu.svg"><link rel="dns-prefetch" href="//s1.bdstati
c.com"/><link rel="dns-prefetch" href="//t1.baidu.com"/><link re
l="dns-prefetch" href="//t2.baidu.com"/><link rel="dns-prefetch" hr
ef="//t3.baidu.com"/><link rel="dns-prefetch" href="//t10.baidu.co
m"/><link rel="dns-prefetch" href="//t11.baidu.com"/><link rel="dn
s-prefetch" href="//t12.baidu.com"/><link rel="dns-prefetch" hre
f="//b1.bdstatic.com"/><title>百度⼀下,你就知道</title> ...

​ 实际上,如果我们在浏览器上打开百度主⻚, 右键选择“查看源代码”,你会发现,跟我们刚才打印出来的是⼀模⼀样。也就是说,上⾯的4⾏代码就已经帮我们把百度的⾸⻚的全部代码爬了下来。

分析代码: 我们来分析⼀下上⾯的这 4⾏代码:

1.第⼀⾏:

1
import urllib2

就是将 urllib2 组建引⼊进来,供给我们使⽤。

2.第⼆⾏

1
response = urllib2.urlopen("http://www.baidu.com")

然后我们调⽤的是 urllib2 库⾥⾯的 urlopen ⽅法,传⼊的 url⽹址是百度⾸⻚,
urlopen()⽅法⼀般接受三个参数:urlopen(url, data=None, timeout=)

第⼀个参数 URL 是必须要传送的,可以传⼊⼀个字符串类型的url 地址,同时打开这个 url 并返回⼀个像⽂件对象⼀样的对象。

第⼆个参数是 data 是经过编码的 post 数据(⼀般使⽤urllib.urlencode()来编码,我们后⾯会说到),默认为空None;第三个参数是 timeout 是可选的超时期(以秒为单位),供所有阻塞操作内部使⽤。默认为 60s,也可以直接设置 timeout=10

3.第三⾏

1
html = response.read()

urlopen()返回的⽂件对象,除了⽀持⽂件⽅法外,还⽀持下⾯的这 些常⽤的⽅法:

  • response.getcode() 返回整数形式的 HTTP 响应代码,⽐如成功返回 200,未找到⽂件时返回 404

  • response.geturl() 返回所返回的数据的实际 url,但是会考虑发⽣的重定向问题

  • response.info() 返回映射对象,该对象带有与 url 关联的信息,对 HTTP 来说,

  • 返回的服务器响应包含 HTTP 报头

4.第四⾏

1
print html

最后就是将字符串打出来,显示到终端上。

⼀个基本的 url 请求对应的 python 代码真的⾮常简单。

Request

我们编辑 urllib2_test2.py

1
2
3
4
5
6
# urllib2_request.py
import urllib2
request = urllib2.Request("http://www.baidu.com")
response = urllib2.urlopen(request)
html = response.read()
print html



运⾏结果是完全⼀样的:

在我们第⼀个例⼦⾥,urlopen()的 url 参数就是⼀个 url 地址;但是如果需要执⾏更复杂的操作,⽐如增加 HTTP 报头,可以创建⼀个Request 实例来作为 urlopen()的 url 参数,⽽url 地址则作为 Request 实例 的参数。


新建 Request 实例,url 为 url 字符串,data 是伴随 url 提交的数据(⽐如要 post 的数据),headers 是⼀个字典,包含了可表示 HTTP 报头的键值对,注意,data 请求为空时,默认 HTTP 请求为"GET",提供 data 参数时,HTTP 请求将从"GET"改为‘POST’。


2.User-Agent

但是这样直接⽤python的urllib2给⼀个⽹站发送请求的话,确实略有些唐突了,就好⽐,⼈家每家都有⻔,你以⼀个路⼈的身份直接闯进去显然不是很礼貌。所以有⼀些站点不喜欢被程序(⾮⼈为访问)访问,有可能会拒绝你的访问请求。

​ 但是如果我们⽤⼀个合法的身份去请求别⼈⽹站,显然⼈家就是欢迎的。



所以我们就应该给我们的这个代码加上⼀个身份,就是所谓的 User-Agent 头。

User-Agent?显然如果你不是学习前端专业的,这个东⻄确实对于后端开发⼯程师是⼀个头疼的东⻄,
不过不要紧,不是我们的东⻄我们只作为了解即可。

我们只需要知道,⽤ 不同的浏览器 在发送请求的时候,会有不同的 UserAgent 头。

浏览器 就是互联⽹世界上 被允许的身份 。 那么如果你不想你的爬⾍代码成为⼀个路⼈,你需要伪装成⼀个被 公认的浏览器 。伪装的办法就是给⾃⼰的请求加上⼀个对应的 User-Agent 头。


1
2
3
4
5
6
7
8
9
10
#urllib2_useragent.py
import urllib2
url = "http://www.baidu.com"
#IE 9.0 的 User-Agent
header = {"User-Agent" : "Mozilla/5.0 (compatible; MSIE 9.0; Window
s NT 6.1; Trident/5.0;"}
request = urllib2.Request(url, headers = header)
response = urllib2.urlopen(req)
html = response.read()
print html



3.添加更多的 Header 信息

  • 在 HTTP Request 中加⼊特定的 Header,来构造⼀个完整的 HTTP 请求消息。

  • 可以通过调⽤ Request.add_header() 添加/修改⼀个特定的 header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# urllib2_headers.py
import urllib2
url = "http://www.baidu.com"
#IE 9.0 的 User-Agent
header = {"User-Agent" : "Mozilla/5.0 (compatible; MSIE 9.0; Window
s NT 6.1; Trident/5.0;"}
request = urllib2.Request(url, headers = header)
#也可以通过调⽤Request.add_header() 添加/修改⼀个特定的 header
request.add_header("Connection", "keep-alive")
# 也可以通过调⽤Request.get_header()来查看 header 信息
# request.get_header(header_name="Connection")
response = urllib2.urlopen(req)
print response.code #可以查看响应状态码
html = response.read()
print html



headers 的⼀些属性,需要特别注意⼀下:

  • User-Agent : 有些服务器或 Proxy 会通过该值来判断是否是浏览器发出的请求;

  • Content-Type : ⽤来确定 HTTP Body 中的内容该怎样解析,

    服务 器会检查该值,设置错误会导致服务器拒绝服务

  • application/xml : 在 XML RPC 调⽤时使⽤

  • application/json : 在 JSON RPC 调⽤时使⽤

  • application/x-www-form-urlencoded : 浏览器提交 Web 表单时使⽤



4.数据传送

上⾯演示的都是最基本的⽹⻚抓取,有时候我们也希望发送⼀些数据到URL,

⽐如账号密码、表单数据等等,这样也能得到相应的响应。

urllib2 默认只⽀持 HTTP 的 GET 和 POST ⽅法

Get⽅式

GET 请求⼀般⽤于我们向服务器获取数据,⽐如说,我们⽤百度搜索 老男孩

https://www.baidu.com/s?wd= 老男孩

在其中我们可以看到在 http://www.baidu.com/s? 之后出现⼀个⻓⻓的字符串,其中就包含我们要查询的关键词。通过 Fiddler 观察,发现 URL 的QueryString 查询字符串的键是 wd ,于是我们可以尝试⽤默认的 Get⽅式来 发送请求。


1
2
3
4
5
6
7
8
9
10
11
12
13
# urllib2_get.py
import urllib #负责 url 编码处理
import urllib2
url = "http://www.baidu.com/s"
word = {"wd":"老男孩"}
word = urllib.urlencode(word) #转换成 url 编码格式(字符串)
newurl = url + "?" + word # url⾸个分隔符就是 ?
headers={ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) Apple
WebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.3
6"}
request = urllib2.Request(newurl, headers=headers)
response = urllib2.urlopen(request)
print response.read()

⼀般 HTTP 请求提交 HTML 表单数据,word 需要编码成 URL 编码格式,然后 做为参数传到 Request 对象。
urllib 和 urllib2 都是接受 URL 请求的相关模块,但是提供了不同的功能。

两个最显著的不同如下:

  • urllib 仅可以接受 URL,⽽ urllib2 可以接受⼀个设置了 headers 的Request 类实例。这表示我们可以伪装⾃⼰的 User Agent 字符串等。

  • urllib 提供 urlencode ⽅法⽤来 GET 查询字符串的产⽣,⽽ urllib2没有。这是为何 urllib 常和 urllib2 ⼀起使⽤的原因。

  • 编码⼯作使⽤urllib 的 urlencode() 函数,帮我们将 key:value 这样的键值对转换成 “key=value” 这样的字符串,解码⼯作可以使⽤urllib 的 unquote() 函数。(注意,不是 urllib2.urlencode() )


1
2
3
4
5
6
7
8
9
10
# IPython2 中的测试结果
In [1]: import urllib
In [2]: word = {"wd":"老男孩"}
# 将字典按 URL 编码转换,汉字部分先转成 GBK 编码,然后把 \x 替换成 %
In [3]: urllib.urlencode(word)
Out[3]: "wd=%E4%BC%A0%E6%99%BA%E6%92%AD%E5%AE%A2"
# 把 % 替换成 \x,变回 GBK 编码,打印出来
In [4]: print urllib.unquote("wd=%E4%BC%A0%E6%99%BA%E6%92%AD%E5%AE%
A2")
wd=老男孩

**POST⽅式: **

上⾯我们说了 Request 请求对象的⾥有 data 参数,它就是⽤在 POST⾥的,我 们要传送的数据就是这个参数 data,data 是⼀个字典,⾥⾯要匹配键值对。

拿拉勾⽹站数据举例,https://www.lagou.com/ 在站内搜索任意关键字 。 输⼊测试数据,再通过使⽤Fiddler 观察,其中有⼀条是 POST 请求,响应⽂件是 JSON 格式⽂件,⽽向服务器发送的请求数据并不是在 url⾥,那么我们可以试着模拟这个 POST 请求。


于是,我们可以尝试⽤POST⽅式发送请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# urllib2_post.py
import urllib2
import urllib
output = open("lagou.json", "w")
page = 1
# POST 请求要传送的数据
formdata = "first=false&pn=" + str(page) + "&kd=xxx"
headers={ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) Apple
WebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.3
6"}
request = urllib2.Request("http://www.lagou.com/jobs/positionAjax.j
son?px=new&needAddtionalResult=false", headers=headers)
# 通过 request.add_data() 将 data 数据传进⼊request 内
request.add_data(formdata)
#print request.get_data()
response = urllib2.urlopen(request)
print response.code
#print resHtml
output.write(response.read())
output.close()

当然可以⽤post 的⽅式发送账号密码到登录界⾯模拟登陆,当⽹⻚采⽤JavaScript 动态技术以后,想封锁基于 HttpClient 的模拟登录就太容易了,甚⾄可以根据你的⿏标活动的特征准确地判断出是不是真⼈在操作。

所以,想做通⽤的模拟登录还得选别的技术,⽐如⽤内置浏览器引擎的爬⾍(关键词:Cookie,PhantomJS,Selenium),这个我们将在以后会学习到。

问题:为什么有时候 POST 也能在 URL 内看到数据?

  • GET⽅式是直接以链接形式访问,链接中包含了所有的参数,服务器端⽤Request.QueryString获取变量的值。如果包含了密码的话是⼀种不安全的选择,不过你可以直观地看到⾃⼰提交了什么内容。

  • POST则不会在⽹址上显示所有的参数,服务器端⽤Request.Form获取提交的数据,在 Form 提交的时候。但是 HTML 代码⾥如果不指 定 method 属性,则默认为 GET 请求,Form 中提交的数据将会附加 在 url 之后,以 ? 分开与 url 分开。

  • 表单数据可以作为 URL 字段(method=“get”)或者 HTTP POST(method=“post”)的⽅式来发送。⽐如在下⾯的 HTML 代码中,表单数据将因为 (method=“get”) ⽽附加到 URL 上:

1
2
3
4
5
<form action="form_action.asp" method="get">
<p>First name: <input type="text" name="fname" /></p>
<p>Last name: <input type="text" name="lname" /></p>
<input type="submit" value="Submit" />
</form>



5.⾃定义 Opener

  • 基本的 urlopen()函数不⽀持代理、cookie 或其他的 HTTP⾼级功能。要⽀持这些功能,

    必须使⽤ build_opener() 函数来创建⾃⼰的⾃定义opener 对象。

  • opener 是 urllib2.OpenerDirector 的实例,我们之前⼀直都在使⽤的urlopen,它是⼀个特殊的 opener

  • install_opener 将⾃定义的 opener 对象 定义为 全局 opener,表示如果之后凡是调⽤urlopen,都将使⽤这个 opener(根据⾃⼰的需求来选择)



6.Proxy(代理)的设置

很多⽹站会检测某⼀段时间某个 IP 的访问次数,如果访问次数过多,它会禁⽌你的访问。所以我们可以设置⼀些代理服务器,每隔⼀段时间换⼀个代理,⽹站管理员就不知道是谁在捣⻤了。

urllib2 中通过 ProxyHandler 来设置使⽤代理服务器,下⾯代码说明如何实⽤⾃定义 opener 来使⽤代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#urllib2_proxy.py
import urllib2
proxyWork = True #定义⼀个代理开关
# 定义了两个代理模式,其中⼀个为不适⽤代理
httpProxyHandler = urllib2.ProxyHandler({"http" : "124.88.67.81:80"
})
nullProxyHandler = urllib2.ProxyHandler({})
if proxyWork: #根据代理开关是否打开,使⽤不同的代理模式
opener = urllib2.build_opener(httpProxyHandler)
else:
opener = urllib2.build_opener(nullProxyHandler)
# 如果这么写,之后的 urlopen 将使⽤这个 opener
#urllib2.install_opener(opener)
#response = urlopen("http://www.baidu.com/")
# 使⽤我们⾃定义的代理 opener 的 open()⽅法打开 url
response = opener.open("http://www.baidu.com/")
html = response.read()
print html



7.Debug Log

使⽤ urllib2 时,可以通过下⾯的⽅法把 HTTP 和 HTTPS 的 debug Log 打开,这样程序在执⾏的时候,会把收发包的内容在屏幕上打印出来,⽅便调试,有时可以省去抓包的⼯作 。

1
2
3
4
5
6
7
8
9
10
11
12
# urllib2_debuglog.py
import urllib2
# 打开 HTTP debug log
httpHandler = urllib2.HTTPHandler(debuglevel=1)
# 打开 HTTPS debug log
httpsHandler = urllib2.HTTPSHandler(debuglevel=1)
# 同时使⽤两种不同的 debug log 模式
opener = urllib2.build_opener(httpHandler, httpsHandler)
# 使⽤install_opener ⽤来创建全局的 opener
urllib2.install_opener(opener)
# urlopen() 默认使⽤之前创建的全局 opener
response = urllib2.urlopen("http://www.baidu.com")



8.Cookie

Cookie 是指某些⽹站的 Web 服务器为了辨别⽤户身份和进⾏Session 跟踪⽽储存在⽤户浏览器上的⽂本⽂件,Cookie 可以保持登录信息到⽤户下次与服务器的会话。

Cookie 由变量名和值组成,根据 Netscape 公司的规定,Cookie 格式如下:Set-Cookie: NAME=VALUE;Expires=DATE;Path=PATH;Domain=DOMAIN_NAME;

SECURE但是注意:

  1. 登录⼀般都会先有⼀个 HTTP GET,⽤于拉取⼀些信息及获得Cookie,然后再 HTTP POST 登录。
  2. http POST 登录的链接有可能是动态的,从 GET 返回的信息中获取。
  3. password 有些是明⽂发送,有些是加密后发送,有些甚⾄⽤动态加密的,包括了很多其他数据的加密信息,不只是密码。能通过查看JS 源码获得加密算法。
  4. ⼤多数⽹站的登陆整体流程类似,可能有些细节不⼀样,所以不能
    保证其他⽹站登录成功。

cookielib 库

cookielib 模块的主要作⽤是提供⽤于存储 cookie 的对象,⼀般与 urllib2 模块配 合使⽤,Python 处理 cookie 是⼀般是 cookielib 和 HTTPCookieProcessor⼀ 起使⽤。

该模块主要的对象有 CookieJar、FileCookieJar、MozillaCookieJar、LWPCookieJar。

它们的关系:CookieJar —-派⽣—-> FileCookieJar(Cookie⽂件保存) —-派⽣—–> MozillaCookieJar(Firefox 浏览器 Cookie) 和 LWPCookieJar。


CookieJar

  • 管理 HTTP cookie 值、存储 HTTP 请求⽣成的 cookie、向传出的 HTTP请求添加 cookie 的对象。整个 cookie 都存储在内存中,对 CookieJar 实例进⾏垃圾回收后 cookie 也将丢失。

FileCookieJar (filename,delayload=None,policy=None)

  • 创建 FileCookieJar 实例,检索 cookie 信息并将 cookie 存储到⽂件中。filename 是存储 cookie 的⽂件名。delayload 为 True 时⽀持延迟访问访问⽂件,即只有在需要时才读取⽂件或在⽂件中存储数据

MozillaCookieJar (filename,delayload=None,policy=None)

  • 创建与 Mozilla 浏览器 cookies.txt 兼容的 FileCookieJar 实例。

LWPCookieJar (filename,delayload=None,policy=None)

  • 创建与 libwww-perl 的 Set-Cookie3⽂件格式兼容的 FileCookieJar 实例。

1)使⽤get⽅式获取 Cookie 保存到变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# urllib2_cookielibtest1.py
import urllib2
import cookielib
#声明⼀个 CookieJar 对象实例来保存 cookie
cookie = cookielib.CookieJar()
#利⽤urllib2 库的 HTTPCookieProcessor 对象来创建 cookie 处理器
handler=urllib2.HTTPCookieProcessor(cookie)
#通过 handler 来构建 opener
opener = urllib2.build_opener(handler)
#此处的 open⽅法同 urllib2 的 urlopen⽅法,也可以传⼊request
response = opener.open("http://www.baidu.com")
# 按标准格式存储 Cookie
cookies = ""
for item in cookie:
cookies = cookies + item.name + "=" + item.value + ";"
# 舍去最后⼀位的分号
print cookies[:-1]

我们使⽤以上⽅法将 cookie 保存到变量中,然后打印出了 cookie 中的值,运⾏结果如下:

1
2
3
BAIDUID=4327A58E63A92B73FF7A297FB3B2B4D0:FG=1;BIDUPSID=4327A58E63A9
2B73FF7A297FB3B2B4D0;H_PS_PSSID=1429_21115_17001_21454_21409_21554_
21398;PSTM=1480815736;BDSVRTM=0;BD_HOME=0

2) 访问⽹站获得 cookie,并把获得的 cookie 保存在 cookie⽂件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# urllib2_cookielibtest2.py
import cookielib
import urllib2
# 设置保存 cookie 的⽂件,同级⽬录下的 cookie.txt
filename = 'cookie.txt'
# 声明⼀个 LWPCookieJar(有 save 实现)对象实例来保存 cookie,之后写⼊⽂件
cookie = cookielib.LWPCookieJar(filename)
# 利⽤urllib2 库的 HTTPCookieProcessor 对象来创建 cookie 处理器
handler = urllib2.HTTPCookieProcessor(cookie)
# 通过 handler 来构建 opener
opener = urllib2.build_opener(handler)
# 创建⼀个请求,原理同 urllib2 的 urlopen
response = opener.open("http://www.baidu.com")
# 保存 cookie 到⽂件,且忽略 cookie 失效限制
cookie.save(ignore_discard=True, ignore_expires=True)

3) 从⽂件中获取 cookies 并访问

1
2
3
4
5
6
7
8
9
10
11
12
13
# urllib2_cookielibtest2.py
import cookielib
import urllib2
# 创建 LWPCookieJar(有 load 实现)实例对象
cookie = cookielib.LWPCookieJar()
# 从⽂件中读取 cookie 内容到变量,忽略 cookie 的使⽤时效
cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True)
# 创建请求的 request
req = urllib2.Request("http://www.baidu.com")
# 利⽤urllib2 的 build_opener⽅法创建⼀个 opener
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
response = opener.open(req)
print response.read()