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状态码的信息请查阅:
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
None
if 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('登录成功!')