分类: Django

  • 部署本地DeepSeek大模型时,怎么选择合适的模型文件?

    问题是什么?

    大家都知道deepseek开源了大模型,可能想本地部署试试效果如何,去各个大模型平台看了一遍,这时候部分人可能会很奇怪,为什么这些平台官方推荐的deepseek大模型都是“DeepSeek-R1-Distill-Qwen” 这种?deepseek和Qwen有什么关系?什么是Distill?

    什么是DeepSeek,Distill,Qwen?

    • DeepSeek-R1,是幻方量化旗下AI公司深度求索(DeepSeek)研发的推理模型 。DeepSeek-R1的强项之一是具有很强的推理能力。
      • 数据搜索能力
    • Qwen,阿里巴巴的大模型,强项是具有较为丰富的参数。
      • 丰富的数据
    • Distill,蒸馏这个过程,就是结合两者的优势,组合成一个新的大模型。
      • 强大的数据集 + 强大的推理计算能力

     

    综上,DeepSeek-R1-Distill-Qwen 类似一种果树的嫁接技术,将deepseek的推理能力嫁接到Qwen的数据里。

    怎么选择适合自己硬件大模型?

    • DeepSeek-R1-Distill-Qwen-32B-IQ3_M.gguf, 14.81GB
    • DeepSeek-R1-Distill-Qwen-7B-f16.gguf, 15.24GB

    以这两个模型为例,它们大小相近,但大多数情况下,2优于1。

    • 1具有320亿参数,IQ3_XS代表了一种平衡性能与效率的量化策略,量化过程可能会导致一定的精度损失,但是它仍然保留了大量的原始信息。
    • 2只有基于70亿参数,没有经过裁剪或量化处理。理论上可以提供最接近于原始训练模型的性能,特别是在准确性和细节处理方面。但是由于参数量较少,它可能在理解和生成复杂文本时不如32B版本。

    这其实是个复杂、需要综合考虑的问题。一般而言,越接近原始大模型的版本最好,但是普通用户受限于显卡算力,只能选择修剪后的版本。

    16GB显存的显卡,一般选择7b,14b参数,跑32b会有压力。因此如果你是最新的5090显卡,就可以更大参数的。

     

     

     

  • Django Rest Framework

    Web应用开发模式

    前后端分离

     

    通俗地讲, 其实判定很简单:如果前端和后端这两个角色, 只通过API 文档就能进行数据交流,就说明他们的逻辑是分离的。我们可以称之为 “前后端代码分离”。

    如果除了 API 文档之外还需要各种其他的数据交流方式,比如后端把数据藏在一个 div 的属性里,那么就不是前后端分离的。像各种框架里的模板引擎和渲染功能。

    至于所谓的 “最佳实践”并不存在, 但通用的, 类似的方案如下:

    • 前端服务器
      • 将前端的所有代码使用javascript+node.js的方式部署在node.js服务器上,作为前端稳定的服务,提供页面使用。
    • 后端服务器
      • 后端的服务器写各种业务逻辑代码, 如业务逻辑, 鉴权逻辑.
    • 前后端交互
      • 后端服务为前端提供一个统一的访问入口(一般是单独部署一个服务),使用restful等风格提供http服务供前端调用,实现前后端数据传输(通常以JSON来交换数据)。

     

     

    这样一来, 现在 前端服务器不再处理任何业务,它接收到请求后,经过转换,发送给各个相关后端服务器,将各个后端服务器返回的,处理过的业务数据填入 HTML 模板,最后发送给浏览器。前端服务器和后端服务器间,可以选用任何你觉得合适的通信手段,可以是 REST,可以是 RPC,选用什么样的通信手段,这是另一个议题了。

    前后端分离, 是浏览器和后端服务分离吗?

    不是,前后端分离里的前端不是浏览器,指的是生成 HTML 的那个服务,它可以是一个仅仅生成 HTML 的 Web 服务器,也可以是在浏览器中通过 JS 动态生成 HTML 的 单页应用。

     

    实践中,有实力的团队往往在实现前后端分离里时,前端选用 node 服务器,后端选用 C#、Java 等。

    API接口规范

     

    Restful

    • 基于http协议
    • 传统的路由设计
      • 增: localhost.com/user/add
      • 删: localhost.com/user/delete/1
      • 改: localhost.com/user/edit/1
      • 查: localhost.com/user/query/1
    • Restful的设计规范, 基于资源, 资源操作由请求方式决定
    • 比如 api.localhost/users/
      • 增: post
      • 删: delete
        • 删除单个资源: api.localhost.com/users/1
        • 删除所有资源: localhost/users/
      • 改:
        • put: 更新单个资源
        • patch: 更新局部/部分资源
      • 查: get
        • 查单个资源: api.localhost.com/users/1
        • 查所有资源: api.localhost.com/users/
      • 过滤,通过在url上传参的形式传递搜索条件
        • https://api.localhost.com/v1/zoos?limit=10:指定返回记录的数
        • https://api.localhost.com/v1/zoos?offset=10:指定返回记录的开始位置
        • https://api.localhost.com/v1/zoos?page=2&per_page=100:指定第几页,以及每页的记录数
        • https://api.localhost.com/v1/zoos?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序
        • https://api.localhost.com/v1/zoos?animal_type_id=1:指定筛选条件

    RPC

    • 性能更高
    • 基于TCP/IP协议(传输层)

    序列化

    什么是序列化?

    序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。

    • 序列化:把对象转化为可传输的字节序列过程称为序列化。
    • 反序列化:把字节序列还原为对象的过程称为反序列化。

     

    为什么要序列化?

    如果光看定义我想你很难一下子理解序列化的意义,那么我们可以从另一个角度来推导出什么是序列化, 那么究竟序列化的目的是什么?

    其实序列化最终的目的是为了对象可以跨平台存储,和进行网络传输。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组

     

    在大多数开发者, 所谓序列化是指数据在 对象和json之前的相互转换.

     

     

    Django Rest Framework

    DRF的安装

    创建新环境

    conda create –prefix=C:/ProgramData/Anaconda3/envs/ENV_DRF_01 python=3.9

    – 激活环境

    • conda activate C:\ProgramData\Anaconda3\envs\ENV_DRF_01

    安装django

    • conda install django=3.2.*

    创建django项目

    • django-admin startproject myshop

    安装drf, drf是基于django的一个包

    • pip install djangorestframework
    • pip install pymysql

    创建django项目内的APP

    • python manage.py startapp LearnDRF

    进行django配置

    • 数据库
    • INSTALLED_APPS

     

    DRF快速体验

    创建并配置数据库

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'learndrf',  # 数据库名字
            'USER': 'root',
            'PASSWORD': 'zxcvbnm',
            'HOST': '127.0.0.1',  # 那台机器安装了MySQL
            'PORT': 3306,
        }
    }

    模型类

    class Student(models.Model):
        # 模型字段
        name = models.CharField(max_length=100,verbose_name="姓名")
        sex = models.BooleanField(default=1,verbose_name="性别")
        age = models.IntegerField(verbose_name="年龄")
        class_null = models.CharField(max_length=5,verbose_name="班级编号")
        description = models.TextField(max_length=1000,verbose_name="个性签名")
    
        class Meta:
            db_table="tb_student"
            verbose_name = "学生"
            verbose_name_plural = verbose_name

    执行迁移

    python manage.py makemigrations
    python manage.py migrate

    可以添加一些数据, 方便测试.

     

    FBV 和CBV

    FBV

    fbv是指function based view, 望文生义, 义如其名.

    fbv 模式的路由

    path(‘login_fbv/’, login)

    • login是view.py的login函数.
    CBV

    path(‘student/’, StudentView.as_view())

    • StudentView是view.py的类
    • as_view()是drf包里面视图方法

     

    DRF的使用

    DRF的优势?

    谈到DRF的使用, 很容易会产生一堆问题? DRF有什么作用? 通常用于哪些场景? 它的优势是什么? 为什么用DRF, 而不是django?

    一句话回答: DRF可以更快, 更高效的完成REST API开发.

    DRF如何实现这些优势的?

    DRF包含一系列高度抽象和功能丰富的组件

    序列化组件

    1.作用: 把python中的对象,转成json格式字符串

    使用步骤1: 写一个类继承Serializer或者ModelSerializer
    举例(类中选取字段进行序列化):
    class BookSerializer(serializers.Serializer):
    id = serializers.CharField()
    title = serializers.CharField()
    price = serializers.CharField()
    举例(把类中字段全部进行序列化):

    class TestSer(serializers.ModelSerializer):
    class Meta:
    model = models.Takes
    fields = ‘__all__’
    总结:
    变量名和source指定的值不能一样

    source=’publish.name’还支持继续 .

    source 还支持方法(没用)

    支持写方法,如下
    方法一定传一个参数,是当前book对象

    publish_dic=serializers.SerializerMethodField()
    def get_publish_dic(self,obj):
    return
    结果:{‘id’:obj.publish.pk,’name’:obj.publish.name}

    认证组件

    1.认证组件的好处:
    比如要访问books/路径,必须登录之后才能访问。一旦登录成功,在响应结果中写一个随机字符串

    举例: {status:100
    msg:登录成功
    token:sdafsdfasd
    }

    2.使用步骤1:写一个类,继承BaseAuthentication

    3.使用步骤2:def authenticate(self,request) ,记住传request对象

    4.在视图类中使用:(不要加括号):

    1.局部使用:authentication_classes=[AuthLogin](写在views中)

    2.全局使用:-REST_FRAMEWORK=
    {“DEFAULT_AUTHENTICATION_CLASSES”:[“app01.auth.AuthLogin”,](在setting中配置)

    3.全局使用的局部禁用:authentication_classes = [](写在views中)

     

    权限组件

    1.使用步骤1:写一个类,继承BasePermission

    2.使用步骤2:def has_permission(self, request, view): ,记住传
    request对象和view

    3.在视图类中使用:(不要加括号):

    1.局部使用:permission_classes=[MyPer](写在views中)

    2.全局使用:-REST_FRAMEWORK={“DEFAULT_PERMISSION_CLASSES”:[‘app01.auth.MyPer’]}(在setting中配置)

    3.全局使用的局部禁用:permission_classes = [](写在views中)

     

    节流组件

    1.使用步骤1:写一个类,继承SimpleRateThrottle

    2.使用步骤2:def get_cache_key(self, request, view):,记住传request对象和view

    3.使用步骤3
    :’DEFAULT_THROTTLE_RATES’: {
    这个key跟scope对应,value值3/m 3/h 4/d
    ‘xx’: ‘3/m’
    }(在setting中配置)

    节流作用:节流(Throttling)类似于权限,因为它决定了是否应该对请求进行授权。节流表示一个临时状态,并用于控制客户端对API的请求率。

    视图组件

    使用:
    1.局部使用:throttle_classes = [VisitThrottle](写在views中)
    2.全局使用:REST_FRAMEWORK={“DEFAULT_THROTTLE_CLASSES”:[“app01.auth.VisitThrottle”]}(在setting中配置)
    3.全局使用的局部禁用:throttle_classes = [](写在views中)

    视图作用: 数据库查询, 2. 构建序列化器, 进行序列化操作, 返回数据

     

    解析器

    1.局部使用:parser_classes=[JSONParser,](写在views中)
    2.全局使用:’DEFAULT_PARSER_CLASSES’:[‘rest_framework.parsers.JSONParser’](在setting中配置)

    解析器作用:解析器的作用就是服务端接收客户端传过来的数据,把数据解析成自己可以处理的数据。本质就是对请求体中的数据进行解析。

     

    渲染器

    DRF提供的渲染器有很多,默认是
    ‘DEFAULT_RENDERER_CLASSES’: (
    ‘rest_framework.renderers.JSONRenderer’,
    ‘rest_framework.renderers.BrowsableAPIRenderer’,
    ),

    渲染器的作用:渲染器同解析器相反,它定义了框架按照content_type来返回不同的响应。

     

    分页组件

    1.路由

    urlpatterns = [
    re_path(‘(?P<version>[v1|v2]+)/page1/’, Pager1View.as_view(),) #分页1
    ]
    序列化

    from rest_framework import serializers
    from api import models
    class PagerSerialiser(serializers.ModelSerializer):
    class Meta:
    model = models.Role
    fields = “__all__”
    视图

    from api.utils.serializsers.pager import PagerSerialiser
    from rest_framework.response import Response
    from rest_framework.pagination import PageNumberPagination
    class Pager1View(APIView):
    def get(self,request,*args,**kwargs):
    #获取所有数据
    roles = models.Role.objects.all()
    #创建分页对象
    pg = PageNumberPagination()
    #获取分页的数据
    page_roles = pg.paginate_queryset(queryset=roles,request=request,view=self)
    #对数据进行序列化
    ser = PagerSerialiser(instance=page_roles,many=True)
    return Response(ser.data)
    settings配置

    REST_FRAMEWORK = {
    #分页
    “PAGE_SIZE”:2 #每页显示多少个
    }

    版本组件

    自定义版本处理

    class ParamVersion(object):
    def determine_version(self, request, *args, **kwargs):
    # version = request._request.GET.get(“version”)
    # version = request.query_params.get(‘version’)
    version = request.GET.get(“version”)
    return version

    class UsersView(APIView):
    versioning_class = ParamVersion

    def get(self, request, *args, **kwargs):
    version = request.version
    return JsonResponse({‘version’: version})

    路由组件

    使用方法

    # format参数是给渲染器用的
    url(r’^(?P<version>[v1|v2]+)/myview\.(?P<format>\w+)$’, MyView.as_view({“get”: “list”, “post”: “create”})),
    创建router对象,并注册视图集。

    from rest_framework import routers
    router = routers.DefaultRouter()
    router.register(r’myview’, MyView)

    最原始的 Serializer + APIView

    2. ModelSerializer + APIView

     

    序列化和视图类的使用

    前置了解

    视图类

    • APIView
      • 继承APIView父类(Django中View的子类)
      • 具备View的所有特性
      • 提供了认证、授权、限流等功能
    • GenericAPIView
      • GenericAPIView为APIView的子类,它拓展了过滤、查询、分页的功能

    序列化器

    • Serializer
    • ModelSerializer

    这里将会通过以下三种组合来熟悉DRF的写法.

    DRF的 Serializer + DRF的 APIView

    DRF的 ModelSerializer + DRF的 APIView

    DRF的 ModelSerializer + DRF的 GenericAPIView

     

    Serializer + APIView

    path('student/', StudentView.as_view()), #drf的as_view方法
    re_path('student/(\d+)/', StudentDetailView.as_view()), #drf的as_view方法

     

    class StudentView(APIView):
    
    
        def get(self, request):
            print(request.GET)
            print(request.query_params)
            students = Student.objects.all()
            print(type(students))
            # students 是一个queryset 数据集. 所谓的序列化就是对该数据再进行封装, 这里其实是for循环, 将里面的数据都拿到,
    
            serializer = StudentsSerializer(instance=students, many=True)
            return Response(serializer.data)
    
    
    
        def post(self, request):
            print(request)
            print(request.data)
            print(type(request.data))
    
            serializer = StudentsSerializer(data=request.data)
            if serializer.is_valid():
                stu = Student.objects.create(**serializer.validated_data)
                ser = StudentsSerializer(instance=stu, many=False)
                return Response(ser.data)
    
            else:
                 return Response(serializer.errors)
    
    
    
    class StudentDetailView(APIView):
    
        print('StudentDetailView')
        def get(self, request, id):
            students = Student.objects.get(pk=id)
            serializer = StudentsSerializer(instance=students, many=False)
            return Response(serializer.data)
    
    
        def delete(self, request, id):
            Student.objects.get(pk=id).delete()
    
            return Response("已删除")
    
        def put(self, request, id):
            print(request)
            print(request.data)
            print(type(request.data))
    
            serializer = StudentsSerializer(data=request.data)
            if serializer.is_valid():
                print(serializer.validated_data)
    
                # 成功修改数据的条数, 为1
                # n = Student.objects.filter(pk=id).update(**serializer.validated_data)
                print(n)
    
                #要修改的数据对象
                stu = Student.objects.get(id=id)
                print(id)
                ser = StudentsSerializer(instance=stu, many=False)
                print(ser.data)
                return Response(ser.data)
    
            else:
                return Response(serializer.errors)

    get 查询所有数据

    • http://127.0.0.1:8000/student/

    post 添加数据

    • http://127.0.0.1:8000/student/

    delete 删除单条数据

    • http://127.0.0.1:8000/student/1/

    put 修改单条数据

    • http://127.0.0.1:8000/student/1/

    get 查询单条数据

    • http://127.0.0.1:8000/student/1/

    ModelSerializer + APIView

    path('publish/', PublishView.as_view()),
    re_path('publish/(\d+)/', PublishDetailView.as_view()),

     

    class PublishSerializer(serializers.ModelSerializer):
        class Meta:
            model = Publish
            # fiedls = "__all__"
            fields = ["name", "email"]
    
    
    class PublishView(APIView):
        def get(self, reqeust):
            publishes = Publish.objects.all()
            ps = PublishSerializer(instance=publishes, many=True)
            return Response(ps.data)
    
        def post(self, request):
            print(request.data, type(request.data))
    
            serializer = PublishSerializer(data=request.data)
            if serializer.is_valid():
                serializer.save()  # create方法 将符合条件的数据插入到数据库
                return Response(serializer.data)
            else:
                return Response(serializer.errors)
    
    
    class PublishDetailView(APIView):
    
        def get(self, reqeust, id):
            publish = Publish.objects.get(pk=id)
            ps = PublishSerializer(instance=publish, many=False)
            return Response(ps.data)
    
        def delete(self, request, id):
            Publish.objects.get(pk=id).delete()
            return Response()
    
        def put(self, request, id):
            # post请求
            instance = Publish.objects.get(pk=id)
            serializer = PublishSerializer(data=request.data, instance=instance)
    
            if serializer.is_valid():
                serializer.save()  # instance的 update
                return Response(serializer.data)
    
            else:
                return Response(serializer.errors)

     

     

    ModelSerializer + GenericAPIView

    path('author/', AuthorView.as_view()),
    re_path('author/(?P<pk>\d+)/', AuthorDetailView.as_view()),

     

    from rest_framework.generics import GenericAPIView
    
    
    class AuthorSerializer(serializers.ModelSerializer):
        class Meta:
            model = Author
            fields = ["name", "age"]
    
    
    class AuthorView(GenericAPIView):
        queryset = Author.objects.all()
        serializer_class = AuthorSerializer
    
        def get(self, reqeust):
            # serializer = self.serializer_class(instance=self.queryset,many=True)
            serializer = self.get_serializer(instance=self.get_queryset(), many=True)
    
            return Response(serializer.data)
    
        def post(self, request):
    
            serializer = self.get_serializer(data=request.data)
    
            if serializer.is_valid():
                serializer.save()  # create方法 将符合条件的数据插入到数据库
                return Response(serializer.data)
            else:
    
                return Response(serializer.errors)
    
    
    class AuthorDetailView(GenericAPIView):
        queryset = Author.objects.all()
        serializer_class = AuthorSerializer
    
        def get(self, request, pk):  # url的pk名
            serializer = self.get_serializer(instance=self.get_object(), many=False)
            return Response(serializer.data)
    
        def delete(self, request, pk):
            self.get_object().delete()
            return Response()
    
        def put(self, request, id):
            # post请求
            instance = self.get_object()
            serializer = self.get_serializer(data=request.data, instance=instance)
    
            if serializer.is_valid():
                serializer.save()  # instance的 update
                return Response(serializer.data)
    
            else:
                return Response(serializer.errors)

     

    ModelSerializer + Mixin

    from rest_framework.mixins import ListModelMixin, CreateModelMixin, RetrieveModelMixin, DestroyModelMixin, \
        UpdateModelMixin
    
    from rest_framework.generics import GenericAPIView
    
    
    class AuthorSerializer(serializers.ModelSerializer):
        class Meta:
            model = Author
            fields = ["name", "age"]
    
    
    class AuthorView(GenericAPIView, ListModelMixin, CreateModelMixin):
        queryset = Author.objects.all()
        serializer_class = AuthorSerializer
    
        def get(self, reqeust):
            return self.list(reqeust)
    
        def post(self, request):
            return self.create(request)
    
    
    class AuthorDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
        queryset = Author.objects.all()
        serializer_class = AuthorSerializer
    
        def get(self, request, pk):  # url的pk名
            return self.retrieve(request)
    
        def delete(self, request, pk):
            return self.destroy(request)
    
        def put(self, request, pk):
            return self.update(request)

    基于Mixin的进一步封装

    ListModelMixin, CreateModelMixin, RetrieveModelMixin, DestroyModelMixin, \ UpdateModelMixin

     

    from rest_framework.generics import ListCreateAPIView,RetrieveUpdateDestroyAPIView
    
    
    class AuthorSerializer(serializers.ModelSerializer):
        class Meta:
            model = Author
            fields = ["id","name", "age"]
    
    
    class AuthorView(ListCreateAPIView):
        queryset = Author.objects.all()
        serializer_class = AuthorSerializer
    
    
    class AuthorDetailView(RetrieveUpdateDestroyAPIView):
        queryset = Author.objects.all()
        serializer_class = AuthorSerializer
    

     

  • Python 进程/线程/协程/异步编程

    前置基础

    什么是GIL?

    进程

     

    线程

     

    协程

    非协程实例

    首先来看非协程的代码实例

    t1 = time.time()
    def func1():
        print("当前执行function 1")
        time.sleep(1)  # 当程序出现了同步操作的时候. 异步就中断了
        print("当前执行function 1")
    
    
    def func2():
        print("当前执行function 2")
        time.sleep(2)
        print("当前执行function 2")
    
    def func3():
        print("当前执行function 3")
        time.sleep(3)
        print("当前执行function 3")
    
    if __name__ == '__main__':
        f1 = func1()
        f2 = func2()
        f3 = func3()
        tasks = [
            f1, f2, f3
        ]
        # 一次性启动多个任务(协程)
        # asyncio.run(asyncio.wait(tasks))
        t2 = time.time()
        print(t2 - t1)
    

     

    结果

     

    当前是function 1
    当前是function 1
    当前是function 2
    当前是function 2
    当前是function 3
    当前是function 3
    6.002589225769043

     

    协程实例

    async def func1():
        print("当前执行function_1")
        await asyncio.sleep(1)
        print("当前执行function_1")
    
    
    async def func2():
        print("当前执行function_2")
        await asyncio.sleep(2)
        print("当前执行function_2")
    
    
    async def func3():
        print("当前执行function_3")
        await asyncio.sleep(3)
        print("当前执行function_3")
    
    
    async def main():
        # 第一种写法
        # f1 = func1()
        # await f1  # 一般await挂起操作放在协程对象前面
        # 第二种写法(推荐)
        tasks = [
            asyncio.create_task(func1()),  # py3.8以后加上asyncio.create_task()
            asyncio.create_task(func2()),
            asyncio.create_task(func3())
        ]
        await asyncio.wait(tasks)
    
    
    if __name__ == '__main__':
        t1 = time.time()
        # 一次性启动多个任务(协程)
        asyncio.run(main())
        t2 = time.time()
        print(t2 - t1)
    

     

    结果

    当前执行function_1
    当前执行function_2
    当前执行function_3
    当前执行function_1
    当前执行function_2
    当前执行function_3
    3.0129427909851074

     

    对比与发现

    1. 一共三个任务
    2. 非协程写法里
      1. 如果使用非协程写法,  time.sleep(1)  ,将会导致IO阻塞。 因此程序会在设定的等待时间结束后, 才会往下执行。
      2. 耗时 6.002589225769043
      3. 打印的顺序自上而下
    3. 协程写法里
      1. 耗时3.0129427909851074
      2. 可以反推: 总耗时 = await 挂起时间最长的那个任务所花的时间(function 3) + 切换协程上下文的所需开销的时长(0.01)
    4. 异步协程的语法结构

     

     

    异步协程最简实例

    import asyncio
    import aiohttp
    import aiofiles
    
    # 构造无数个urls
    urls = [
        "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png",
        "https://inews.gtimg.com/newsapp_bt/0/12171811596_909/0.png",
        "http://www.soso.com/soso/images/logo_index_sosox2.png"
    ]
    
    async def aiodownload(url):
        # 异步下载功能的函数, 以下三个步骤都是IO操作
            # 1.发送请求 aiohttp
            # 2. 得到图片内容 async
            # 3. 保存到文件 aiofiles
    
        filename = url.rsplit("/", 1)[1]
    
        async with aiohttp.ClientSession() as session:  # 类似同步的 requests
            async with session.get(url) as resp:        # 类似同步的 resp = requests.get()
                # 请求回来. 异步写入文件
                async with aiofiles.open(filename, mode='wb') as f:
                    # await f.write(await resp.content.read())  # 读取内容是异步的. 需要await挂起, resp.text()
                    # Response对象的read()和text()方法会将响应一次性全部读入内存,这会导致内存爆满,导致卡顿,影响效率。 因此采取字节流的形式,每次读取4096个字节并写入文件。
                    while True:
                        pic_stream = await resp.content.read(4096)
                        if not pic_stream:
                            break
                        await f.write(pic_stream)
        print(f'{filename} 已下载')
    
    async def main():
        # tasks = []
        # for url in urls:
        #     # tasks.append(aiodownload(url))
        #     d = asyncio.create_task(aiodownload(url))
        #     tasks.append(d)
    
        # 利用推导式的简写方式
        tasks = [asyncio.create_task(aiodownload(url)) for url in urls]
        await asyncio.wait(tasks)
    
    if __name__ == '__main__':
        asyncio.run(main())
    
    
    

     

    反思

    在爬虫的三个基本操作都是涉及到IO

    •  1.发送请求 aiohttp
    • 2. 得到图片内容 async
    • 3. 保存到文件 aiofiles

    但2和1、3有显著区别。

    • 磁盘io与网络io不同,磁盘顺序读写单个文件最快,并发读写会涉及到多个文件的切换问题,反而花了更多的时间,所以异步编程使用aiofiles要谨慎。
    • 举一个有差异但基本原理类似的例子。在同一台电脑上, 将C盘里的文件复制到D盘去, 这里有个很明显的经验是如果同时只有一个这样的复制操作, 那么“较为高效”。如果“将C盘里的文件复制到D盘去”的同时,  开启多个文件转移复制粘贴进程, 速度极为缓慢甚至进程管理亲卡死。
    • 解决的办法应该有很多种。
      • 可以考虑把文件读写任务抽离出来,放到队列里面,然后用专门的线程或进程去按顺序去处理。
      • 因此采取字节流的形式,每次读取4096个字节并写入文件
  • Python 符号用法总结

    下划线

    单下划线 _

    函数名称前单下划线

    def _add():
    	...
        return

    是一种私有函数的命名约定,即提示程序员该函数只能在类或者该文件内部使用,但实际上也可以在外部使用。

     

    _xxx 单下划线

    • protected 类型变量
    • 只允许其本身与子类进行访问
    • 也不能使用from xxx import * 的方式导入

    xxx_ 单下划线

    • 避免名称与关键字冲突

     

    星号*

  • Python环境问题记录

    Django

    • 安装django
      • pip install django
    • 创建django项目
      • django-admin startproject projectname
    • 启动django项目
      • python manage.py startapp app_name
    • 注册app
      • INSTALLED_APPS = {
        'app01.apps.App01Config'
        }
    • 配置静态文件和模板路径
    • 配置数据库
      • 创建数据库
      • 安装数据库连接模块
        • pip install mysqlclient
      • 配置数据库连接
        • DATABASES = {
              'default': {
                  'ENGINE': 'django.db.backends.mysql',
                  'NAME': 'django_test_2',  # 数据库名字
                  'USER': 'root',
                  'PASSWORD': 'zxcvbnm',
                  'HOST': '127.0.0.1',  # 哪台机器安装了MySQL
                  'PORT': 3306,
              }
          }
      • 创建表
        • app-models.py 写类
        • python manage.py makemigrations
        • python manage.py migrate

     

    本地conda虚拟环境中部署Django项目

    利用conda创建虚拟环境

    conda create -n env_name python=version_number

    conda remove -n env_name (删除指定环境)

     

    激活新创建的环境

    conda init

    conda activate env_name

    (ENV_20221122) PS D:\Practice\Python\Django_20221122>

    当前已激活的环境名称会显示在最左侧的小括号内.

     

    在新创建的环境中安装所需包

    conda env list (查看已安装哪些包)

    conda安装

    conda install package_name

    (base) PS D:\Practice\Python\Django_20221122> conda env list
    # conda environments:
    #
    base * C:\ProgramData\Anaconda3
    DjangoProject C:\ProgramData\Anaconda3\envs\DjangoProject
    ENV_20221122 C:\ProgramData\Anaconda3\envs\ENV_20221122
    Python36 C:\ProgramData\Anaconda3\envs\Python36
    test C:\ProgramData\Anaconda3\envs\test
    

     

    pip安装

    尽管在anaconda下我们可以很方便的使用conda install来安装我们需要的依赖,但是anaconda本身只提供部分包,远没有pip提供的包多,有时conda无法安装我们需要的包,我们需要用pip将其装到conda环境里。

    首先,我们需要判断目前我们用的pip指令,会把包装到哪里,通常情况下,pip不像conda一样,他不知道环境,我们首先要确保我们用的是当前环境的pip,这样pip install时,包才会创建到本环境中,不然包会创建到base环境,供各个不同的其他conda环境共享,此时可能会产生版本冲突问题(不同环境中可能对同一个包的版本要求不同)

    用 which -a pip 命令查看我们此时用的pip为哪个环境.

    新创建的虚拟环境应该已经包含pip, 如没有, 可用conda install pip先安装

    退出当前环境

     

    本次使用完成后, 最好使用以下命令退出, 不要直接关闭cmd, 有概率会导致产生潜在问题.

    conda deactivate

    (在当前的conda虚拟环境里,只需要执行conda deactivate 命令即可,无须参数)

     

     

    linux 环境下部署Django项目

     

    当前环境的导出和导入(windows)

    pip

    记录和导出环境信息

    pip freeze > requirements_pip.txt

    生成requirements_pip.txt文件

    asgiref==3.5.2
    Django==4.1.3
    sqlparse==0.4.3
    tzdata==2022.6
    

     

    导入和安装环境配置

    pip install freeze > requirements_pip.txt

    conda

    conda list -e > requirements_conda.txt

  • Django – 前端提交数据, 后端接收并入库简例 (ModelForm)

    models.py

     

    class Boss(models.Model):
        name = models.CharField(verbose_name="姓名", max_length=64)
        age = models.IntegerField(verbose_name="年龄")
        img = models.CharField(verbose_name="头像", max_length=256)

     

    这种写法需要在view_name.py文件中去处处理 待保存文件的路径问题, 并调用create方法.

    media_file_path = os.path.join("media", image_object.name)
    print(media_file_path)
    f = open(media_file_path, mode="wb")
    for chunk in image_object.chunks():
        f.write(chunk)
        f.close()
    
    models.Boss.objects.create(
        name = form.cleaned_data['name'],
        age = form.cleaned_data['age'],
        img = media_file_path,

     

     

    class City(models.Model):
        name = models.CharField(verbose_name="名称", max_length=64)
        count = models.IntegerField(verbose_name="人口")
    
        # 此处写成"FileField", 而不是"IntegerField", 这样FileField会多出upload_to='目录名'属性, 在入库时可快速将图片保存到该目录
        logo = models.FileField(verbose_name="logo", max_length=256, upload_to='city/')

     

    view_name.py

    class UpModelForm(BootStrapModelForm):
        class Meta:
    
            model = models.City
            fields = "__all__"
    
    def upload_model_form(request):
        title = "ModelForm上传"
        if request.method == "GET":
            form = UpModelForm()
            return render(request, 'upload_form.html', {'form': form, "title": title})
    
    
        form = UpModelForm(data=request.POST, files=request.FILES)
        if form.is_valid():
            form.save()
            return HttpResponse('成功')

     

    可以看到这种写法非常简捷,  保存路径已在创建model时设置, 而保存数据只需form.save()即可.

    结果验证

    • 保存路径: logo = models.FileField(verbose_name=”logo”, max_length=256, upload_to=’city/’)
    • 如何保存: form.save()

     

     

    前置条件

    配置media目录. (实际使用中, 由于目录位置等差异, 根据实际情调整.)

    setting.py

    MEDIA_ROOT = os.path.join(BASE_DIR, "media")
    MEDIA_URL = "/media/"

     

    urls.py 路由

     

    from django.views.static import serve
    from django.conf import settings
    re_path(r'^media/(?P<path>.*)$', serve, {"document_root": settings.MEDIA_ROOT}, name='media'),

     

  • Django – 前端提交数据, 后端接收并入库简例 (Form)

    view

    class UpForm(BootStrapForm):  封装了bootstrap的Form组件的类, 让表单快速获得bootstrap的样式
        bootstrap_exclude_fields = ['img']  # 在BootStrapForm中,排除img表单的样式
        name = forms.CharField(label="姓名")
        age = forms.IntegerField(label="年龄")
        img = forms.FileField(label="头像")
    
    
    def upload_form(request):
        title = "表格上传"
    
        if request.method == "GET":
            form = UpForm()
            return render(request, 'upload_form.html', {'form':form, "title":title})
    
        if request.method == "POST":
            # 获取前端用户提交的数据
            form = UpForm(data=request.POST, files = request.FILES)
            if form.is_valid():
                image_object = form.cleaned_data.get("img")
              
                # 对图片数据进行特殊处理
                db_file_path = os.path.join('static', 'img', image_object.name)
                file_path = os.path.join('app01', db_file_path)
                print(file_path)
                f = open(file_path, mode="wb")
                for chunk in image_object.chunks():
                    f.write(chunk)
                    f.close()
                # 写入数据库
                models.Boss.objects.create(
                    name = form.cleaned_data['name'],
                    age = form.cleaned_data['age'],
                    img = db_file_path,
                )
    
        return render(request, 'upload_form.html', {'form': form, "title": title})
    

     

    template

    <form method="post" novalidate enctype="multipart/form-data">
        {% csrf_token %}
    
        {% for field in form %}
            <div class="form-group">
                <label>{{ field.label }}</label>
                {{ field }}
                <span style="color: red">{{ field.errors.0 }}</span>
            </div>
        {% endfor %}
    
        <button type="submit" class="btn btn-primary">提交</button>
    
    </form>

     

    router

    path('upload/form/', upload.upload_form)

     

  • Django – Form和ModelForm组件

    表单的很多字段信息, 和models.py文件里的模型是一致的,为了避免重复代码,以及提高效率, 可以使用ModelForm,将模型和表单进行绑定。

    Form

    views.py

    //创建业务类MyForm, 继承django中Form类
    class MyForm(Form):  
        // Form类会在html中渲染出原始表单
        user = forms.CharField(widget=forms.Input)
        pwd = form.CharFiled(widget=forms.Input)
        email = form.CharFiled(widget=forms.Input)
        account = form.CharFiled(widget=forms.Input)
        create_time = form.CharFiled(widget=forms.Input)
        depart = form.CharFiled(widget=forms.Input)
        gender = form.CharFiled(widget=forms.Input)
    
    
    def user_add(request):
        if request.method == "GET":
            form = MyForm()
            return render(request, 'user_add.html',{"form":form})

     

    user_add.html (Form)

    <form method="post">
        {% for field in form%}
        	{{ field }}
        {% endfor %}
        <!-- <input type="text"  placeholder="姓名" name="user" /> -->
    </form>

    或者

    <form method="post">
        {{ form.user }}
        {{ form.pwd }}
        {{ form.email }}
        <!-- <input type="text"  placeholder="姓名" name="user" /> -->
    </form>

     

     

    ModelForm

     

    models.py 

    class UserInfo(models.Model):
        """ 员工表 """
        name = models.CharField(verbose_name="姓名", max_length=16)
        password = models.CharField(verbose_name="密码", max_length=64)
        age = models.IntegerField(verbose_name="年龄")
        account = models.DecimalField(verbose_name="账户余额", max_digits=10, decimal_places=2, default=0)
        create_time = models.DateTimeField(verbose_name="入职时间")
        depart = models.ForeignKey(to="Department", to_field="id", on_delete=models.CASCADE)
        gender_choices = (
            (1, "男"),
            (2, "女"),
        )
        gender = models.SmallIntegerField(verbose_name="性别", choices=gender_choices)

     

     

    views.py 快速建立表单

    class UserModelForm(forms.ModelForm):
        name = forms.CharField(min_length=2, label="用户名")
        password = forms.CharField(label="密码")
        class Meta:
            model = UserInfo
            fields = ["name","password","age","xx"]
    
    
    def user_add(request):
        if request.method == "GET":
            form = MyForm()
            return render(request, 'user_add.html',{"form":form})

    views.py 给表单添加样式

     

    class UserModelForm(forms.ModelForm):
        name = forms.CharField(min_length=2, label="用户名")
        password = forms.CharField(label="密码")
        # create_time = forms.DateTimeField()
        class Meta:
            model = models.UserInfo
            fields = ['name', 'password', 'age', 'account', 'create_time', 'gender', 'depart']
            
            # 方法1: 为前端元素添加类属性, 使其获得样式. 
    
            # widgets = {
            #     "name":forms.TextInput(attrs={"class":"form-control"})
            # }
    
            # 方法2: 为前端元素添加类属性, 使其获得样式. 
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)  #super() 函数是用于调用父类(超类)的一个方法。
            for name, field in self.fields.items():
                field.widget.attrs = {"class":"form-control", "placeholder":field.label}
    

     

     

    user_add.html (ModelForm)

    <form method="post" novalidate>
        {% csrf_token %}
    
        {% for field in form %}
            <div class="form-group">
                <label>{{ field.label }}</label>
                {{ field }}
                <span style="color: red">{{ field.errors.0 }}</span>
            </div>
        {% endfor %}
    
        <button type="submit" class="btn btn-primary">提交</button>
    
    </form>