点击上方蓝字关注我们
欢迎关注我的公众号,志学Python
01
服务端 HTTP HTTP 响应
HTTP响应也由四个部分组成,分别是: 状态行
、消息报头
、空行
、响应正文
HTTP/1.1 200 OK
Server: Tengine
Connection: keep-alive
Date: Wed, 30 Nov 2016 07:58:21 GMT
Cache-Control: no-cache
Content-Type: text/html;charset=UTF-8
Keep-Alive: timeout=20
Vary: Accept-Encoding
Pragma: no-cache
X-NWS-LOG-UUID: bd27210a-24e5-4740-8f6c-25dbafa9c395
Content-Length: 180945
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ....
理论上所有的响应头信息都应该是回应请求头的。但是服务端为了效率,安全,还有其他方面的考虑,会添加相对应的响应头信息,从上图可以看到:
这个值告诉客户端,服务端不希望客户端缓存资源,在下次请求资源时,必须要从新请求服务器,不能从缓存副本中获取资源。
这个字段作为回应客户端的Connection:keep-alive,告诉客户端服务器的tcp连接也是一个长连接,客户端可以继续使用这个tcp连接发送http请求。
告诉客户端,服务端发送的资源是采用gzip编码的,客户端看到这个信息后,应该采用gzip对资源进行解码。
告诉客户端,资源文件的类型,还有字符编码,客户端通过utf-8对资源进行解码,然后对资源进行html解析。通常我们会看到有些网站是乱码的,往往就是服务器端没有返回正确的编码。
这个是服务端发送资源时的服务器时间,GMT是格林尼治所在地的标准时间。http协议中发送的时间都是GMT的,这主要是解决在互联网上,不同时区在相互请求资源的时候,时间混乱问题。
这个响应头也是跟缓存有关的,告诉客户端在这个时间前,可以直接访问缓存副本,很显然这个值会存在问题,因为客户端和服务器的时间不一定会都是相同的,如果时间不同就会导致问题。所以这个响应头是没有Cache-Control:max-age=*这个响应头准确的,因为max-age=date中的date是个相对时间,不仅更好理解,也更准确。
这个含义与Cache-Control等同。
这个是服务器和相对应的版本,只是告诉客户端服务器的信息。
这个响应头告诉客户端,服务器发送的资源的方式是分块发送的。一般分块发送的资源都是服务器动态生成的,在发送时还不知道发送资源的大小,所以采用分块发送,每一块都是独立的,独立的块都能标示自己的长度,最后一块是0长度的,当客户端读到这个0长度的块时,就可以确定资源已经传输完了。
告诉缓存服务器,缓存压缩文件和非压缩文件两个版本,现在这个字段用处并不大,因为现在的浏览器都是支持压缩的。
响应状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值。
100~199
:表示服务器成功接收部分请求,要求客户端继续提交其余请求才能完成整个处理过程。200~299
:表示服务器成功接收请求并已完成整个处理过程。常用200(OK 请求成功)。300~399
:为完成请求,客户需进一步细化请求。例如:请求的资源已经移动一个新地址、常用302(所请求的页面已经临时转移至新的url)、307和304(使用缓存资源)。400~499
:客户端的请求有错误,常用404(服务器无法找到被请求的页面)、403(服务器拒绝访问,权限不够)。500~599
:服务器端出现错误,常用500(请求未完成。服务器遇到不可预知的情况)。服务器和客户端的交互仅限于请求/响应过程,结束之后便断开,在下一次请求时,服务器会认为新的客户端。
为了维护他们之间的链接,让服务器知道这是前一个用户发送的请求,必须在一个地方保存客户端的信息。
Cookie:通过在 客户端 记录的信息确定用户的身份。
Session:通过在 服务器端 记录的信息确定用户的身份。
设置好后,本机HTTP通信都会经过127.0.0.1:8888代理,也就会被Fiddler拦截到。
02
HTTP 和 HTTPS 的 get 和 post 方法
# IPython 中的测试结果
In [1]: import urllib.parse
In [2]: word = {"wd" : "传智播客"}
# 通过urllib.parse.urlencode()方法,将字典键值对按URL编码转换,从而能被web服务器接受。
In [3]: urllib..parse.urlencode(word)
Out[3]: "wd=%E4%BC%A0%E6%99%BA%E6%92%AD%E5%AE%A2"
# 通过urllib.parse.unquote()方法,把 URL编码字符串,转换回原先字符串。
In [4]: print (urllib.parse.unquote("wd=%E4%BC%A0%E6%99%BA%E6%92%AD%E5%AE%A2"))
wd=传智播客
GET请求一般用于我们向服务器获取数据,比如说,我们用百度搜索传智播客
:https://www.baidu.com/s?wd=传智播客
浏览器的url会跳转成如图所示:
https://www.baidu.com/s?wd=%E4%BC%A0%E6%99%BA%E6%92%AD%E5%AE%A2
在其中我们可以看到在请求部分里,http://www.baidu.com/s?
之后出现一个长长的字符串,其中就包含我们要查询的关键词传智播客,于是我们可以尝试用默认的Get方式来发送请求。
# urllib_get.py
import urllib.parse #负责url编码处理
import urllib.request
url = "http://www.baidu.com/s"
word = {"wd":"传智播客"}
word = urllib.parse.urlencode(word) #转换成url编码格式(字符串)
newurl = url + "?" + word # url首个分隔符就是 ?
headers={ "User-Agent": "
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36
"}
request = urllib.request.Request(newurl, headers=headers)
response = urllib.request.urlopen(request)
print (response.read())
首先我们创建一个python文件, tiebaSpider.py,我们要完成的是,输入一个百度贴吧的地址,比如:
百度贴吧LOL吧第一页:http://tieba.baidu.com/f?kw=lol&ie=utf-8&pn=0
第二页: http://tieba.baidu.com/f?kw=lol&ie=utf-8&pn=50
第三页: http://tieba.baidu.com/f?kw=lol&ie=utf-8&pn=100
发现规律了吧,贴吧中每个页面不同之处,就是url最后的pn的值,其余的都是一样的,我们可以抓住这个规律。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import urllib.request
import urllib.parse
def loadPage(url, filename):
"""
作用:根据url发送请求,获取服务器响应文件
url: 需要爬取的url地址
filename : 处理的文件名
"""
print ("正在下载 " + filename)
headers = {"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36"}
request = urllib.request.Request(url, headers = headers)
return urllib.request.urlopen(request).read()
def writePage(html, filename):
"""
作用:将html内容写入到本地
html:服务器相应文件内容
"""
print ("正在保存 " + filename)
# 文件写入
with open(filename, "wb+") as f:
f.write(html)
print ("-" * 30)
def tiebaSpider(url, beginPage, endPage):
"""
作用:贴吧爬虫调度器,负责组合处理每个页面的url
url : 贴吧url的前部分
beginPage : 起始页
endPage : 结束页
"""
for page in range(beginPage, endPage + 1):
pn = (page - 1) * 50
filename = "第" + str(page) + "页.html"
fullurl = url + "&pn=" + str(pn)
#print fullurl
html = loadPage(fullurl, filename)
#print html
writePage(html, filename)
print ('谢谢使用')
if __name__ == "__main__":
kw = input("请输入需要爬取的贴吧名:")
beginPage = int(input("请输入起始页:"))
endPage = int(input("请输入结束页:"))
url = "http://tieba.baidu.com/f?"
key = urllib.parse.urlencode({"kw": kw})
fullurl = url + key
tiebaSpider(fullurl, beginPage, endPage)
其实很多网站都是这样的,同类网站下的html页面编号,分别对应网址后的网页序号,只要发现规律就可以批量爬取页面了。
上面我们说了Request请求对象的里有data参数,它就是用在POST里的,我们要传送的数据就是这个参数data,data是一个字典,里面要匹配键值对。
输入测试数据,再通过使用Fiddler观察,其中有一条是POST请求,而向服务器发送的请求数据并不是在url里,那么我们可以试着模拟这个POST请求。
于是,我们可以尝试用POST方式发送请求。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import urllib.request
import urllib.parse
# 通过抓包的方式获取的url,并不是浏览器上显示的url
url = "http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule&smartresult=ugc&sessionFrom=null"
# 完整的headers
headers = {
"Accept" : "application/json, text/javascript, */*; q=0.01",
"X-Requested-With" : "XMLHttpRequest",
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
"Content-Type" : "application/x-www-form-urlencoded; charset=UTF-8",
}
# 用户接口输入
# 发送到web服务器的表单数据
formdata = {
"type" : "AUTO",
"i" : "我爱你",
"doctype" : "json",
"xmlVersion" : "1.6",
"keyfrom" : "fanyi.web",
"ue" : "UTF-8",
"typoResult" : "true"
}
# 经过urlencode转码
data = urllib.parse.urlencode(formdata).encode('utf-8')
# 如果Request()方法里的data参数有值,那么这个请求就是POST
# 如果没有,就是Get
#request = urllib.request.Request(url, data = data, headers = headers)
response = urllib.request.urlopen(url,data)
html = response.read().decode('utf-8')
print(html)
#print (urllib.request.urlopen(req).read())
Content-Length: 144
:是指发送的表单数据长度为144,也就是字符个数是144个。X-Requested-With: XMLHttpRequest
:表示Ajax异步请求。Content-Type: application/x-www-form-urlencoded
:表示浏览器提交 Web 表单时使用,表单数据会按照 name1=value1&name2=value2 键值对形式进行编码。
有些网页内容使用AJAX加载,只要记得,AJAX一般返回的是JSON,直接对AJAX地址进行post或get,就返回JSON数据了。
"作为一名爬虫工程师,你最需要关注的,是数据的来源"
import urllib
import urllib2
# demo1
url = "https://movie.douban.com/j/chart/top_list?type=11&interval_id=100%3A90&action"
headers={"User-Agent": "Mozilla...."}
# 变动的是这两个参数,从start开始往后显示limit个
formdata = {
'start':'0',
'limit':'10'
}
data = urllib.urlencode(formdata)
request = urllib2.Request(url, data = data, headers = headers)
response = urllib2.urlopen(request)
print response.read()
# demo2
url = "https://movie.douban.com/j/chart/top_list?"
headers={"User-Agent": "Mozilla...."}
# 处理所有参数
formdata = {
'type':'11',
'interval_id':'100:90',
'action':'',
'start':'0',
'limit':'10'
}
data = urllib.urlencode(formdata)
request = urllib2.Request(url, data = data, headers = headers)
response = urllib2.urlopen(request)
print response.read()
?
分开与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>
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import urllib.request
url = "http://www.renren.com/410043129/profile"
headers = {
"Host" : "www.renren.com",
"Connection" : "keep-alive",
#"Upgrade-Insecure-Requests" : "1",
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36",
"Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Referer" : "http://www.renren.com/SysHome.do",
#"Accept-Encoding" : "gzip, deflate, sdch",#加上会得到压缩文件
"Cookie" : "anonymid=ixrna3fysufnwv; _r01_=1; depovince=GW; jebe_key=f6fb270b-d06d-42e6-8b53-e67c3156aa7e%7Cc13c37f53bca9e1e7132d4b58ce00fa3%7C1484060607478%7C1%7C1484400895379; jebe_key=f6fb270b-d06d-42e6-8b53-e67c3156aa7e%7Cc13c37f53bca9e1e7132d4b58ce00fa3%7C1484060607478%7C1%7C1484400890914; JSESSIONID=abcX8s_OqSGsYeRg5vHMv; jebecookies=0c5f9b0d-03d8-4e6a-b7a9-3845d04a9870|||||; ick_login=8a429d6c-78b4-4e79-8fd5-33323cd9e2bc; _de=BF09EE3A28DED52E6B65F6A4705D973F1383380866D39FF5; p=0cedb18d0982741d12ffc9a0d93670e09; ap=327550029; first_login_flag=1; ln_uact=mr_mao_hacker@163.com; ln_hurl=http://hdn.xnimg.cn/photos/hdn521/20140529/1055/h_main_9A3Z_e0c300019f6a195a.jpg; t=56c0c522b5b068fdee708aeb1056ee819; societyguester=56c0c522b5b068fdee708aeb1056ee819; id=327550029; xnsid=5ea75bd6; loginfrom=syshome",
"Accept-Language" : "zh-CN,zh;q=0.8,en;q=0.6",
}
request = urllib.request.Request(url, headers = headers)
response = urllib.request.urlopen(request)
print(response.read())
现在随处可见 https 开头的网站,urllib2可以为 HTTPS 请求验证SSL证书,就像web浏览器一样,如果网站的SSL证书是经过CA认证的,则能够正常访问,如:https://www.baidu.com/
等...
如果SSL证书验证不通过,或者操作系统不信任服务器的安全证书,比如浏览器在访问12306网站如:https://www.12306.cn/mormhweb/的时候,会警告用户证书不受信任。(据说 12306 网站证书是自己做的,没有通过CA认证)
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import urllib.request
import ssl
# 忽略SSL安全认证
context = ssl._create_unverified_context()
url = "https://www.12306.cn/mormhweb/"
#url = "https://www.baidu.com/"
headers = {
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"
}
request = urllib.request.Request(url, headers = headers)
# 添加到context参数里
response = urllib.request.urlopen(request, context = context)
print (response.read())
CA(Certificate Authority)是数字证书认证中心的简称,是指发放、管理、废除数字证书的受信任的第三方机构,如北京数字认证股份有限公司、上海市数字证书认证中心有限公司等...
CA的作用是检查证书持有者身份的合法性,并签发证书,以防证书被伪造或篡改,以及对证书和密钥进行管理。
现实生活中可以用身份证来证明身份, 那么在网络世界里,数字证书就是身份证。和现实生活不同的是,并不是每个上网的用户都有数字证书的,往往只有当一个人需要证明自己的身份的时候才需要用到数字证书。
普通用户一般是不需要,因为网站并不关心是谁访问了网站,现在的网站只关心流量。但是反过来,网站就需要证明自己的身份了。
比如说现在钓鱼网站很多的,比如你想访问的是www.baidu.com,但其实你访问的是www.daibu.com”,所以在提交自己的隐私信息之前需要验证一下网站的身份,要求网站出示数字证书。
一般正常的网站都会主动出示自己的数字证书,来确保客户端和网站服务器之间的通信数据是加密安全的。