urllib2库的基本使用
所谓网页抓取,就是把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
urlopen
我们先来段代码:
# urllib2_baidu.py
import urllib2
response = urllib2.urlopen("http://www.baidu.com")
html = response.read()
print html
So Easy! 最简单的获取一个url的页面代码居然只需要4行!
执行写的python代码:
Power@PowerMac ~$: python urllib2_baidu.py
会看到以下结果:
<!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行代码:
第一行:
import urllib2
就是将urllib2组建引入进来,供给我们使用。
第二行
response = urllib2.urlopen("http://www.baidu.com")
然后我们调用的是 urllib2 库里面的 urlopen 方法,传入的url网址是百度首页,urlopen()方法一般接受三个参数:
urlopen(url, data=None, timeout=<object object>)
第一个参数URL是必须要传送的,可以传入一个字符串类型的url地址,同时打开这个 url 并返回一个像文件对象一样的对象。
第二个参数是data是经过编码的post数据(一般使用urllib.urlencode()来编码,我们后面会说到),默认为空 None;
第三个参数是timeout是可选的超时期(以秒为单位),供所有阻塞操作内部使用。默认为 60s,也可以直接设置
timeout=10
第三行
html = response.read()
urlopen()返回的文件对象,除了支持文件方法外,还支持下面的这些常用的方法:
response.getcode() 返回整数形式的HTTP响应代码,比如成功返回200,未找到文件时返回404
response.geturl() 返回所返回的数据的实际url,但是会考虑发生的重定向问题
response.info() 返回映射对象,该对象带有与url关联的信息,对HTTP来说,返回的服务器响应包含HTTP报头
第四行
print html
最后就是将字符串打出来,显示到终端上。
一个基本的url请求对应的python代码真的非常简单。
Request
我们编辑urllib2_test2.py
# 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’。
User-Agent
但是这样直接用python的urllib2给一个网站发送请求的话,确实略有些唐突了,就好比,人家每家都有门,你以一个路人的身份直接闯进去显然不是很礼貌。所以有一些站点不喜欢被程序(非人为访问)访问,有可能会拒绝你的访问请求。
- 但是如果我们用一个合法的身份去请求别人网站,显然人家就是欢迎的。
- 所以我们就应该给我们的这个代码加上一个身份,就是所谓的
User-Agent
头。
User-Agent?显然如果你不是学习前端专业的,这个东西确实对于后端开发工程师是一个头疼的东西,不过不要紧,不是我们的东西我们只作为了解即可。
我们只需要知道,用 不同的浏览器 在发送请求的时候,会有不同的 UserAgent 头。
浏览器 就是互联网世界上 被允许的身份 。那么如果你不想你的爬虫代码成为一个路人,你需要伪装成一个被 公认的浏览器 。
伪装的办法就是给自己的请求加上一个对应的User-Agent头。
#urllib2_useragent.py
import urllib2
url = "http://www.itcast.cn"
#IE 9.0 的 User-Agent
header = {"User-Agent" : "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;"}
request = urllib2.Request(url, headers = header)
response = urllib2.urlopen(req)
html = response.read()
print html
添加更多的Header信息
在 HTTP Request 中加入特定的 Header,来构造一个完整的HTTP请求消息。
- 可以通过调用
Request.add_header()
添加/修改一个特定的header - 也可以通过调用
Request.get_header()
来查看已有的header。
# urllib2_headers.py
import urllib2
url = "http://www.itcast.cn"
#IE 9.0 的 User-Agent
header = {"User-Agent" : "Mozilla/5.0 (compatible; MSIE 9.0; Windows 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 表单时使用
进阶:数据传送
上面演示的都是最基本的网页抓取,有时候我们也希望发送一些数据到URL,比如账号密码、表单数据等等,这样也能得到相应的响应。
urllib2默认只支持HTTP的GET
和POST
方法
Get方式
GET请求一般用于我们向服务器获取数据,比如说,我们用百度搜索播客
:https://www.baidu.com/s?wd=汽车
浏览器的url会跳转成如图所示:
在其中我们可以看到在http://www.baidu.com/s?
之后出现一个长长的字符串,其中就包含我们要查询的关键词。通过Fiddler观察,发现URL的QueryString查询字符串的键是 wd
,于是我们可以尝试用默认的Get方式来发送请求。
# 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) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"}
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() )
# 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方式发送请求。
# 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) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"}
request = urllib2.Request("http://www.lagou.com/jobs/positionAjax.json?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 上:
<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>
自定义Opener
基本的urlopen()函数不支持代理、cookie或其他的HTTP高级功能。要支持这些功能,必须使用
build_opener()
函数来创建自己的自定义opener对象。opener是urllib2.OpenerDirector的实例,我们之前一直都在使用的urlopen,它是一个特殊的opener
install_opener
将自定义的 opener对象 定义为 全局opener,表示如果之后凡是调用urlopen,都将使用这个opener(根据自己的需求来选择)
Proxy(代理)的设置
很多网站会检测某一段时间某个IP的访问次数,如果访问次数过多,它会禁止你的访问。所以我们可以设置一些代理服务器,每隔一段时间换一个代理,网站管理员就不知道是谁在捣鬼了。
urllib2中通过ProxyHandler来设置使用代理服务器,下面代码说明如何实用自定义opener来使用代理:
#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
Debug Log
使用 urllib2 时,可以通过下面的方法把HTTP 和 HTTPS 的 debug Log 打开,这样程序在执行的时候,会把收发包的内容在屏幕上打印出来,方便调试,有时可以省去抓包的工作。
# 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")
Cookie
Cookie 是指某些网站的 Web 服务器为了辨别用户身份和进行Session跟踪而储存在用户浏览器上的文本文件,Cookie可以保持登录信息到用户下次与服务器的会话。
Cookie由变量名和值组成,根据Netscape公司的规定,Cookie格式如下:
Set-Cookie: NAME=VALUE;Expires=DATE;Path=PATH;Domain=DOMAIN_NAME;SECURE
但是注意:
- 登录一般都会先有一个HTTP GET,用于拉取一些信息及获得Cookie,然后再HTTP POST登录。
- http POST登录的链接有可能是动态的,从GET返回的信息中获取。
- password有些是明文发送,有些是加密后发送,有些甚至用动态加密的,包括了很多其他数据的加密信息,不只是密码。能通过查看JS源码获得加密算法。
- 大多数网站的登陆整体流程类似,可能有些细节不一样,所以不能保证其他网站登录成功。
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保存到变量
# 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中的值,运行结果如下:
BAIDUID=4327A58E63A92B73FF7A297FB3B2B4D0:FG=1;BIDUPSID=4327A58E63A92B73FF7A297FB3B2B4D0;H_PS_PSSID=1429_21115_17001_21454_21409_21554_21398;PSTM=1480815736;BDSVRTM=0;BD_HOME=0
2. 访问网站获得cookie,并把获得的cookie保存在cookie文件中
# 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并访问
# 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()