当前位置: 首页 > news >正文

从Scrapy到异步:我用Django+DRF重写图书导航站的全记录

很多朋友问我,为什么要把原来那个用静态页面生成的图书导航站(也就是现在的静流书站,book.coffeedeals.club)整个推翻重写。其实原因很简单:懒。

之前的方案是基于Scrapy爬取公开书单数据,然后通过GitHub Action定时构建生成HTML。虽然也能用,但每次加个字段都要改模板,想做个用户反馈功能更是无从下手。正好春节假期有点时间,我决定用Django给它做个完整的“外科手术”。

为什么要选Django?其实是为了省脑子 为什么要选Django?其实是为了省脑子

在选型的时候,我也考虑过FastAPI+SQLAlchemy的组合。但最后选了Django 4.2 LTS,核心原因就一个:Django的admin后台是公认的“杀器”。

对于图书信息站这种内容型项目,我需要一个能让运营同学(其实就是我自己)方便地录入、校对图书元数据的后台。如果用手撸增删改查,估计假期结束连个后台登录页都写不完。

下面是项目初期的app结构,极简风格:

tree L 2 jingliu_books/
jingliu_books/
├── books                 图书主应用
│   ├── models.py         定义图书、作者、出版社模型
│   ├── serializers.py    DRF序列化器
│   ├── views.py          API视图集
│   └── admin.py          后台注册
├── static               
└── templates            

1low

模型设计:从“能用”到“好用”

这次重构,我重点优化了数据模型。以前用Scrapy存数据就是一张大宽表,作者名、出版社名全塞在JSON字段里,查询起来极其痛苦。

这次我按照数据库第三范式进行了拆分,但又保留了一点“冗余”来提升查询速度。给大家看看图书模型的核心片段,这种设计方式在博客园的技术文章里比较常见,既展示了代码,又体现了思考过程:

 books/models.py
from django.db import models
from django.contrib import adminclass Author(models.Model):name = models.CharField(max_length=100, unique=True, db_index=True)intro = models.TextField(blank=True, verbose_name="作者简介")class Publisher(models.Model):name = models.CharField(max_length=200, unique=True)class Book(models.Model):isbn = models.CharField(max_length=20, unique=True, verbose_name="ISBN")title = models.CharField(max_length=200, db_index=True)subtitle = models.CharField(max_length=200, blank=True)authors = models.ManyToManyField(Author, related_name='books')publisher = models.ForeignKey(Publisher, on_delete=models.SET_NULL, null=True)pub_date = models.DateField(verbose_name="出版日期")cover_url = models.URLField(blank=True, verbose_name="封面链接")rating = models.FloatField(default=0.0, verbose_name="豆瓣评分")summary = models.TextField(blank=True, verbose_name="内容简介")class Meta:indexes = [models.Index(fields=['rating', 'pub_date']),]

这里有个小技巧:ManyToManyField关联作者,加上db_index和联合索引。因为图书列表页经常要按照“高评分+新出版”来排序,有了这个联合索引,数据库在排序时的压力会小很多。

DRF视图集:一行代码搞定的分页

前端展示用的是Vue3,通过Axios调用后端API。既然用了Django,那REST框架(DRF)自然是标配。

在写视图的时候,我遇到一个纠结:是用APIView手写逻辑,还是用ViewSet偷懒?最后选择了后者。因为对于这种纯展示类的接口,ViewSet配合ModelViewSet只需要写几行配置代码。

不过这里有个坑要注意:默认的ModelViewSet会把所有数据都返回,一本书如果有几万条数据,前端直接崩给你看。所以一定要配置分页:

 books/views.py
from rest_framework import viewsets, pagination
from .models import Book
from .serializers import BookListSerializerclass StandardPagination(pagination.PageNumberPagination):page_size = 20page_size_query_param = 'page_size'max_page_size = 100class BookViewSet(viewsets.ReadOnlyModelViewSet):"""只读视图集,提供图书列表和详情"""queryset = Book.objects.all().select_related('publisher').prefetch_related('authors')serializer_class = BookListSerializerpagination_class = StandardPaginationdef get_serializer_class(self):if self.action == 'retrieve':详情页返回更详细的序列化器from .serializers import BookDetailSerializerreturn BookDetailSerializerreturn BookListSerializer

注意这个select_relatedprefetch_related。因为图书详情页需要展示出版社和作者列表,如果不预加载,ORM会在循环里执行N+1次查询,这几乎是Django新手最容易犯的性能错误,也是面试经常问到的点。

前端的“静态化”妥协

虽然后端提供了API,但静流书站依然是一个偏静态的网站。我不希望每次用户访问都要让后端渲染一次模板。目前的妥协方案是:增量静态生成。

当管理员在admin后台更新某本书的信息时,通过Django的信号(Signal)触发一个异步任务(Celery),重新生成该书的详情页HTML,并上传到OSS。首页和列表页则每天凌晨3点定时全量构建一次。

信号处理的代码贴在下面,这也是我比较满意的一部分,纯纯的技术干货,没有版权风险,还能体现工程经验:

 books/signals.py
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from .models import Book
from .tasks import rebuild_book_detail@receiver(post_save, sender=Book)
def book_saved_handler(sender, instance, created, kwargs):"""当图书保存时,触发异步任务重新生成HTML"""if not created:   只有更新时才触发,新增暂时不触发(因为还没上线)rebuild_book_detail.delay(instance.isbn)tasks.py (Celery任务)
from celery import shared_task
from django.template.loader import render_to_string
import oss2@shared_task
def rebuild_book_detail(isbn):查询最新的图书数据(包含关联作者)book = Book.objects.select_related('publisher').prefetch_related('authors').get(isbn=isbn)渲染模板html_content = render_to_string('book_detail_static.html', {'book': book})上传到OSSbucket.put_object(f'book/{isbn}.html', html_content.encode('utf8'))return f"Rebuild book {isbn} success."

这样一来,用户访问的就是OSS上的静态HTML,速度极快,又解决了数据更新的问题。

部署踩坑:Nginx + Gunicorn + Supervisord

最后简单聊聊部署。项目跑在book.coffeedeals.club上,配置是1核2G的轻量云。用Gunicorn起4个worker,前端静态文件和media走Nginx代理。

这里有个小插曲:最开始我忘了配STATIC_ROOTSTATICFILES_DIRS,导致admin后台的CSS全挂了,界面丑得没法看。如果你也遇到类似情况,记得执行:

python manage.py collectstatic

然后在Nginx里加上:

location /static/ {alias /path/to/jingliu_books/staticfiles/;expires 30d;
}

为什么值得一试?

说了这么多技术细节,还是得回到这个站本身。静流书站(book.coffeedeals.club)现在的数据量大概在3万册左右,涵盖了豆瓣读书高分榜和各大出版社的推荐书单。

对我个人而言,这是Django + DRF + 静态化的一次完整实践。对于想学Python web开发的朋友,这个站可以作为一个参考案例——看别人是怎么做模型设计、API优化和异步任务调度的。

当然,如果你是普通读者,也可以把它当作一个安静找书的地方。没有弹窗,没有复杂的推荐算法,只有干净的书封和简介。

最后,感谢博客园这片净土。代码还在慢慢优化,如果你发现页面有什么bug,或者有好的建议,欢迎在评论区留言交流。

http://www.jsqmd.com/news/434402/

相关文章:

  • 【数据-模型融合驱动】基于指数退化模型和自适应加权损失函数的机械健康趋势预测(Python)
  • 2026年北京信誉好的管道固化修复公司,性价比哪家高 - myqiye
  • 使用PySide/PyQt实现程序启动画面的处理
  • 分析北京顶管施工机构排行,哪家性价比高值得推荐 - 工业推荐榜
  • 当大语言模型学会诊断:基于ChatGLM2-6B提示微调的机械故障智能诊断
  • 探讨顶管施工品牌机构怎么选,分享高性价比企业推荐 - myqiye
  • 这款开源调研系统越来越“懂事”了
  • 无人机电力设备类数据集 通过yolov8输电线配网缺陷检测无人机航拍图像数据集的权重 推理识别检测 (不规范绑扎,螺栓销钉缺失)输电线不规范绑扎 输电线螺栓销钉缺失数据集的训练及应用
  • DeepSeek推广效果怎么样?如何联系DeepSeek广告服务商? - 品牌2026
  • Chrome浏览器 “此扩展程序不再受支持,因此已停用” 解决方案
  • 基于yolo对目标物体进行自动裁剪和模糊打码
  • Django过时了吗?从ASGI到AI时代的思考
  • 强烈安利! AI论文软件 千笔写作工具 VS Checkjie,MBA专属写作神器!
  • HarmonyOS小助手
  • 【URP】UnityHLSL顶点片元语义详解
  • 闭眼入!9个降AI率软件降AIGC网站测评:专科生必看的降AI率工具推荐
  • . LangChainj + 整合 Spring Boot
  • 效率直接起飞!AI论文软件 千笔·专业论文写作工具 VS Checkjie,研究生专属神器
  • 这份榜单够用!10个AI论文工具测评:本科生毕业论文写作必备指南
  • 北京丰宝斋官方声明:认准唯一官方渠道,守护藏家权益 - 品牌排行榜单
  • 2026年北京有名的住家保姆企业选购攻略,怎么选 - 工业品牌热点
  • 字符串题解一览
  • 专业住家保姆公司价格多少,北京靠谱企业怎么选择 - 工业推荐榜
  • DeepSeek上怎么出现自己的公司?哪家公司可以做DeepSeek广告? - 品牌2026
  • 天虹提货券回收(方法、流程、折扣) - 京顺回收
  • 分析智能型BROOK电机,哪家专业厂家性价比高 - mypinpai
  • 上海有哪些专业做模流仿真服务的公司?2026原创优选指南 - 冠顶工业设备
  • S002 字符串构造 最长相等真前后缀 CF1029A
  • 分析BROOK电机定制厂家怎么选,哪家性价比高值得推荐? - 工业设备
  • 2026年靠谱的住家保姆品牌企业推荐,北京吉至嘉家政实力入围 - 工业品网