《Python实战进阶》小技巧 1:一篇文章讲完网站部署如何优化网站照片加载/访问提速的方法

一篇文章讲完网站部署如何优化网站照片加载/访问提速的方法

摘要

在网络速度较低的情况下,大量照片会导致网站加载缓慢。本文档详细介绍了优化家庭网站中照片加载速度的多种方法和技术。以下是主要的优化策略及其具体实现:

1. 图片压缩与优化

  • 自动压缩上传的图片:通过Python脚本使用PIL库压缩图片,调整大小、转换模式,并保存为优化的JPEG格式。
  • 批量优化现有图片:编写脚本对文件夹中的图片进行批量处理,包括创建备份、调整大小、转换模式及保存优化后的图片。

2. 生成并使用缩略图

  • 创建缩略图系统:利用PIL库生成缩略图,并保存到指定目录。在模板中使用缩略图来提高页面加载速度。

3. 延迟加载 (Lazy Loading)

  • 添加延迟加载属性:修改HTML模板中的图片标签,使用data-src属性存储实际图片路径,并通过JavaScript实现延迟加载。
  • JavaScript实现延迟加载:使用IntersectionObserver或回退方案确保图片仅在进入视口时加载。

4. 渐进式加载

  • 使用渐进式JPEG:在保存图片时启用渐进式JPEG格式,使图片逐步显示,提高用户体验。

5. WebP格式

  • 转换为WebP格式:将图片转换为WebP格式以减少文件大小。在模板中使用元素支持多种格式。

6. 分页加载

  • 实现分页加载:在视图函数中支持分页查询,并在模板中添加分页控件,方便用户浏览大量照片。

7. CDN加速静态资源

  • 使用CDN服务:推荐使用Cloudflare等免费CDN服务或配置Nginx作为静态资源服务器,并在Flask应用中配置静态URL。

8. 无限滚动

  • 实现无限滚动:通过JavaScript监听滚动事件,动态加载更多照片,替代传统的分页方式。

9. 图片预加载

  • 预加载下一张图片:在用户浏览相册时预加载下一张图片,提升流畅度。

10. 服务器端优化

  • 启用Gzip压缩:在Nginx配置中启用Gzip压缩,减小传输文件大小。
  • 使用HTTP/2:更新Nginx配置以支持HTTP/2协议,提高并发处理能力。

《Python实战进阶》小技巧 1:一篇文章讲完网站部署如何优化网站照片加载/访问提速的方法_第1张图片

1. 图片压缩与优化

自动压缩上传的图片

from PIL import Image
import io

def optimize_image(image_file, quality=85, max_width=1920):
    """压缩并优化图片"""
    img = Image.open(image_file)
    
    # 调整大小
    if img.width > max_width:
        ratio = max_width / img.width
        new_height = int(img.height * ratio)
        img = img.resize((max_width, new_height), Image.LANCZOS)
    
    # 转换为RGB模式(如果是RGBA)
    if img.mode == 'RGBA':
        img = img.convert('RGB')
    
    # 保存为优化的JPEG
    output = io.BytesIO()
    img.save(output, format='JPEG', quality=quality, optimize=True)
    output.seek(0)
    
    return output

将此函数集成到上传处理中:

@bp.route('/upload', methods=['POST'])
def upload_photo():
    if 'photo' not in request.files:
        return redirect(request.url)
    
    file = request.files['photo']
    if file.filename == '':
        return redirect(request.url)
    
    if file and allowed_file(file.filename):
        # 优化图片
        optimized_image = optimize_image(file)
        
        # 保存优化后的图片
        filename = secure_filename(file.filename)
        unique_filename = f"{uuid.uuid4().hex}_{filename}"
        file_path = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
        
        with open(file_path, 'wb') as f:
            f.write(optimized_image.getvalue())
        
        # 保存到数据库...

批量优化现有图片

创建一个脚本批量处理现有图片:

import os
from PIL import Image
import glob

def batch_optimize_images(folder_path, quality=85, max_width=1920):
    """批量优化文件夹中的所有图片"""
    image_files = glob.glob(os.path.join(folder_path, "*.jpg")) + \
                 glob.glob(os.path.join(folder_path, "*.jpeg")) + \
                 glob.glob(os.path.join(folder_path, "*.png"))
    
    for img_path in image_files:
        try:
            # 创建备份
            backup_path = img_path + ".backup"
            if not os.path.exists(backup_path):
                os.rename(img_path, backup_path)
            
            # 优化图片
            img = Image.open(backup_path)
            
            # 调整大小
            if img.width > max_width:
                ratio = max_width / img.width
                new_height = int(img.height * ratio)
                img = img.resize((max_width, new_height), Image.LANCZOS)
            
            # 转换为RGB模式(如果是RGBA)
            if img.mode == 'RGBA':
                img = img.convert('RGB')
            
            # 保存为优化的JPEG
            img.save(img_path, format='JPEG', quality=quality, optimize=True)
            
            print(f"优化完成: {img_path}")
        except Exception as e:
            print(f"处理 {img_path} 时出错: {str(e)}")
            # 恢复备份
            if os.path.exists(backup_path):
                os.rename(backup_path, img_path)

# 使用示例
batch_optimize_images("app/static/uploads/photos")

2. 生成并使用缩略图

创建缩略图系统

def create_thumbnail(image_path, thumbnail_size=(300, 300)):
    """为原图创建缩略图"""
    img = Image.open(image_path)
    img.thumbnail(thumbnail_size)
    
    # 生成缩略图文件名
    filename = os.path.basename(image_path)
    thumbnail_dir = os.path.join(os.path.dirname(os.path.dirname(image_path)), "thumbnails")
    os.makedirs(thumbnail_dir, exist_ok=True)
    thumbnail_path = os.path.join(thumbnail_dir, filename)
    
    # 保存缩略图
    img.save(thumbnail_path, optimize=True)
    return os.path.relpath(thumbnail_path, app.static_folder)

修改相册模板,使用缩略图:

{% for photo in photos %}
<div class="photo-item">
    <a href="{{ url_for('static', filename='uploads/photos/' + photo.filename) }}" 
       data-lightbox="album-{{ album.id }}">
        <img src="{{ url_for('static', filename='uploads/thumbnails/' + photo.filename) }}" 
             alt="{{ photo.description or '照片' }}" 
             class="img-thumbnail">
    a>
div>
{% endfor %}

3. 实现延迟加载 (Lazy Loading)

添加延迟加载属性

修改模板中的图片标签:

<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" 
     data-src="{{ url_for('static', filename='uploads/photos/' + photo.filename.split('/')[-1]) }}" 
     alt="{{ photo.description or '照片' }}"
     class="lazy-load">

添加JavaScript实现延迟加载

document.addEventListener("DOMContentLoaded", function() {
    let lazyImages = [].slice.call(document.querySelectorAll("img.lazy-load"));
    
    if ("IntersectionObserver" in window) {
        let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
            entries.forEach(function(entry) {
                if (entry.isIntersecting) {
                    let lazyImage = entry.target;
                    lazyImage.src = lazyImage.dataset.src;
                    lazyImage.classList.remove("lazy-load");
                    lazyImageObserver.unobserve(lazyImage);
                }
            });
        });
        
        lazyImages.forEach(function(lazyImage) {
            lazyImageObserver.observe(lazyImage);
        });
    } else {
        // 回退方案
        let active = false;
        
        const lazyLoad = function() {
            if (active === false) {
                active = true;
                
                setTimeout(function() {
                    lazyImages.forEach(function(lazyImage) {
                        if ((lazyImage.getBoundingClientRect().top <= window.innerHeight && lazyImage.getBoundingClientRect().bottom >= 0) && getComputedStyle(lazyImage).display !== "none") {
                            lazyImage.src = lazyImage.dataset.src;
                            lazyImage.classList.remove("lazy-load");
                            
                            lazyImages = lazyImages.filter(function(image) {
                                return image !== lazyImage;
                            });
                            
                            if (lazyImages.length === 0) {
                                document.removeEventListener("scroll", lazyLoad);
                                window.removeEventListener("resize", lazyLoad);
                                window.removeEventListener("orientationchange", lazyLoad);
                            }
                        }
                    });
                    
                    active = false;
                }, 200);
            }
        };
        
        document.addEventListener("scroll", lazyLoad);
        window.addEventListener("resize", lazyLoad);
        window.addEventListener("orientationchange", lazyLoad);
    }
});

4. 实现渐进式加载

使用渐进式JPEG

修改图片保存函数,使用渐进式JPEG:

def save_photo(file):
    # ... 现有代码 ...
    
    # 使用PIL处理图片
    img = Image.open(file)
    if img.mode == 'RGBA':
        img = img.convert('RGB')
    
    # 保存为渐进式JPEG
    img.save(file_path, 'JPEG', quality=85, optimize=True, progressive=True)
    
    # ... 现有代码 ...

5. 使用WebP格式

WebP格式比JPEG和PNG更小,但保持相同的质量:

def convert_to_webp(image_path, quality=85):
    """将图片转换为WebP格式"""
    img = Image.open(image_path)
    
    # 生成WebP文件名
    filename = os.path.splitext(os.path.basename(image_path))[0] + '.webp'
    webp_path = os.path.join(os.path.dirname(image_path), filename)
    
    # 保存为WebP
    img.save(webp_path, 'WEBP', quality=quality)
    return os.path.basename(webp_path)

在模板中使用picture元素支持多种格式:

<picture>
    <source srcset="{{ url_for('static', filename='uploads/photos/' + photo.webp_filename) }}" type="image/webp">
    <img src="{{ url_for('static', filename='uploads/photos/' + photo.filename) }}" 
         alt="{{ photo.description or '照片' }}">
picture>

6. 实现分页加载

修改相册视图函数,支持分页:

@bp.route('/album/')
def view_album(album_id):
    album = Album.query.get_or_404(album_id)
    page = request.args.get('page', 1, type=int)
    per_page = 12  # 每页显示12张照片
    
    photos = Photo.query.filter_by(album_id=album_id).paginate(page=page, per_page=per_page)
    
    return render_template('album/view.html', album=album, photos=photos)

修改模板,添加分页控件:


<div class="row">
    {% for photo in photos.items %}
    
    {% endfor %}
div>


<nav aria-label="相册分页">
    <ul class="pagination justify-content-center">
        {% if photos.has_prev %}
        <li class="page-item">
            <a class="page-link" href="{{ url_for('album.view_album', album_id=album.id, page=photos.prev_num) }}">上一页a>
        li>
        {% else %}
        <li class="page-item disabled">
            <span class="page-link">上一页span>
        li>
        {% endif %}
        
        {% for page_num in photos.iter_pages(left_edge=1, right_edge=1, left_current=2, right_current=2) %}
            {% if page_num %}
                {% if page_num == photos.page %}
                <li class="page-item active">
                    <span class="page-link">{{ page_num }}span>
                li>
                {% else %}
                <li class="page-item">
                    <a class="page-link" href="{{ url_for('album.view_album', album_id=album.id, page=page_num) }}">{{ page_num }}a>
                li>
                {% endif %}
            {% else %}
                <li class="page-item disabled">
                    <span class="page-link">...span>
                li>
            {% endif %}
        {% endfor %}
        
        {% if photos.has_next %}
        <li class="page-item">
            <a class="page-link" href="{{ url_for('album.view_album', album_id=album.id, page=photos.next_num) }}">下一页a>
        li>
        {% else %}
        <li class="page-item disabled">
            <span class="page-link">下一页span>
        li>
        {% endif %}
    ul>
nav>

7. 使用CDN加速静态资源

如果您有公网服务器,可以考虑使用CDN加速:

  1. 使用免费CDN服务

    • Cloudflare
    • Netlify
    • Vercel
  2. 配置Nginx作为静态资源服务器

server {
    listen 80;
    server_name static.yourdomain.com;
    
    location / {
        root /var/www/family_website/app/static;
        expires 30d;
        add_header Cache-Control "public, max-age=2592000";
        gzip on;
        gzip_types text/plain text/css application/javascript image/svg+xml;
        gzip_min_length 1000;
    }
}

然后在Flask应用中配置静态URL:

app.config['STATIC_URL'] = 'http://static.yourdomain.com/'

8. 实现无限滚动

替代分页的另一种方式是无限滚动:

let page = 1;
let loading = false;
const container = document.querySelector('.photos-container');

function loadMorePhotos() {
    if (loading) return;
    
    loading = true;
    page++;
    
    fetch(`/api/album/${albumId}/photos?page=${page}`)
        .then(response => response.json())
        .then(data => {
            if (data.photos.length > 0) {
                data.photos.forEach(photo => {
                    const photoElement = document.createElement('div');
                    photoElement.className = 'col-md-3 mb-4';
                    photoElement.innerHTML = `
                        ${photo.filename}" data-lightbox="album">
                            ${photo.filename}" 
                                 alt="${photo.description || '照片'}" 
                                 class="img-thumbnail">
                        
                    `;
                    container.appendChild(photoElement);
                });
                loading = false;
            } else {
                // 没有更多照片了
                window.removeEventListener('scroll', handleScroll);
            }
        })
        .catch(error => {
            console.error('加载更多照片时出错:', error);
            loading = false;
        });
}

function handleScroll() {
    if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 500) {
        loadMorePhotos();
    }
}

window.addEventListener('scroll', handleScroll);

添加相应的API端点:

@bp.route('/api/album//photos')
def api_album_photos(album_id):
    page = request.args.get('page', 1, type=int)
    per_page = 12
    
    photos = Photo.query.filter_by(album_id=album_id).paginate(page=page, per_page=per_page)
    
    return jsonify({
        'photos': [{
            'id': photo.id,
            'filename': photo.filename,
            'description': photo.description
        } for photo in photos.items],
        'has_next': photos.has_next
    })

9. 使用图片预加载

对于相册浏览,可以预加载下一张图片:

function preloadImages() {
    const images = document.querySelectorAll('a[data-lightbox="album"]');
    let currentIndex = 0;
    
    // 预加载当前可见图片的下一张
    images.forEach((image, index) => {
        if (isElementInViewport(image)) {
            currentIndex = index;
        }
    });
    
    // 预加载下一张图片
    if (currentIndex + 1 < images.length) {
        const nextImage = new Image();
        nextImage.src = images[currentIndex + 1].href;
    }
}

function isElementInViewport(el) {
    const rect = el.getBoundingClientRect();
    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
        rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
}

window.addEventListener('scroll', preloadImages);
document.addEventListener('DOMContentLoaded', preloadImages);

10. 服务器端优化

启用Gzip压缩

在Nginx配置中添加:

gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types
  application/javascript
  application/json
  application/x-javascript
  application/xml
  image/svg+xml
  text/css
  text/javascript
  text/plain
  text/xml;

使用HTTP/2

更新Nginx配置以支持HTTP/2:

server {
    listen 443 ssl http2;
    server_name yourdomain.com;
    
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    
    # 其他配置...
}

实施建议

  1. 首先实施最简单的优化

    • 图片压缩
    • 生成缩略图
    • 添加延迟加载
  2. 根据网站规模选择合适的方案

    • 小型网站:本地优化足够
    • 中型网站:添加分页或无限滚动
    • 大型网站:考虑CDN和服务器优化
  3. 监控性能改进
    使用浏览器开发工具的Network面板监控加载时间

通过实施这些优化,您的家庭网站在低速网络环境下的照片加载速度应该会有显著提升。

你可能感兴趣的:(Python实战进阶,python,php,网络)