You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何保存关联多张图片的Django模型?解决后台仅显示单图问题

问题根因
  • Image实例初始化位置错误:你在公寓遍历循环的外层仅创建了一个Image对象,遍历同个公寓的多张图片时反复修改同一个实例,最终数据库只会保留最后一张图片的记录
  • 未执行Image模型的入库操作:仅调用了ImageField的save()方法处理文件存储,没有调用Image实例自身的save()方法把数据写入数据库
  • save_data调用逻辑错误:在爬取每个公寓的循环内部就调用save_data,会重复处理列表中已经存在的公寓数据,产生冗余记录
  • 下载的临时图片文件没有清理,会残留多余文件在项目根目录
代码修正方案

1. 调整scraper中save_data的调用位置

把save_data(apartments)从for link循环内移到循环结束后,爬完所有公寓再统一执行保存:

@receiver(post_save, sender=Url)
def saved_url(instance, created, **kwargs):
    apartments = []
    if created:
        url = instance.url
        soup = make_request(url)
        for link in soup.find_all('div', attrs={'data-name': 'LinkArea'}):
            url = link.find('a').get('href')
            driver.get(url)
            time.sleep(1)
            try:
                rooms = driver.find_element(By.XPATH, '//div[1][@data-name="OfferTitle"]/h1').text
            except:
                rooms = None
            try:
                price = driver.find_element(By.XPATH, '//div[1]/div/span/span[1][@itemprop="price"]').text
            except:
                price = None
            try:
                address = driver.find_element(By.XPATH,
                                              '//section/div/div[1]/div[2]/span[@itemprop="name"]').get_attribute(
                    'content')
            except:
                address = None
            try:
                desc = driver.find_element(By.XPATH, '//div/span/p[@itemprop="description"]').text
            except:
                desc = None
            try:
                floor = driver.find_element(By.XPATH, '//div/div[4]/div[1][@data-testid="object-summary-description-value"]').text
            except:
                floor = None
            try:
                photos = [img.get_attribute('src') for img in driver.find_elements(By.CLASS_NAME, 'fotorama__img')]
            except:
                photos = None
            
            apartments.append(
                {
                    'rooms': rooms,
                    'price': price,
                    'address': address,
                    'desc': desc,
                    'floor': floor,
                    'photos': photos,
                }
            )
        # 移到循环外,爬完所有公寓再保存
        save_data(apartments)

2. 重写save_data存储逻辑

import os
def save_data(apartments_list):
    for ap in apartments_list:
        try:
            # 先创建公寓记录
            apartment = Apartment.objects.create(
                rooms=ap['rooms'],
                price=ap['price'],
                address=ap['address'],
                desc=ap['desc'],
                floor=ap['floor'],
            )
            # 每张图片都新建一个Image实例
            for image in ap['photos']:
                pic_name = image.split('/')[-1]
                # 下载图片到临时路径
                pic_path = urllib.request.urlretrieve(image, pic_name)[0]
                # 新建Image对象
                im = Image()
                im.apartment = apartment
                # 保存图片字段
                im.img.save(pic_name, File(open(pic_path, 'rb')))
                # 把Image实例写入数据库
                im.save()
                # 清理临时下载的图片文件
                os.remove(pic_path)
        except Exception as e:
            print(e)
            break
额外校验项

确保你在admin.py中已经配置了Image的内联展示,才能在Apartment的详情页看到所有关联图片:

from django.contrib import admin
from .models import Apartment, Image, Url

class ImageInline(admin.TabularInline):
    model = Image
    extra = 0

@admin.register(Apartment)
class ApartmentAdmin(admin.ModelAdmin):
    inlines = [ImageInline]

@admin.register(Url)
class UrlAdmin(admin.ModelAdmin):
    pass

内容的提问来源于stack exchange,提问作者Evgeny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 00:06:10