一、cookie和session
(一)产生的缘由
由于http是一个无状态的协议,每次请求如果需要之前请求的一些信息,就必须重新发送之前的请求。
为了解决这种问题,产生了一种记录状态的技术–就是cookie和session。
(二)作用
- cookie是在客户端记录状态,session是在服务端记录状态。
- 在做爬虫的时候,如果要实现登录,只需要将浏览器中登录后的cookie信息封装到请求头中就可以了。
(三)详解
1.session
对于session(会话),其本来的含义是指有始有终的一系列动作或消息。而在web中,会话对象用来存储特定用户的会话所需的属性及配置信息。
当一个客户端发送一个cookie,服务器会从这个cookie中找到sessionid,再查找出相应session信息返回给客户端,来进行用户页面的流转。
问题1
可能会出现,通过sessionid来查找session,发现没有的情况。因为第一次登录时,会创建一个session,在session的有效期内,继续访问页面,服务器会直接查找到这个session返回给客户端。失效后就找不到了。
那session什么时候失效呢?
当session的寿命达到时会失效,一般是三十分钟。
2.cookie
cookie指某些网站为了辨别用户身份、进行会话跟踪而存储在用户本地终端上的数据。
问题2
cookie和session一般是配合使用的
那当cookie被用户禁用,session怎么使用?
有两种方法:
- 提示必须开启cookie
- 使用url重传,就是将sessionid附带在url后面传递给浏览器
1.cookie的组成
- Name:该cookie的名称,一旦创建,不可更改。
- value:该cookie的值,如果值为Unicode字符,需要为字符编码。如果值为二进制数据,则需要使用BASE64编码。
- Domain:可以访问该cookie的域名。例如:如果设置为.zhihu.com,则所有以zhihu.com结尾的域名都可以访问该cookie。
- MaxAge:该cookie的失效时间,单位为秒。也常和Expires— 起使用,通过它可以计算出其有效时间。Max Age 如果为正数,则该cookie在MaxAge 秒之后失效。如果为负数,则关闭浏览器时cookie失效,浏览器也不会以任何形式保存该cookie 。
- Path:该cookie 的使用路径。如果设置为/path/,则只有路径为/path/的页面可以访问该cookie。如果设置为/,则本域名下的所有页面都可以访问该cookie。
- Size:该cookie的大小。
- HTTP:该cookie的httponly属性。若此属性为true,则只有在HTTP头中会带有此cookie的信息,而不能通过document.cookie来访问此cookie。
- Secure:该cookie是否仅被使用安全协议传输。安全协议有http,https和SSL等,在网络上传输数据之前先将数据加密,默认为flase。
2.会话cookie和持久cookie
会话cookie就是指存储在浏览器内存中的cookie,当浏览器关闭,会话cookie失效。
持久cookie是保存在硬盘上的cookie。
这两个cookie的分类标准:
主要是通过maxAge或者expires字段定义的。为负数,则为会话cookie。
二、在爬虫中实现登录
(一)方法一
将登录后页面中的cookie,封装到请求头中。
新浪微博
import requests
base_url = 'https://weibo.com/5567019285/profile?'
params = {
'topnav': '1',
'wvr': '6',
'is_all': '1'
}
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36',
'Cookie': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
}
response = requests.get(base_url,headers=headers,params=params)
if '吕-二' in response.text:
print('登录成功')
else:
print('登录失败')
(二)方法二
使用requests模块中的session对象
session = requests.session() # 这个session对象可以记录登录状态
人人网
import requests
base_url = 'http://www.renren.com/PLogin.do'
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'keep-alive',
'Host': 'www.renren.com',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
}
data = {
'email':str(13811780191),
'password':str(123123)
}
session = requests.session()
session.post(base_url,headers=headers,data=data)
response = session.get('http://www.renren.com/972683871/newsfeed/photo')
print(response.headers)
if '吕二三' in response.text:
print('登录成功')
else:
print('登录失败')
三、代理
(一)基本原理
代理实际上指的就是代理服务器,英文叫作proxy server。它的功能是代理网络用户去取得网络信息。形象地说,它是网络信息的中转站。在我们正常请求一个网站时,是发送了请求给web服务器,web服务器把响应传回给我们。如果设置了代理服务器,实际上就是在本机和服务器之间搭建了一个桥,此时本机不是直接向web服务器发起请求,而是向代理服务器发出请求,请求会发送给代理服务器,然后由代理服务器再发送给web服务器,接着由代理服务器再把web服务器返回的响应转发给本机。这样我们同样可以正常访问网页,但这个过程中web服务器识别出的真实IP就不再是我们本机的IP了,就成功实现了IP伪装。
(二)作用
-
突破自身IP访问限制
访问一些平时不能访问的站点。
-
访问一些单位或团体内部的内部资源
比如使用教育网内地址段免费代理服务器,就可以用于对教育网开放的各类FTP下载上传,以及各类资料查询共享等服务。
-
提高访问速度
通常代理服务器都设置一个较大的硬盘缓冲区,当有外界的信息通过时,同时也将其保存到缓冲区中,当其他用户再访问相同的信息时,则直接由缓冲区中取出信息传给用户即可,可以提高访问速度。
-
隐藏真实ip
上网者也可以通过这种方法隐藏自己的IP,免受攻击。对于爬虫来说,我们用代理就是为了隐藏自身IP,防止自身的被封锁。
(三)分类
1.根据协议区分
- FTP 代理服务器:主要用于访问FTP 服务器, 一般有上传、下载以及缓存功能, 端口一般为21 、2121 等。
- HTTP 代理服务器:主要用于访问网页, 一般有内容过滤和缓存功能, 端口一般为80 、8080 、3128 等。
- SSL/TLS 代理:主要用于访问加密网站, 一般有SSL 或TLS加密功能( 最高支持128 位加密强度) , 端口一般为443 。
- RTSP 代理:主要用于访问Real 流媒体服务器, 一般有缓存功能, 端口一般为554 。
- Telnet 代理:主要用于telnet 远程控制( 黑客人侵计算机时常用于隐藏身份),端口一般为23 。
- POP3/SMTP 代理:主要用于POP3/SMTP 方式收发邮件, 一般有缓存功能, 端口一般为110 / 25 。
- SOCKS 代理:只是单纯传递数据包, 不关心具体协议和用法, 所以速度快很多, 一般有缓存功能, 端口一般为1080 。SOCKS 代理协议又分为SOCKS4 和SOCKS5 , 前者只支持TCP ,而后者支持TCP 和UDP , 还支持各种身份验证机制、服务器端域名解析等。简单来说,SOCKS4 能做到的SOCKS5 都可以做到, 但SOCKS5 能做到的SOCKS4 不一定能做到。
2.根据匿名程度区分
- 高度匿名代理: 会将数据包原封不动地转发, 在服务端看来就好像真的是一个普通客户端在访问, 而记录的IP 是代理服务器的IPO。
- 普通匿名代理: 会在数据包上做一些改动, 服务端上有可能发现这是个代理服务器, 也有一定机率追查到客户端的真实伊代理服务器通常会加人的HITP 头有HTTP_VIA 和HTTP_X_FORWARDED FOR 。
- 透明代理:不但改动了数据包, 还会告诉服务器客户端的真实IPO 这种代理除了能用缓存技术提高浏览速度, 能用内容过滤提高安全性之外, 并无其他显著作用, 最常见的例子是内网中的硬件防火墙。
- 间谍代理:指组织或个人创建的用于记录用户传输的数据, 然后进行研究、监控等目的的代理服务器。
(四)在requests模块中设置代理
步骤
-
创建一个代理字典
proxies = ( 'http':代理ip, 'https':'https://ip:port' )
-
用get或者post请求是,增加proxies这个参数就可以了
四、数据的分类
(一)结构化数据
数据以行为单位,一行数据表示一个实体的信息,每一行的数据的属性是相同的。
常见于关系型数据库。
(二)半结构化数据
结构化数据的一种形式,并不符合关系型数据库或其他数据表的形式关联起来的数据模型结构,但包含相关标记,用来分隔语义元素以及对记录和字段进行分层。因此它也被称为自描述的结构。
常见的半结构化数据有:xml,html,json…
json数据
JSON是JS对象的字符串表达式,他使用文本形式表示一个JS对象的信息,本质是一个字符串。
JS的对象就相当于python中的字典。
JS的数组就相当于python中的列表。
因为json用来存储js的对象或者数组,所以在python中我们可以将json转化为list或者dict。
方法
json.dumps(Python中的list或dict) # 返回值为json字符串
json.loads(json字符串) # 返回值为Python中的list或dict
json.dump(list/dict,fp) # 将list/dict保存到json文件中
json.load(fp) # 从json文件中读取json数据,返回值为list/dict
(三)非结构化数据
没有固定的结构,常见的非结构化数据有:文档,图片,视频等…
对于非结构化数据,一般我们以二进制的格式进行整体存储。
五、正则表达式
(一)元字符
1.匹配边界
- ^:行首
- $:行尾
2.重复次数
- ?:0或1
- *:>=0
- +:>0
- {n,}:>=n
- {n,m}:>=n&&<=m
- {n}:n
3.各种字符的表示
- []:表示单字符
- [abc]:匹配a或b或c
- [a-z]:匹配a到z中的一个
- [0-9a-zA-Z_]:匹配数字字母下划线
- \d:数字
- \D:非数字
- \w:数字字母下划线
- \W:非数字字母下划线
- \s:空白(空格、制表符、换行)
- \S:非空白
- \b:单词的边界–开始、结束(不消耗待匹配字符)
- \B:非边界
- .:除换行符外的任意字符
(二)re模块
1.使用步骤
-
将正则表达式编译成一个pattern对象。
pattern = re.compile('正则表达式')
-
pattern提供了一系列的方法,对文本进行匹配,一般返回值为match对象。
-
通过使用match对象提供的方法获取匹配结果。
match.group() # 获取匹配结果 match.group(0) # 获取匹配结果 match.span() # 获取匹配范围 match.start() # 匹配开始位置 match.end() # 匹配结束位置
2.pattern对象的方法
1.match():默认从头开始匹配,只匹配一次,返回一个match对象。
pattern.match(
'待匹配的字符串'(必须指定),
start(匹配的开始位置),
end(匹配的结束位置)
)
返回值为match对象
import re
pattern = re.compile(r'\d+')
content = 'one12twothree34four'
m1 = pattern.match(content)
m2 = pattern.match(content,2,10)
m3 = pattern.match(content,3,10)
print(m1)
print(m2)
print(m3)
# match对象的方法
print(m3.group())
print(m3.group(0))
print(m3.span()) # 到匹配字符的下一个位置
print(m3.start())
print(m3.end()) # 到匹配字符的下一个位置
2.search():从任意位置匹配,只匹配一次,返回一个match对象。
pattern.match(
'待匹配的字符串'(必须指定),
start(匹配的开始位置),
end(匹配的结束位置)
)
返回值为match对象
import re
pattern = re.compile(r'\d+')
content = 'one12twothree34four'
m1 = pattern.search(content)
m2 = pattern.search(content,5)
print(m1)
print(m1.group())
print(m2)
print(m2.group())
3.findall():全文匹配,多次匹配,将匹配到的结果放到一个list中返回。
pattern.findall(
'待匹配的字符串'(必须指定),
start(匹配的开始位置),
end(匹配的结束位置)
)
返回值为list
import re
# content = 'we are well welcome'
# pattern1 = re.compile(r'\bwe\b')
# pattern2 = re.compile('\\bwe\\b')
# pattern3 = re.compile('we')
# result1 = pattern1.findall(content)
# result2 = pattern2.findall(content)
# result3 = pattern3.findall(content)
# print(result1)
# print(result2)
# print(result3)
pattern = re.compile(r'\d+')
result1 = pattern.findall('hello 123456 789')
result2 = pattern.findall('one1two2three3four4')
print(result1)
print(result2)
4.finditer():全文匹配,多次匹配,返回一个包含匹配结果的迭代器。
pattern.finditer(
'待匹配的字符串'(必须指定),
start(匹配的开始位置),
end(匹配的结束位置)
)
返回值为迭代器iterator
import re
pattern = re.compile(r'\d+')
content1 = 'hello 123456 789'
content2 = 'one1two2three3four4'
result_iter1 = pattern.finditer(content1)
result_iter2 = pattern.finditer(content2,0,7)
print(type(result_iter1))
print(type(result_iter2))
for m1 in result_iter1:
print('matching string:{},position:{}'.format(m1.group(),m1.span()))
print('-----------------------------------------')
for m2 in result_iter2:
print('matching string:{},position:{}'.format(m2.group(), m2.span()))
5.split():切分字符串,按照整个表达式所指定的内容切分,返回值是list。
pattern.split(
‘要切分的字符串’,
切分的次数(不指定就默认全部切割)
)
返回值为list
import re
p = re.compile(r'[\s,;]+')
a = p.split('a,b;;,c,; d')
print(a)
6.sub():用指定的字符串,替换正则表达式匹配到的目标字符串的内容。
pattern.sub(
repl(替换成什么),
content(待替换内容),
count(替换次数,可不指定,默认替换所有)
)
repl可以是字符串也可以是函数。
repl是字符串时,局限性是只能替换为固定的内容,不灵活。
import re
p = re.compile(r'(\w+) (\w+)')
s = 'hello 123,hello 456'
print(p.sub('ssss',s))
print(p.sub(r'\2 \1',s)) # 引用分组
repl是函数时,就很灵活了。
当repl是函数的时候,这个函数必须满足以下要求:
-
函数必须携带一个参数,这个参数是一个match对象。
def func(match): ''' 对match对象的操作。 ''' print(macth.group())
-
当在sub方法中传入这个参数时,这个match对象其实就是用正则匹配到的每一个match对象。
-
这个函数是有返回值的,返回值必须是一个字符串,将来是用这个字符串进行替换目标字符串的。
import re
p = re.compile(r'(\w+) (\w+)')
s = 'hello 123,hello 456'
def func(m):
return 'hi' + ' ' + m.group(2)
print(p.sub(func,s))
print(p.sub(func,s,1))
p = re.compile(r'\d+')
content = 'zhangsan:3000,lisi:4000'
def add(match):
return str(int(match.group())+2000)
print(p.sub(add,content))
(三)正则表达式中的分组
分组是通过()来表示的,一个括号就表示一个分组。
1.作用
-
筛选特定内容。
取分组内容可以通过match对象的group方法取。
group(1)取的是正则表达式中第一个括号(分组)匹配到的内容,以此类推。
import re content = '{name:"zhangsan",age:"10",hobby:["basktball","football","read"]}' pattern = re.compile(r'{name:"(\w+)",age:"(\d+)".+}') match = pattern.search(content) print(match.group(1)) print(match.group(2))
-
可以在同一个表达式的后面引用前面的分组表达式。
import re s = '<html><h1>正则表达式</h1></html>' pattern = re.compile(r'<(html)><(h1)>(.*)</\2></\1>') match = pattern.search(s) print(match.group(3))
(四)正则表达式的模式
pattern = re.compile(
‘正则表达式’,
‘正则表达式的模式’
)
正则表达式的模式有以下几种:
- re.I:使匹配对大小写不敏感
- re.M:多行匹配,影响^和$
- re.S:使.匹配包括换行在内的所有字符
(五)贪婪和非贪婪模式
- 贪婪是用*控制的
- 非贪婪是用?控制的
- *和?都是作用于表示重复次数的元字符的
- 正则默认是贪婪模式,所以数量控制符默认是最大值,也就是贪婪
- 在表示重复的元字符后面加一个?,此时就是非贪婪,取这个重复元字符的最小值
(六)通用匹配正则表达式
‘.*?’,配合re.S
content = '{name:"zhangsan",age:"10",hobby:["basktball","football","read"]}'
pattern = re.compile(r'.*?"(.*?)".*?"(.*?)".*?')
match = pattern.search(content)
print(match)
print(match.group(1))
print(match.group(2))