0. 请写下这一节课你学习到的内容:格式不限,回忆并复述是加强记忆的好方式!
今天我们决定在实战中来进行学习,会举两个例子,第一个例子是我们会下载一只猫,第二个例子是我们用Python来模拟浏览器通过在线的谷歌翻译进行文本的翻译。
如果你认为上节课我只是简单介绍了一下 urlopen() 函数的用法,那你就错了,上节课我已经说了,相关的文档在哪里,要教你的东西在文档里都有,OK,我们来第一个例子吧。
(一)使用Python下载一只猫
我们常说,林子大了,什么鸟都有。互联网这么大,那当然不管什么样的奇葩网站都会有。我们今天举的例子就是要访问一个 {placekitten} - Placeholder kitten images for developers,这个网站是为猫农量身定制的一个站点,网站后面你只需要加上 /宽度/高度,就可以得到一只相应宽度和高度的猫的图片。这些图片都是JPG格式的,你可以通过右键将其简单保存到桌面上。
我们第一个例子就是使用Python实现刚才的操作,事实上我们上节课教过的内容也是完全足够的,我们新建一个 download_cat.py 文件。
首先,我们需要 import urllib.request,然后使用urlopen() 函数得到 response,得到的 cat_img 可以用一个文件保存,我们命名这个文件为 cat_500_600.jpg,我们说过,图片也是文件,它也是二进制数据组成的,我们这里用 ‘wb’ 将收到的二进制数据写入 jpg 格式的文件就可以了。
-  #download_cat.py
-  import urllib.request
-  response = urllib.request.urlopen("http://placekitten.com/500/600")
-  cat_img = response.read()
-  with open('cat_500_600.jpg', 'wb') as f:
-  f.write(cat_img)
运行之后,就在桌面上有了一张 名为 cat_500_600.jpg 的图片。我们接着继续解释一下上面的代码:
上节课,我们说过,urlopen() 函数中的 url 参数可以是字符串,也可以是 Request object,其实,在上面的程序中,我们传入的是地址字符串,它也是将地址字符串转换为 Request 对象,然后再将对象传入 urlopen() 函数。因此,
response = urllib.request.urlopen("http://placekitten.com/500/600")等价于
-  req = urllib.request.Resquest("http://placekitten.com/500/600")
-  response = urllib.request.urlopen(req)
另外,urlopen() 函数返回的 response 其实是一个对象(object),看下图文档解释,因此你可以使用 read() 方法来读取内容,

文档还告诉我们,除了可以使用 read() 方法之外,还可以是使用 geturl() 、info() 和 getcode() 方法,我们试一下这三个函数分别返回什么:
我们运行 download_cat.py 之后,调用这几个方法:
-  >>>
-  =========== RESTART: C:\Users\XiangyangDai\Desktop\download_cat.py ===========
-  >>> response.geturl()
-  'http://placekitten.com/500/600'
-  >>> response.info()
-  <http.client.HTTPMessage object at 0x00000150F729AB70>
-  >>> print(response.info())
-  Date: Tue, 11 Dec 2018 06:57:33 GMT
-  Content-Type: image/jpeg
-  Content-Length: 20921
-  Connection: close
-  Set-Cookie: __cfduid=d2f9e8e46b6e9940463cf24baf0b7f0fb1544511453; expires=Wed, 11-Dec-19 06:57:33 GMT; path=/; domain=.placekitten.com; HttpOnly
-  Access-Control-Allow-Origin: *
-  Cache-Control: public, max-age=86400
-  Expires: Wed, 12 Dec 2018 06:57:33 GMT
-  CF-Cache-Status: HIT
-  Accept-Ranges: bytes
-  Vary: Accept-Encoding
-  Server: cloudflare
-  CF-RAY: 48760ec5d6fc99c1-LAX
-  >>> response.getcode()
-  200
geturl() 得到的就是你访问的具体的地址;
info() 得到的是一个 HTTPMessage 的对象,你可以将它打印出来,包含了 远程服务器返回的 Head 信息;
getcode() 返回的是 Http 的状态码,200 表示 OK,就是正常响应。
(二)利用在线有道翻译来翻译文本

我们怎样编写Python 程序模拟浏览器,让它翻译呢?我们首先要介绍的是审查元素这个功能。基本上现在所有的浏览器都会自带这样这个调试插件,以360浏览器为例,右键选择-审查元素,或者直接按 F12,就会显示审查元素窗口。

我们要看的是 Network 这一块,当我们点下自动翻译按钮时,在下面会看到有很多 Method,其中有 Get , 有Post ,这些内容都是浏览器与客户端的通信内容,在客服端与服务器之间进行请求的时候,两种最常用的方法:一种就是Get,一种就是 Post,在定义上来说,Get是指从服务器请求获得数据,而Post是向指定服务器提交被处理的数据,当然在现实情况中,Get也常常用作提交数据。但是我们这里有 Post,刚刚我们是提交数据,提交 I love you!这个语句让它翻译,我们点进去:

我们看到有 Headers 和 Preview 等,我们先看一下 Preview

我们看到这里有我们所需要的结果,说明我们就找对地方了,但是在编写程序之前,我们还是有必要讲解一下 Headers 中的内容:

Request URL:http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule,有人会认为 urlopen()函数打开的应该是 有道翻译_文本、文档、网页、在线即时翻译 这个地址,其实在内部嵌入的是前面的这个地址,你要实现翻译的机制是在这。
Request Method:POST,请求的方法是 Post 的形式。
Status Code:200 OK,状态码 200 表示正常响应。如果是 404 就是页面不见了。更多关于HTTP状态码的信息请查阅:
HTTP状态码大全
Remote Address 是服务器的 IP 地址加上打开的端口号。
Resquest Headers:是客服端(这里就是浏览器,用 Python代码的时候就是我们的代码)发送请求的Headers,这个常常用于服务端来判断是否非人类访问,什么意思呢?假设我们写一个 Python 代码,然后用这个代码批量的访问网站的数据,这样子,服务器的压力就很大了,所以呢,服务器一般是不欢迎非人类的访问的。一般我们就是使用Resquest Headers里面的User-Agent来识别是浏览器访问还是代码访问,大家可以看到,这里的User-Agent显示的系统的架构是(Windows NT 10.0; WOW64),后面你还包括浏览器的核心及其版本号等信息。如果你使用Python 访问的话,这个User-Agent默认就是 Python URL 3.5,这样就可能被屏蔽掉。(不过呢,如果服务器君以为这样就可以阻挡我们前进的脚步的话,他就太天真了,这个User-Agent是可以进行自定义的,嘻嘻,后面会给大家介绍)
Form Data:其实就是我们这个Post提交的主要内容,在 i 这里看到了提交的待翻译的内容。
介绍到这里就已经够用了,接下来看看文档,了解Python如何提交Post呢?
urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)Open the URL url, which can be either a string or a Request object.
data must be a bytes object specifying additional data to be sent to the server, or
Noneif no such data is needed. data may also be an iterable object and in that case Content-Length value must be specified in the headers. Currently HTTP requests are the only ones that use data; the HTTP request will be a POST instead of a GET when the data parameter is provided.data should be a buffer in the standard application/x-www-form-urlencoded format. The urllib.parse.urlencode() function takes a mapping or sequence of 2-tuples and returns an ASCII text string in this format. It should be encoded to bytes before being used as the data parameter.
上面蓝色文字已经写得很清楚了(这些内容来自urllib的Python文档的urllib.request部分),urlopen有一个data参数,如果这个参数被赋值,那么它就是以POST的形式取代GET的形式,也就是说,如果data = None的话,就默认是以GET的形式。这里还说了,data参数必须是基于application/x-www-form-urlencoded的格式,它还很贴心的告诉我们,你可以使用urllib.parse.urlencode()函数将字符串转换为所需要的形式。
事实上,我们有了这两段话的描述,我们就可以来写代码了:(命名为:translation.py)
-  #translation.py
-  import urllib.request
-  import urllib.parse
-  url = "http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule"
-  #直接从审查元素中copy过来的url会报错,必须把translate_o中的_o 删除才可以
-  #url = "http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule"
-  data = {} #这里就是把 Form Data 中的内容贴过来
-  data['i'] = '我爱你'
-  data['from'] = 'AUTO'
-  data['to'] = 'AUTO'
-  data['smartresult'] = 'dict'
-  data['client'] = 'fanyideskweb'
-  data['salt'] = '15445124815349'
-  data['sign'] = 'a824eba4c23c6f541ffadfee26b1e500'
-  data['ts'] = '1544512481534'
-  data['bv'] = 'bbb3ed55971873051bc2ff740579bb49'
-  data['doctype'] = 'json'
-  data['version'] = '2.1'
-  data['keyfrom'] = 'fanyi.web'
-  data['action'] = 'FY_BY_REALTIME'
-  data['typoResult'] = 'false'
-  #需要使用urllib.parse.urlencode() 把data转换为需要的形式
-  data = urllib.parse.urlencode(data).encode('utf-8')
-  response = urllib.request.urlopen(url, data)
-  html = response.read().decode('utf-8')
-  print(html)
运行结果为:
-  =========== RESTART: C:\Users\XiangyangDai\Desktop\translation.py ===========
-  {"type":"ZH_CN2EN","errorCode":0,"elapsedTime":0,"translateResult":[[{"src":"我爱你","tgt":"I love you"}]]}
结果倒是可以了,只是这样的结果是给程序员看的,如果是给用户看,那也太不友好了。(另外,如果大家对于编码还有什么困惑的,可以查看:Python编码问题的解决方案总结),我们打印出来的是一个字符串,有人就说,我们可以通过字符串查找的形式把 tgt 找出来,但这样太被动了。
其实,这是一个 json 结构,json 是一种轻量级的数据交换结构,说白了,这里就是用字符串的形式把 Python 的输出结果给封装起来,这个字符串里面包含的其实是一个字典,"translateResult" 里面的值是一个列表的列表的字典,我们可以使用下面的方法来解决:
-  =========== RESTART: C:\Users\XiangyangDai\Desktop\translation.py ===========
-  {"type":"ZH_CN2EN","errorCode":0,"elapsedTime":0,"translateResult":[[{"src":"我爱你","tgt":"I love you"}]]}
-  >>> import json
-  >>> json.loads(html)
-  {'errorCode': 0, 'type': 'ZH_CN2EN', 'elapsedTime': 0, 'translateResult': [[{'tgt': 'I love you', 'src': '我爱你'}]]}
-  >>> target = json.loads(html)
-  >>> type(target)
-  <class 'dict'>
-  >>> target['translateResult'][0][0]['tgt']
-  'I love you'
综上,我们就可以把我们的翻译程序美化一下:
-  #translation.py
-  import urllib.request
-  import urllib.parse
-  import json
-  content = input('请输入需要翻译的内容:')
-  url = "http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule"
-  #直接从审查元素中copy过来的url会报错,必须把translate_o中的_o 删除才可以
-  #url = "http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule"
-  data = {} #这里就是把 Form Data 中的内容贴过来
-  data['i'] = content
-  data['from'] = 'AUTO'
-  data['to'] = 'AUTO'
-  data['smartresult'] = 'dict'
-  data['client'] = 'fanyideskweb'
-  data['salt'] = '15445124815349'
-  data['sign'] = 'a824eba4c23c6f541ffadfee26b1e500'
-  data['ts'] = '1544512481534'
-  data['bv'] = 'bbb3ed55971873051bc2ff740579bb49'
-  data['doctype'] = 'json'
-  data['version'] = '2.1'
-  data['keyfrom'] = 'fanyi.web'
-  data['action'] = 'FY_BY_REALTIME'
-  data['typoResult'] = 'false'
-  #需要使用urllib.parse.urlencode() 把data转换为需要的形式
-  data = urllib.parse.urlencode(data).encode('utf-8')
-  response = urllib.request.urlopen(url, data)
-  html = response.read().decode('utf-8')
-  target = json.loads(html)
-  print('翻译结果:%s' %(target['translateResult'][0][0]['tgt']))
运行结果:
-  =========== RESTART: C:\Users\XiangyangDai\Desktop\translation.py ===========
-  请输入需要翻译的内容:人生苦短,我学Python
-  翻译结果:Life is too short, I learn Python
我们的要求实现了,但是这样的代码还不能应用到我们的生产实践中,因为你这样搞多了,服务器就会发现非人类的 User Agent 频繁访问,就会把你屏蔽掉了。还有就是发现这个IP怎么访问的这么频繁,就把你拉黑了。其实这些问题,Python都是有解决方法的,欲知详情如何,请听下回分解。
测试题
0. urlopen() 方法的 timeout 参数用于设置什么?
答:timeout 参数用于设置连接的超时时间,单位是秒。
1. 如何从 urlopen() 返回的对象中获取 HTTP 状态码?
答:
-  …
-  response = urllib.request.urlopen(url)
-  code = response.getcode()
-  …
2. 在客户端和服务器之间进行请求-响应时,最常用的是哪两种方法?
答:GET 和 POST。
3. HTTP 是基于请求-响应的模式,那是客户端发出请求,服务端做出响应;还是服务端发出请求,客户端做出响应呢?
答:发出请求的永远是客户端,做出响应的永远是服务端。
4. User-Agent 属性通常是记录什么信息?
答:普通浏览器会通过该内容向访问网站提供你所使用的浏览器类型、操作系统、浏览器内核等信息的标识。
5. 如何通过 urlopen() 使用 POST 方法像服务端发出请求?
答:urlopen 函数有一个 data 参数,如果给这个参数赋值,那么 HTTP 的请求就是使用 POST 方式;如果 data 的值是 None,也就是默认值,那么 HTTP 的请求就是使用 GET 方式。
6. 使用字符串的什么方法将其它编码转换为 Unicode 编码?
答:decode。decode 的作用是将其他编码的字符串转换成 unicode 编码,相反,encode 的作用是将 unicode 编码转换成其他编码的字符串。
7. JSON 是什么鬼?
答:JSON 是一种轻量级的数据交换格式,说白了这里就是用字符串把 Python 的数据结构封装起来,便与存储和使用。
动动手
0. 配合 EasyGui,给“下载一只猫“的代码增加互动:
- 让用户输入尺寸;
- 如果用户不输入尺寸,那么按默认宽400,高600下载喵;
- 让用户指定保存位置。
程序实现如下图:



代码清单:
-  import easygui as g
-  import urllib.request
-  def main():
-  msg = "请填写喵的尺寸"
-  title = "下载一只喵"
-  fieldNames = ["宽:", "高:"]
-  fieldValues = []
-  size = width, height = 400, 600
-  fieldValues = g.multenterbox(msg, title, fieldNames, size)
-  while 1:
-  if fieldValues == None:
-  break
-  errmsg = ""
-  try:
-  width = int(fieldValues[0].strip())
-  except:
-  errmsg += "宽度必须为整数!"
-  try:
-  height = int(fieldValues[1].strip())
-  except:
-  errmsg += "高度必须为整数!"
-  if errmsg == "":
-  break
-  fieldValues = g.multenterbox(errmsg, title, fieldNames, fieldValues)
-  url = "http://placekitten.com/g/%d/%d" % (width, height)
-  response = urllib.request.urlopen(url)
-  cat_img = response.read()
-  filepath = g.diropenbox("请选择存放喵的文件夹")
-  if filepath:
-  filename = '%s/cat_%d_%d.jpg' % (filepath, width, height)
-  else:
-  filename = 'cat_%d_%d.jpg' % (width, height)
-  with open(filename, 'wb') as f:
-  f.write(cat_img)
-  if __name__ == "__main__":
-  main()
1. 写一个登录豆瓣的客户端。
这道题可能要难为大家了,因为需要 N 多你没学过的知识!
不过我也不打算让你断送希望,下边是一个可行的 Python 2 的代码片段,请修改为 Python 3 版本。其中一些库和知识点你可能还没学过,但凭借着过人的自学能力,你可以在不看答案的情况下完成任务的,对吗?
程序实现如下图:

Python2 实现的代码:
-  # -- coding:gbk --
-  import re
-  import urllib, urllib2, cookielib
-  loginurl = 'https://www.douban.com/accounts/login'
-  cookie = cookielib.CookieJar()
-  opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
-  params = {
-  "form_email":"your email",
-  "form_password":"your password",
-  "source":"index_nav" #没有的话登录不成功
-  }
-  #从首页提交登录
-  response=opener.open(loginurl, urllib.urlencode(params))
-  #验证成功跳转至登录页
-  if response.geturl() == "https://www.douban.com/accounts/login":
-  html=response.read()
-  #验证码图片地址
-  imgurl=re.search('<img id="captcha_image" src="(.+?)" alt="captcha" class="captcha_image"/>', html)
-  if imgurl:
-  url=imgurl.group(1)
-  #将图片保存至同目录下
-  res=urllib.urlretrieve(url, 'v.jpg')
-  #获取captcha-id参数
-  captcha=re.search('<input type="hidden" name="captcha-id" value="(.+?)"/>' ,html)
-  if captcha:
-  vcode=raw_input('请输入图片上的验证码:')
-  params["captcha-solution"] = vcode
-  params["captcha-id"] = captcha.group(1)
-  params["user_login"] = "登录"
-  #提交验证码验证
-  response=opener.open(loginurl, urllib.urlencode(params))
-  ''' 登录成功跳转至首页 '''
-  if response.geturl() == "http://www.douban.com/":
-  print 'login success ! '
答:Python 3 对比 Python 2 有不少的改变。
在本题中:
- urllib 和 urllib2 合并,大多数功能放入了 urllib.request 模块;
- 原来的 urllib.urlencode() 变为 urllib.parse.urlencode().encode(),由于编码的关系,你还需要在后边加上 encode('utf-8');
- cookielib 被改名为 http.cookiejar;
课堂中我们还没讲,所以这里借机会给大家简单科普一下 cookie 是什么东西:
我们说 HTTP 协议是基于请求响应模式,就是客户端发一个请求,服务端回复一个响应酱紫……
但 HTTP 协议是无状态的,也就是说客户端这会儿给服务端提交了账号密码,服务端回复验证通过,但下一秒客户端说我要访问 XXOO 资源,服务端回复:“啊??你是谁?!”
为了解决这个尴尬的困境,有人就发明出了 cookie。cookie 相当于服务端(网站)用于验证你的身份的密文。于是客户端每次提交请求的时候,服务端通过验证 cookie 即可知道你的身份信息。那么正如你所猜测的,CookieJar 是 Python 用于存放 cookie 的对象。
当然,这里已经给你提供了 Python 2 的代码,你不懂上边这些,也不影响完成作业。
代码清单:
-  import re
-  import urllib.request
-  from http.cookiejar import CookieJar
-  # 豆瓣的登录url
-  loginurl = 'https://www.douban.com/accounts/login'
-  cookie = CookieJar()
-  opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor)
-  data = {
-  "form_email":"your email",
-  "form_password":"your password",
-  "source":"index_nav"
-  }
-  data = {}
-  data['form_email'] = '你的账号'
-  data['form_password'] = '你的密码'
-  data['source'] = 'index_nav'
-  response = opener.open(loginurl, urllib.parse.urlencode(data).encode('utf-8'))
-  #验证成功跳转至登录页
-  if response.geturl() == "https://www.douban.com/accounts/login":
-  html = response.read().decode()
-  #验证码图片地址
-  imgurl = re.search('<img id="captcha_image" src="(.+?)" alt="captcha" class="captcha_image"/>', html)
-  if imgurl:
-  url = imgurl.group(1)
-  # 将验证码图片保存至同目录下
-  res = urllib.request.urlretrieve(url, 'v.jpg')
-  # 获取captcha-id参数
-  captcha = re.search('<input type="hidden" name="captcha-id" value="(.+?)"/>' ,html)
-  if captcha:
-  vcode = input('请输入图片上的验证码:')
-  data["captcha-solution"] = vcode
-  data["captcha-id"] = captcha.group(1)
-  data["user_login"] = "登录"
-  # 提交验证码验证
-  response = opener.open(loginurl, urllib.parse.urlencode(data).encode('utf-8'))
-  # 登录成功跳转至首页 '''
-  if response.geturl() == "http://www.douban.com/":
-  print('登录成功!')




![[SSM]手写Spring框架](https://img-blog.csdnimg.cn/1b4d7ed5f4664660a07d72beac594f95.png)














