16. 你很勇哦,这么点数据就敢用异步加载?

时间:2023-01-01 09:51:03

爬虫训练场项目第16课,异步AJAX加载学校清单。
爬虫训练场,让天下没有失效的爬虫,2023年橡皮擦最新专栏。
项目仓库地址 :https://gitcode.net/hihell/spider_playground
博客清单:https://pachong.vip/blog

Bootstrap 实现 ajax 请求

本篇博客的核心是使用 Bootstrap 中的 JavaScript 插件发送 Ajax 请求,下面先看一个示例,展示如何使用 Bootstrap 中的 $.ajax() 方法发送 AJAX 请求,这里使用的 $.ajax() 由 jQuery 插件实现,所以提前在模板文件中进行一下导入。

https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.6.0.min.js

实现本步骤操作,需要在 app/templates/school 目录中新增 ajax_list.html 文件,然后输入下述代码。

{% extends "base.html" %}
{% block script %}
<script type="text/javascript" src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.6.0.min.js"></script>
<script type="text/javascript">

</script>
{% endblock script %}


{% block content %}
{% endblock %}

该文件继承了 base.html 文件,然后在其 script 块中,可以编写JS代码。

使用 jQuery 发送 Ajax 请求的示例代码如下所示,后续将在此基础上实现请求和渲染逻辑。

$.ajax({
  type: "POST",
  url: "/path/to/server",
  data: {
    key1: "value1",
    key2: "value2"
  },
  success: function(response) {
    console.log("AJAX request succeeded!");
  },
  error: function(error) {
    console.log("AJAX request failed: " + error);
  }
});

接下来将刚刚的文件,绑定到视图函数中,代码编写到 school/index.py 中,如下所示。

@s.route('ajax_list')
def ajax_list():
    page = 1  # 初始化第一页数据

    pagination = pagination_object(page)
    return render_template('school/ajax_list.html', pagination=pagination)

这里将分页对象的创建的函数进行了封装,便于后续重复使用,pagination_object() 函数内容如下所示。

def pagination_object(page=1):
    # schools = School.query.all()

    query = School.query

    total = query.count()  # 获取数据总量
    pagination = Pagination(page, total)  # 获取分页对象详细参数

    if total != 0:
        data_list = query.offset(pagination["offset"]).limit(pagination["page_size"]).all()
    else:
        data_list = []
    pagination["data_list"] = data_list
    return pagination

使用 AJAX 渲染数据重点在 JS 部分,核心操作为分页请求,所以首次进入页面,需要对第一页数据进行渲染,补充 ajax_list.html 首次渲染部分代码。

<div class="container" id="school_list">

    {% for school in pagination.data_list %}
    <div class="row mt-3">
        <div class="col">
            <div class="d-flex">
                <div class="flex-shrink-0">
                    <a href="#">
                        <img class="rounded-pill img-thumbnail" width="64" height="64" src="{{school.pic}}" alt="">
                    </a>
                </div>
                <div class="flex-grow-1 ms-3">
                    <h5 class="float-start pe-3">{{school.name}}</h5>
                    <p class="ms-3">
                        {% for fea in school.feature.split(',') %}
                        <span class="badge rounded-pill bg-primary">{{fea}}</span>
                        {% endfor %}
                    </p>
                    <p><em>所在省市:<span class="text-black-50">{{school.province}} -- {{school.city}}</span></em></p>
                </div>
            </div>
        </div>
    </div>
    {% endfor %}

</div>
<div class="container">
    <div class="row">
        <div class="col">
            <span class="text-dark float-end align-middle"
                  style="line-height: 40px;">合计  {{pagination.total}} 条数据</span>
            <ul class="pagination float-end">
                <li class="page-item prev" page="{{pagination.prev_page}}">
                    <a class="page-link" href="#">上一页</a>
                </li>
                <li class="page-item next" page="{{ pagination.next_page }}"><a class="page-link"
                                                                                href="#">下一页</a>
                </li>
            </ul>
        </div>
    </div>
</div>

上述代码进行了两部分修改,第一个是给 .container 类绑定的 DIV 增加了一个 ID 值,第二个修改是给分页 <li> 标签增加了自定义属性 page,并分别绑定上一页和下一页的页码,然后取消原来的超链接跳转。

核心 JS 部分

准备工作已经完成,下面的重点在数据 AJAX 请求部分。该部分逻辑分步骤实现。

第一步,给上一页和下一个按钮绑定事件

    $(function(){
        $('.page-item').on('click',function(){
            page = $(this).attr('page');
            // 获取数据
            get_data(page);
        })
    })

这部分直接使用 .on() 方法进行绑定即可,注意看上述代码中的 get_data(),该函数用于发起 AJAX 请求,获取响应,并渲染数据,代码如下。

    function get_data(page){
        $.ajax({
            type: "get",
            url: "/ss/api2",
            data: {
                page: page
            },
            success: function(response) {
                // ajax 请求成功
                render_data(response);
                // 修改分页数据
                $('.prev').attr('page',response["prev_page"]);
                $('.next').attr('page',response["next_page"]) ;
                console.log("AJAX request succeeded!");
            },
            error: function(error) {
                console.log("AJAX request failed: " + error);
            }
         });
    }

请求的数据接口地址为 /ss/api2,所以还需要回到 school/index.py 文件中,增加该部分函数。

@s.route('api2')
def ajax_list_school():
    page = int(request.args.get("page", 1))

    pagination = pagination_object(page)

    return jsonify(pagination)

上述代码出现了一个新函数 jsonify(),在 Python Flask 中,可以使用 jsonify() 函数将数据转换为 JSON 并返回给客户端。

简单的案例如下所示。

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/data')
def get_data():
    data = {'key1': 'value1', 'key2': 'value2'}
    return jsonify(data)

if __name__ == '__main__':
    app.run()

在上面的代码中,我们定义了一个视图函数 get_data(),该函数创建了一个字典,并使用 jsonify() 函数将其转换为 JSON 并返回给客户端。

继续回到JS代码部分,在接收到数据请求之后,我们依次编写了如下代码,其分为两部分,第一部分是数据渲染,第二部分是分页组件页码变化。

render_data(response);
// 修改分页数据
$('.prev').attr('page',response["prev_page"]);
$('.next').attr('page',response["next_page"]) ;

这里主要看 render_data() 函数的实现,代码如下所示。

    function render_data(response){
            data_list = response["data_list"];
            if(data_list.length>0){
             // 清空原有数据
                $('#school_list').empty();
                $.each(data_list,function(index,item){
     // 循环渲染数据
                    var row = $('<div>', {
                      'class': 'row mt-3',
                      'data-custom-attribute': 'value'
                    });
                    var col =$('<div>', {
                      'class': 'col'
                    });
                    var d_flex = $('<div>', {
                      'class': 'd-flex'
                    });
                    d_flex.append('<div class="flex-shrink-0"><a href="#"><img class="rounded-pill img-thumbnail" width="64" height="64" src="'+item.pic+'" alt=""></a></div>');
                    // 生成一下标签代码
                    var badge = "";
                    $.each(item.feature.split(','),function(i,f){
                       badge += ' <span class="badge rounded-pill bg-primary">'+f+'</span> ';
                    });

                    d_flex.append('<div class="flex-grow-1 ms-3"><h5 class="float-start pe-3">'+item.name+'</h5><p class="ms-3">'+badge+'</p><p><em>所在省市:<span class="text-black-50">'+item.province+'--'+item.city+'</span></em></p></div>')

                    col.append(d_flex);
                    row.append(col);
                    $('#school_list').append(row);
                })
            }
    }

JS代码使用了循环渲染,拼接HTML DOM 对象的方式实现,其重点代码就是在循环后台发送过来的学校对象数组,然后追加到 ID = school_list 的 DIV 中。

到此,核心代码已经编写完毕,可以进行运行测试。

但是当点击下一页时,控制台出现了如下错误。

TypeError: Object of type 'School' is not JSON serializable

该原因是由于 School 实体直接返回的是对象,不是可JSON序列化的数据,所以要继续进行修改。

找到 model.py 文件,修改 School 类文件,增加一个 to_dict() 方法。

class School(db.Model, EntityBase):

 # 前面的字典注意不要丢失
    def to_dict(self):
        return {
            'name': self.name,
            'province': self.province,
            'city': self.city,
            'feature': self.feature,
            'hotValue': self.hotValue,
            'pic': self.pic,
            'category': self.category,
            'batchTimes': self.batchTimes
        }

然后回到 school/index.py 文件中,在 pagination_object() 函数中增加类型转换代码。

data_list = [row.to_dict() for row in data_list]
pagination["data_list"] = data_list
return pagination

重启项目,再次点击分页,即完成本案例,无刷新 AJAX 请求。

本案例到此结束,已更新到 爬虫训练场 欢迎大家访问学习。
项目同步到代码仓库 https://gitcode.net/hihell/spider_playground

????????????????????????
???? 你正在阅读 【梦想橡皮擦】 的博客
???? 阅读完毕,可以点点小手赞一下
???? 发现错误,直接评论区中指正吧
???? 橡皮擦的第 813 篇原创博客

从订购之日起,案例 5 年内保证更新