整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

在 ASP.NET CORE 中使用 SESSION

ession 是保存用户和 Web 应用的会话状态的一种方法,ASP.NET Core 提供了一个用于管理会话状态的中间件。在本文中我将会简单介绍一下 ASP.NET Core 中的 Session 的使用方法。

安装配置 Session

nuget 添加引用 Microsoft.AspNetCore.Session

ession 是基于 IDistributedCache 构建的,所以必须引用一种 IDistributedCache 的实现,ASP.NET Core 提供了多种 IDistributedCache 的实现 (Redis、SQL Server、In-memory)

In-memory

services.AddDistributedMemoryCache();
services.AddSession();

SQL Server

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(以及可选地,SchemaNameTableName)通常应该被存储的源控制(如UserSecrets)以外,因为它们可能包含凭证。

像所有的缓存实现一样,你的应用程序应该使用一个实例来获取和设置缓存值IDistributedCache,而不是SqlServerCache。该示例SqlServerCacheProduction环境中实现(因此已配置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();

Redis

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 存储数据

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.方法 & 描述
1public Object getAttribute(String name)返回session对象中与指定名称绑定的对象,如果不存在则返回null
2public Enumeration getAttributeNames()返回session对象中所有的对象名称
3public long getCreationTime()返回session对象被创建的时间, 以毫秒为单位,从1970年1月1号凌晨开始算起
4public String getId()返回session对象的ID
5public long getLastAccessedTime()返回客户端最后访问的时间,以毫秒为单位,从1970年1月1号凌晨开始算起
6public int getMaxInactiveInterval()返回最大时间间隔,以秒为单位,servlet 容器将会在这段时间内保持会话打开
7public void invalidate()将session无效化,解绑任何与该session绑定的对象
8public boolean isNew()返回是否为一个新的客户端,或者客户端是否拒绝加入session
9public void removeAttribute(String name)移除session中指定名称的对象
10public void setAttribute(String name, Object value) 使用指定的名称和值来产生一个对象并绑定到session中
11public 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。

  • 基本使用
    • 发送请求
    • 解析响应获
  • 获取需要的内容
    • 快速获取链接
    • 获取元素
  • 高级功能
    • JS渲染
    • 自动翻页(不太好用)
  • 异步
    • 异步渲染JS
    • 异步发送请求

初识requests_html模块

感觉只要学过Python爬虫的同学应该都知道requests这个库吧,它在我们的Python爬虫任务中应该是最常用的一个库了!今天跟大家分享的这个模块requests_html,他的作者和前者是同一人!这是一个解析HTML的库,用起来和requests一样方便,下面就来介绍一下它!

  • 参考视频

使用requests_html

安装

  • 依然是那个命令 pip3 install -i https://pypi.doubanio.com/simple requests_html
  • 注意:由于requests_html模块中使用了异步asynico模块,所以官方声明,需要在python3.6以上版本才能正常使用!

基本使用

发送请求

  • requests_html发送请求获取页面需要先实例化一个HTMLSession对象,然后使用get/post...方法获取响应,如下列代码
#!/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等参数
  • requests_html发送请求的方式和requests中使用session方式发送请求几乎是一样的,可以对比参考
  • requests_html同样可以发送get/post等请求,且可以和requests同样携带headers/data等参数,具体用法参考requests

解析响应获

  • 接上,我们需要将获取的响应解析获取html页面,在这里我们同样可以使用requests中的r.content.decode()等原方法!
  • 但是在requests_html中还提供了更便捷的方法:r.html.html
  • r.html.html实际上是使用了requests_html中的HTML类(负责对HTML进行解析)来进行解析!如下
#!/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], '...')
  • 运行结果(这里只显示了部分结果!)

获取需要的内容

快速获取链接

  • requests_html中提供了快速获取网址链接的方法
  • 使用linksabsolute_links两个属性分别可以返回HTML对象所包含的所有链接和绝对链接(均不包含锚点)
# 快速获取链接
pprint(r.html.links)  # 获取html中的链接(href属性)
pprint(r.html.absolute_links)  # 会自动拼接url生成绝对链接
  • 部分运行结果如下

获取元素

  • requests_html中的HTML对象可以直接使用xpathcss选择器

使用xpath

  • requests_html中的HTML对象支持xpath语法,它有以下几个参数:
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,编码格式。
  • 接上面的例子!使用获取到的响应得到HTML对象r.html
pprint(r.html.xpath('//li[@class="hotsearch-item odd"]/a'))
pprint(r.html.xpath('//li[@class="hotsearch-item odd"]/a', first=True).text)
  • 运行结果
  • xpath语法

使用css选择器(find方法)

  • requests_html中的HTML对象支持css选择器,它有以下几个参数:
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,编码格式。
  • 接上面的例子!使用获取到的响应得到HTML对象r.html
pprint(r.html.find('a.mnav'))
pprint(r.html.find('a.mnav', first=True).text)
  • 运行结果
  • css选择器语法
  • 可以使用text属性来获取元素的文本内容

pprint(r.html.find('a.mnav')[0].text)

  • 执行结果
  • 如果要获取元素的attribute,用attrs属性

pprint(r.html.find('a.mnav')[0].attrs)

  • 执行结果
  • 获取到attrs属性后,就可以使用字典的相关方法获取内容了!
  • 最后还可以使用html属性获取一个元素的html代码,如下

pprint(r.html.find('a.mnav')[0].html)

  • 执行结果

正则搜索(search、search_all)

  • requests_html除了上面的方式还可以使用search/search_all来直接搜索内容,返回的是一个Result对象/Result对象列表实际上是作者将re正则进行了封装)!
def search(self, template: str) -> Result:
# 只有一个参数
template: 就是要检索的内容,这里使用英文状态的 {} 来获取内容,有点类似正则里面的 ()
  • 使用英文状态的 {} 来获取内容,如下
    ret = r.html.find('a.mnav')[0].search('新{}')
    pprint(ret)
    pprint(type(ret))
    pprint(ret[0])
  • 执行结果
  • search()获取到的是第一个匹配的对象,而searchh_all()则是获取所有匹配的对象,得到的是一个列表,如下

ret = r.html.find('a.mnav')[0].search_all('新{}')
pprint(ret)
pprint(type(ret))
pprint(ret[0][0])

  • 运行结果
  • 除了对某个元素进行检索外,还可以直接对html对象进行搜索,如下

ret = r.html.search_all('百度{}')
pprint(ret)
pprint(type(ret))
pprint(ret[0][0])

  • 运行结果
  • requests_html内容提取的方式这么多,大家可以根据需要和习惯选择使用!

search补充

  • 在上面提到的search()/search_all()方法中,我们设定的template参数可以有多个取值(多个{}),得到的结果是一个列表,我们可以遍历别表进行取值 取值的时候可以通过result[索引]的方式进行获取对应的数据,如下(示例部分代码) search_ret = r.html.search_all('<a h{}f="{}"',)
    print(search_ret)
    for ret in search_ret:
    print(ret)
    print(ret[1])
    print('--------------')
    运行结果(部分)
  • 除此之外,我们还可以对取值进行命名,返回的结果是可以使用类似字典(不是字典)[name]的方式取值(不能使用get),如下示例(部分代码) search_ret = r.html.search_all('<a h{test}f="{url}"',)
    print(search_ret)
    for ret in search_ret:
    print(ret)
    print(ret['name'])
    print('--------------')
  • 运行结果(部分)
  • 以上就是对requests_html模块search()/search_all()方法的补充内容!

HTML类

  • requests_html中使用HTML类负责对HTML进行解析
  • HTML类不仅可以解析网络请求获取的响应,还可以直接解析html文本,如下
>>> from requests_html import HTML
>>> doc = """<a href='https://www.baidu.com'>"""

>>> html = HTML(html=doc)
>>> html.links
{'https://www.baidu.com'}
  • 还可以直接渲染JS,如下
# 和上面一段代码接起来
>>> 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>

高级功能

  • 前面介绍的是requests_htmlrequests库的基础上整合的html解析&数据筛选的功能!
  • 下面要为大家介绍的是requests_html模块中的一些高级功能:自动渲染JS&智能分页

JS渲染

  • 我们在做爬虫项目的时候会遇到网站的页面是由js生成的情况!这个时候要么就是自己去一步一步地分析请求,要么就是使用selenium等第三方库来进行渲染页面,为了解决这个难题,requests_html模块中引进了pyppeteer,使用pyppeteer可以像使用selenium一样实现网站的完整加载!而且pyppeteer是一个异步模块!效率会更高!
  • requests_html模块在HTML对象的基础上使用render()方法重新加载js页面
  • 注意:在第一次使用render()的时候,系统在用户目录(默认是~/.pyppeteer/)中下载一个chromium。下载过程只在第一次执行,以后就可以直接使用chromium来执行任务了。在没有科学上网的环境下可能下载速度有点慢,请耐心等待...
  • 下面是一个官方示例
>>> 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>'
  • requests_html模块在HTML对象的基础上使用render()方法重新加载js页面,它有以下几个参数:
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 访问浏览器页面
  • requests_html还支持异步渲染JS[^1]

自动翻页(不太好用)

  • 很多网站会出现翻页的情况,requests_html模块的HTML对象中提供了一个next()方法来实现自动翻页!
  • requests_html模块在HTML对象的基础上使用next()方法来实现自动翻页!它有以下几个参数:
def next(self, fetch: bool = False, next_symbol: _NextSymbol = DEFAULT_NEXT_SYMBOL) -> _Next:
fetch: 一个布尔型参数,默认为False:直接返回下一页的 url地址;
       如果设置为True:则直接返回下一页的 HTML对象
  • 这个方法我自己测试了一下,只有一些特定的网站才能实现这个功能,requests_html在的源码中可以看到,作者通过搜索包含'next', 'more', 'older'字段的a标签(因为一般情况下我们的下一页url就是在a标签下的href属性中),所以只有满足了他的条件才会实现这个功能(也就是说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标签
  • 这里我就不做举例了,大家可以自行去尝试!

异步

  • requests_html中还支持了异步功能
  • requests_html是使用了asynico来封装了一些异步操作,所以这里的一些操作会用到asynico库相关的一些知识,如果您还不太了解,请自行学习!

异步渲染JS

  • 前面我们介绍了使用render()方法渲染JS,其实它还支持异步渲染
  • 一般异步渲染使用在我们有多个页面需要进行渲染的情况下,因为只要在多任务的时候才能体现出异步的高效率,它有以下几个参数:
def __init__(self, loop=None, workers=None, mock_browser: bool = True, *args, **kwargs):
loop: 使用的Asyncio循环。
workers: 用于执行异步调用的线程数量。如果不传递,它将默认为电脑处理器数量乘以5
  • 更多的异步使用方法请参考asyncio库的使用方法,下面是一个官方示例
>>> 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倍!可能只是比平时渲染一个页面多花了一点时间而已!这就是异步的好处!
  • 注意:results是一个列表

异步传参

  • 这里是后面加的内容,因为我突然想到有时候我们的函数有可能是带参数的,那么这个时候我们可以使用lambda来进行传参,看下面示例
#!/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)
  • 注意:这里ret1/ret2/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)

  • 这里在使用lambda传参的时候可能会出现一个错误,可以参考文章解决!
  • 运行结果

异步发送请求

  • 我们在做爬虫的时候,特别是大型爬虫的时候,需要对很多页面进行操作,或者说是需要发送很多请求,也就是需要进行很多IO操作。所以,使用异步发送请求能显著地提升我们的爬虫效率!
  • requests_html模块中,设置了一个AsyncHTMLSession类来实现发送异步请求
  • ,下面是一个官方示例
>>> 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/
  • 上面的示例用到了asynico库中一些相关的知识,如果您还不太了解,请自行学习!

总结

  • requests_html模块requests库的基础上封装了页面解析数据清理的功能,并且添加了对当前比较流行的异步操作,让我们在做爬虫项目(一般项目)的时候无需再去使用多个第三方模块来实现功能,几乎是提供了一站式的服务!
  • 所以Python写爬虫使用requests_html就对了!(当然大项目还是首选scrapy,个人愚见!)
  • 更多内容

视频讲解源码

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日】