ession 是保存用户和 Web 应用的会话状态的一种方法,ASP.NET Core 提供了一个用于管理会话状态的中间件。在本文中我将会简单介绍一下 ASP.NET Core 中的 Session 的使用方法。
nuget 添加引用 Microsoft.AspNetCore.Session
ession 是基于 IDistributedCache 构建的,所以必须引用一种 IDistributedCache 的实现,ASP.NET Core 提供了多种 IDistributedCache 的实现 (Redis、SQL Server、In-memory)
services.AddDistributedMemoryCache();
services.AddSession();
nuget 添加引用 Microsoft.Extensions.Caching.SqlServer
SqlServerCache实现允许分布式缓存使用SQL Server数据库作为其后备存储。要创建SQL Server表,您可以使用sql-cache工具,该工具将使用您指定的名称和模式创建一个表。
要使用sql-cache工具,请添加SqlConfig.Tools到.csproj文件的<ItemGroup>元素并运行dotnet恢复。
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.Extensions.Caching.SqlConfig.Tools" Version="1.0.0-msbuild3-final" />
</ItemGroup>
通过运行以下命令来测试SqlConfig.Tools
C:\DistCacheSample\src\DistCacheSample>dotnet sql-cache create --help
sql-cache工具将显示用法,选项和命令帮助,现在你可以创建表到sql server中,运行“sql-cache create”命令:
C:\DistCacheSample\src\DistCacheSample>dotnet sql-cache create "Data Source=(localdb)\v11.0;Initial Catalog=DistCache;Integrated Security=True;" dbo TestCache
info: Microsoft.Extensions.Caching.SqlConfig.Tools.Program[0]
Table and index were created successfully.
创建的表格具有以下架构:
注意的ConnectionString(以及可选地,SchemaName和TableName)通常应该被存储的源控制(如UserSecrets)以外,因为它们可能包含凭证。
像所有的缓存实现一样,你的应用程序应该使用一个实例来获取和设置缓存值IDistributedCache,而不是SqlServerCache。该示例SqlServerCache在Production环境中实现(因此已配置ConfigureProductionServices)。
// Microsoft SQL Server implementation of IDistributedCache.
// Note that this would require setting up the session state database.
services.AddDistributedSqlServerCache(o =>
{
o.ConnectionString = "Server=.;Database=ASPNET5SessionState;Trusted_Connection=True;";
o.SchemaName = "dbo";
o.TableName = "Sessions";
});
services.AddSession();
nuget 添加引用 Microsoft.Extensions.Caching.Redis
Redis是一款开源的内存数据存储,通常用作分布式缓存。您可以在本地使用它,并且可以为Azure托管的ASP.NET Core应用程序配置Azure Redis缓存。您的ASP.NET Core应用程序使用RedisDistributedCache实例配置缓存实施。
您可以ConfigureServices通过请求一个实例IDistributedCache(参见上面的代码)来配置Redis实现并在您的应用代码中访问它。
在示例代码中,RedisCache当为服务器配置Staging环境时使用实现。因此该ConfigureStagingServices方法配置RedisCache:
services.AddDistributedRedisCache(options => { options.Configuration = "localhost"; options.InstanceName = "SampleInstance"; });
接着在 Startup.cs 的 Config 方法中配置使用 Session 中间件,所有中间件的配置顺序非常重要,必须在 UseSession 调用后才能访问 Session 。
// 必须在 UseMvc 之前调用
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
在 AddSession 和 UseSession 方法中可以传入一个 SessionOptions 参数,通过该参数可以设置 Session 的 Cookie name, Cookie path 等信息。
配置完成后,就可以使用 Session 保存数据了。
具体实现redis实现 https://www.cnblogs.com/liuxiaoji/p/9259747.html
Session 安装配置好就可以通过 HttpContext.Session 来保存和读取数据了。由于 Session 是基于 IDistributedCache 构建的,因此 Session 只能存储 byte[] 数据,这样使用起来很不方便,好在有很多扩展方法可以用来直接读取和保存 string、int 等类型数据。
一个 Session 使用的简单示例:
public IActionResult Index()
{
HttpContext.Session.SetString("SessionStartedTime", "Session started time:" + DateTime.Now.ToString());
return View();
}
public IActionResult About()
{
ViewData["CurrentTime"] = "Current time:" + DateTime.Now.ToString();
ViewData["SessionStartedTime"] = HttpContext.Session.GetString("SessionStartedTime");
return View();
}
或者设置一个扩展类也可以直接将实体类序列化成json存储
TTP是无状态协议,这意味着每次客户端检索网页时,都要单独打开一个服务器连接,因此服务器不会记录下先前客户端请求的任何信息。
有三种方法来维持客户端与服务器的会话:
Cookies
网络服务器可以指定一个唯一的session ID作为cookie来代表每个客户端,用来识别这个客户端接下来的请求。
这可能不是一种有效的方式,因为很多时候浏览器并不一定支持cookie,所以我们不建议使用这种方法来维持会话。
隐藏表单域
一个网络服务器可以发送一个隐藏的HTML表单域和一个唯一的session ID,就像下面这样:
<input type="hidden" name="sessionid" value="12345">
这个条目意味着,当表单被提交时,指定的名称和值将会自动包含在GET或POST数据中。每当浏览器发送一个请求,session_id的值就可以用来保存不同浏览器的轨迹。
这种方式可能是一种有效的方式,但点击<A HREF>标签中的超链接时不会产生表单提交事件,因此隐藏表单域也不支持通用会话跟踪。
重写URL
您可以在每个URL后面添加一些额外的数据来区分会话,服务器能够根据这些数据来关联session标识符。
举例来说,http://w3cschool.cc/file.htm;sessionid=12345, session标识符为sessionid=12345,服务器可以用这个数据来识别客户端。
相比而言,重写URL是更好的方式来,就算浏览器不支持cookies也能工作,但缺点是您必须为每个URL动态指定session ID,就算这是个简单的HTML页面。
session对象
除了以上几种方法外,JSP利用servlet提供的HttpSession接口来识别一个用户,存储这个用户的所有访问信息。
默认情况下,JSP允许会话跟踪,一个新的HttpSession对象将会自动地为新的客户端实例化。禁止会话跟踪需要显式地关掉它,通过将page指令中session属性值设为false来实现,就像下面这样:
<%@ page session="false" %>
JSP引擎将隐含的session对象暴露给开发者。由于提供了session对象,开发者就可以方便地存储或检索数据。
下表列出了session对象的一些重要方法:
S.N. | 方法 & 描述 |
---|---|
1 | public Object getAttribute(String name)返回session对象中与指定名称绑定的对象,如果不存在则返回null |
2 | public Enumeration getAttributeNames()返回session对象中所有的对象名称 |
3 | public long getCreationTime()返回session对象被创建的时间, 以毫秒为单位,从1970年1月1号凌晨开始算起 |
4 | public String getId()返回session对象的ID |
5 | public long getLastAccessedTime()返回客户端最后访问的时间,以毫秒为单位,从1970年1月1号凌晨开始算起 |
6 | public int getMaxInactiveInterval()返回最大时间间隔,以秒为单位,servlet 容器将会在这段时间内保持会话打开 |
7 | public void invalidate()将session无效化,解绑任何与该session绑定的对象 |
8 | public boolean isNew()返回是否为一个新的客户端,或者客户端是否拒绝加入session |
9 | public void removeAttribute(String name)移除session中指定名称的对象 |
10 | public void setAttribute(String name, Object value) 使用指定的名称和值来产生一个对象并绑定到session中 |
11 | public void setMaxInactiveInterval(int interval)用来指定时间,以秒为单位,servlet容器将会在这段时间内保持会话有效 |
JSP Session应用
这个例子描述了如何使用HttpSession对象来获取创建时间和最后一次访问时间。我们将会为request对象关联一个新的session对象,如果这个对象尚未存在的话。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@ page import="java.io.*,java.util.*" %><% // 获取session创建时间 Date createTime = new Date(session.getCreationTime()); // 获取最后访问页面的时间 Date lastAccessTime = new Date(session.getLastAccessedTime()); String title = "再次访问菜鸟教程实例"; Integer visitCount = new Integer(0); String visitCountKey = new String("visitCount"); String userIDKey = new String("userID"); String userID = new String("ABCD"); // 检测网页是否由新的访问用户 if (session.isNew()){ title = "访问菜鸟教程实例"; session.setAttribute(userIDKey, userID); session.setAttribute(visitCountKey, visitCount); } else { visitCount = (Integer)session.getAttribute(visitCountKey); visitCount += 1; userID = (String)session.getAttribute(userIDKey); session.setAttribute(visitCountKey, visitCount); }%><html><head><title>Session 跟踪</title></head><body><h1>Session 跟踪</h1><table border="1" align="center"> <tr bgcolor="#949494"> <th>Session 信息</th> <th>值</th></tr> <tr> <td>id</td> <td><% out.print( session.getId()); %></td></tr> <tr> <td>创建时间</td> <td><% out.print(createTime); %></td></tr> <tr> <td>最后访问时间</td> <td><% out.print(lastAccessTime); %></td></tr> <tr> <td>用户 ID</td> <td><% out.print(userID); %></td></tr> <tr> <td>访问次数</td> <td><% out.print(visitCount); %></td></tr> </table> </body></html>
试着访问 http://localhost:8080/testjsp/main.jsp ,第一次运行时将会得到如下结果:
再次访问,将会得到如下结果:
删除Session数据
当处理完一个用户的会话数据后,您可以有如下选择:
移除一个特定的属性:
调用public void removeAttribute(String name) 方法来移除指定的属性。
删除整个会话:
调用public void invalidate() 方法来使整个session无效。
设置会话有效期:
调用 public void setMaxInactiveInterval(int interval) 方法来设置session超时。
登出用户:
支持servlet2.4版本的服务器,可以调用 logout()方法来登出用户,并且使所有相关的session无效。
配置web.xml文件:
如果使用的是Tomcat,可以向下面这样配置web.xml文件:
<session-config> <session-timeout>15</session-timeout> </session-config>
超时以分钟为单位,Tomcat中的默认的超时时间是30分钟。
Servlet中的getMaxInactiveInterval( ) 方法以秒为单位返回超时时间。如果在web.xml中配置的是15分钟,则getMaxInactiveInterval( ) 方法将会返回900。
“
感觉只要学过Python爬虫的同学应该都知道requests这个库吧,它在我们的Python爬虫任务中应该是最常用的一个库了!今天跟大家分享的这个模块requests_html,他的作者和前者是同一人!这是一个解析HTML的库,用起来和requests一样方便,下面就来介绍一下它!
”
#!/usr/bin/env python3
# coding : utf-8
# Author : xiao qiang
# 微信公众号 : xiaoqiangclub
# Software : PyCharm
# File : test.py
# Time : 2021/5/29 7:57
from requests_html import HTMLSession
if __name__ == '__main__':
url = 'https://wwww.baidu.com'
session = HTMLSession() # 获取实例化session对象
r = session.get(url) # 这里的请求和requests的几乎一样!同样可以根据需要添加headers等参数
#!/usr/bin/env python3
# coding : utf-8
# Author : xiao qiang
# 微信公众号 : xiaoqiangclub
# Software : PyCharm
# File : test.py
# Time : 2021/5/29 7:57
from requests_html import HTMLSession
if __name__ == '__main__':
url = 'https://wwww.baidu.com'
session = HTMLSession() # 获取实例化session对象
r = session.get(url) # 这里的请求和requests的几乎一样!同样可以根据需要添加headers等参数
# 获取html页面
# html = r.content.decode() # requests方式
get_html = r.html.html # requests_html中的方法
print(get_html[:15], '...')
# 快速获取链接
pprint(r.html.links) # 获取html中的链接(href属性)
pprint(r.html.absolute_links) # 会自动拼接url生成绝对链接
def xpath(self, selector: str, *, clean: bool = False, first: bool = False, _encoding: str = None) -> _XPath:
- selector,要用的 xpath选择器;
- clean,布尔值,如果为True,会清除HTML中style和script标签;
- first,布尔值,如果为True,会返回第一个元素,否则会返回满足条件的元素列表;
- _encoding,编码格式。
pprint(r.html.xpath('//li[@class="hotsearch-item odd"]/a'))
pprint(r.html.xpath('//li[@class="hotsearch-item odd"]/a', first=True).text)
def find(self, selector: str = "*", *, containing: _Containing = None, clean: bool = False, first: bool = False, _encoding: str = None) -> _Find:
- selector,要用的CSS选择器;
- clean,布尔值,如果为True,会清除HTML中style和script标签;
- containing,如果设置该属性,只返回包含该属性文本的标签;
- first,布尔值,如果为True,会返回第一个元素,否则会返回满足条件的元素列表;
- _encoding,编码格式。
pprint(r.html.find('a.mnav'))
pprint(r.html.find('a.mnav', first=True).text)
pprint(r.html.find('a.mnav')[0].text)
pprint(r.html.find('a.mnav')[0].attrs)
pprint(r.html.find('a.mnav')[0].html)
def search(self, template: str) -> Result:
# 只有一个参数
template: 就是要检索的内容,这里使用英文状态的 {} 来获取内容,有点类似正则里面的 ()
ret = r.html.find('a.mnav')[0].search('新{}')
pprint(ret)
pprint(type(ret))
pprint(ret[0])
ret = r.html.find('a.mnav')[0].search_all('新{}')
pprint(ret)
pprint(type(ret))
pprint(ret[0][0])
ret = r.html.search_all('百度{}')
pprint(ret)
pprint(type(ret))
pprint(ret[0][0])
search补充
>>> from requests_html import HTML
>>> doc = """<a href='https://www.baidu.com'>"""
>>> html = HTML(html=doc)
>>> html.links
{'https://www.baidu.com'}
# 和上面一段代码接起来
>>> script = """
() => {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
deviceScaleFactor: window.devicePixelRatio,
}
}
"""
>>> val = html.render(script=script, reload=False) # render()方法 后面会讲
>>> print(val)
{'width': 800, 'height': 600, 'deviceScaleFactor': 1}
>>> print(html.html)
<html><head></head><body><a href="https://www.baidu.com"></a></body></html>
>>> r = session.get('http://python-requests.org/')
>>> r.html.render()
[W:pyppeteer.chromium_downloader] start chromium download.
Download may take a few minutes.
[W:pyppeteer.chromium_downloader] chromium download done.
[W:pyppeteer.chromium_downloader] chromium extracted to: C:\Users\xxxx\.pyppeteer\local-chromium\571375
>>> r.html.search('Python 2 will retire in only {months} months!')['months']
'<time>25</time>'
def render(self, retries: int = 8, script: str = None, wait: float = 0.2, scrolldown=False, sleep: int = 0, reload: bool = True, timeout: Union[float, int] = 8.0, keep_page: bool = False):
- retries: 加载页面失败的次数
- script: 页面上需要执行的JS脚本(可选)
- wait: 加载页面前等待的时间(秒),防止超时(可选)
- scrolldown: 页面向下滚动的次数(整数)
- sleep: 在页面初次渲染之后的等待时间
- reload: 如果为False,那么页面不会从浏览器中加载,而是从内存中加载,只有设置为True才会在浏览器中渲染JS
- keep_page: 如果为True,允许您使用 r.html.page 访问浏览器页面
def next(self, fetch: bool = False, next_symbol: _NextSymbol = DEFAULT_NEXT_SYMBOL) -> _Next:
fetch: 一个布尔型参数,默认为False:直接返回下一页的 url地址;
如果设置为True:则直接返回下一页的 HTML对象
DEFAULT_NEXT_SYMBOL = ['next', 'more', 'older']
# next()方法
def next(self, fetch: bool = False, next_symbol: _NextSymbol = DEFAULT_NEXT_SYMBOL) -> _Next:
"""Attempts to find the next page, if there is one. If ``fetch``
is ``True`` (default), returns :class:`HTML <HTML>` object of
next page. If ``fetch`` is ``False``, simply returns the next URL.
"""
def get_next():
candidates = self.find('a', containing=next_symbol) # 寻找 包含字段'next', 'more', 'older' 的a标签
def __init__(self, loop=None, workers=None, mock_browser: bool = True, *args, **kwargs):
loop: 使用的Asyncio循环。
workers: 用于执行异步调用的线程数量。如果不传递,它将默认为电脑处理器数量乘以5
>>> async def get_pyclock():
... r = await asession.get('https://pythonclock.org/')
... await r.html.arender()
... return r
...
>>> results = asession.run(get_pyclock, get_pyclock, get_pyclock) # 这里作者将同一个页面使用异步方式进行了3次渲染,但是实际上使用的时间并不是平时的3倍!可能只是比平时渲染一个页面多花了一点时间而已!这就是异步的好处!
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# @Author : xiao qiang
# @WeChat : xiaoqiangclub
# @Software : PyCharm
# @File : test002.py
# @Time : 2021/5/30 19:48
from requests_html import AsyncHTMLSession
aSession = AsyncHTMLSession()
async def test(tt, yy):
r = await aSession.get('https://www.baidu.com/')
await r.html.arender()
print('-{}-{}-'.format(tt, yy))
return r
ret1 = aSession.run(lambda: test('1', 'a'))
ret2 = aSession.run(lambda: test('2', 'b'))
ret3 = aSession.run(lambda: test('3', 'c'))
print(ret1)
print(ret2)
print(ret3)
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# @Author : xiao qiang
# @WeChat : xiaoqiangclub
# @Software : PyCharm
# @File : test002.py
# @Time : 2021/5/30 19:48
from requests_html import AsyncHTMLSession
aSession = AsyncHTMLSession()
async def test(tt, yy):
r = await aSession.get('https://www.baidu.com/')
await r.html.arender()
print('-{}-{}-'.format(tt, yy))
return r
# ret1 = aSession.run(lambda: test('1', 'a'))
# ret2 = aSession.run(lambda: test('2', 'b'))
# ret3 = aSession.run(lambda: test('3', 'c'))
# print(ret1)
# print(ret2)
# print(ret3)
#
test_dict = {
'1': 'a',
'2': 'b',
'3': 'c'
}
tasks = [lambda i=i, y=y: test(i, y) for i, y in
test_dict.items()] # lambda传参误区参考文章:https://www.jianshu.com/p/58ebd1618556
ret = aSession.run(*tasks) # 注意前面有个 *,不可少!# 参考文章:https://www.jianshu.com/p/58ebd1618556
print(ret)
>>> from requests_html import AsyncHTMLSession
>>> asession = AsyncHTMLSession()
>>> async def get_pythonorg():
... r = await asession.get('https://python.org/')
... return r
...
>>> async def get_reddit():
... r = await asession.get('https://reddit.com/')
... return r
...
>>> async def get_google():
... r = await asession.get('https://google.com/')
... return r
...
>>> results = asession.run(get_pythonorg, get_reddit, get_google)
>>> results # check the requests all returned a 200 (success) code
[<Response [200]>, <Response [200]>, <Response [200]>]
>>> # Each item in the results list is a response object and can be interacted with as such
>>> for result in results:
... print(result.html.url)
...
https://www.python.org/
https://www.google.com/
https://www.reddit.com/
from requests_html import HTMLSession, HTML, AsyncHTMLSession
from pprint import pprint
class DouBanTest:
def __init__(self):
self.start_url = 'https://movie.douban.com/chart' # 豆瓣电影排行榜url
self.js_url = 'https://movie.douban.com/explore#!type=movie&tag=%E7%83%AD%E9%97%A8&sort=recommend&page_limit=20&page_start=0'
self.session = HTMLSession() # 实例化session
self.aSession = AsyncHTMLSession() # 实例化异步session
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36'
}
def get_response(self, url):
"""获取响应,并返回requests_html中的HTML对象"""
r = self.session.get(url, headers=self.headers)
# print(r)
return r.html
# 快速获取页面中的url
def fast_get_urls(self):
"""快速获取页面中的url"""
html = self.get_response(self.start_url)
# HTML的 links属性 可以快速获取到页面中 a标签中的href属性
urls = html.links
# pprint(urls)
# HTML的 absolute_links属性 可以快速获取到页面中 a标签中的href属性,并返回绝对url地址
absolute_urls = html.absolute_links
pprint(absolute_urls)
# 清洗数据(提取数据)
def get_data_by_xpath(self):
"""使用xpath获取数据"""
html = self.get_response(self.start_url)
a_list = html.xpath('//table//div/a')
# pprint(a_list)
# 提取它的标题和url
movies_info = dict()
for a in a_list:
title = a.text # 获取标题(文本)
# print(title)
movie_url = a.attrs.get('href') # 使用 attrs 来解析element元素,并获得一个字典
# print(movie_url)
# print('-----')
movies_info[title] = movie_url
pprint(movies_info)
# 清洗数据(提取数据)
def get_data_by_css(self):
"""使用css获取数据"""
html = self.get_response(self.start_url)
a_list = html.find('tr[class="item"] div a') # 参考 css选择器 语法
# pprint(a_list)
# 提取它的标题和url
movies_info = dict()
for a in a_list:
title = a.text # 获取标题(文本)
# print(title)
movie_url = a.attrs.get('href') # 使用 attrs 来解析element元素,并获得一个字典
# print(movie_url)
# print('-----')
movies_info[title] = movie_url
pprint(movies_info)
# 清洗数据(提取数据)
def get_data_by_re(self):
"""使用css获取数据"""
html = self.get_response(self.start_url)
# search() 获取第一条匹配的数据
# first_url = html.search('a href="{}"') # 参数可以参考正则,获取第一条匹配的数据
# pprint(first_url)
# search_all() 获取所有满足条件的数据列表
# url_list = html.search_all('a h{}f="{}"')
url_list = html.search_all('a h{title}f="{url}"') # 对取值方式进行命名,返回一个列表
# pprint(url_list)
#
# 提取数据
for url in url_list:
print(url)
print(url['title']) # 使用 result[name] 进行取值
print(url['url'])
# print(url[0])
# print(url[1])
print('----------')
# HTML类
def use_HTML(self):
"""使用HTML模块处理文档"""
html_str = '<a class="nbg" href="https://movie.douban.com/subject/3099221/" title="活死人军团">'
html = HTML(html=html_str)
# links
print(html.links)
# search()
print(html.search('href="{}"'))
# 加载JS页面
def load_js(self):
html = self.get_response(self.js_url)
# 使用一个 render()方法 来加载js(实际上使用这个pyppeteer)
# html.render(wait=3) # js加载
print(html.html)
async def send_requests_ues_async(self, url):
"""发送异步请求"""
"""获取响应,并返回requests_html中的HTML对象"""
r = await self.aSession.get(url, headers=self.headers)
# print(r)
return r.html
def get_response_by_async(self):
url_list = [
'https://www.baidu.com',
'https://www.qq.com',
'https://www.163.com',
]
tasks = [lambda url=url: self.send_requests_ues_async(url) for url in url_list]
ret = self.aSession.run(*tasks) # 返回的是一个HTML对象列表
# print(ret)
# print(ret[0].html)
for html in ret:
print(html)
async def load_js_use_async(self, url):
"""异步加载js"""
html = await self.send_requests_ues_async(url)
# 异步加载js
await html.arender()
return html
def get_js_by_async(self):
# ret = self.aSession.run(self.load_js_use_async)
#
# print(ret[0].html)
url_list = [
'https://www.baidu.com',
'https://www.qq.com',
'https://www.163.com',
]
tasks = [lambda url=url: self.load_js_use_async(url) for url in url_list]
ret = self.aSession.run(*tasks) # 返回的是一个HTML对象列表
# print(ret)
# print(ret[0].html)
for html in ret:
print(html)
if __name__ == '__main__':
test = DouBanTest()
# test.get_data_by_xpath()
# test.get_data_by_css()
# test.fast_get_urls()
# test.get_data_by_re()
# test.use_HTML()
# test.load_js()
# test.get_response_by_async()
test.get_js_by_async()
【本文由 "XiaoqiangClub" 发布,2021年6月17日】
*请认真填写需求信息,我们会在24小时内与您取得联系。