您现在的位置是:首页 >技术教程 >【Docker】bing每日壁纸下载保存及自建API服务网站首页技术教程
【Docker】bing每日壁纸下载保存及自建API服务
文章目录
前言
本文介绍了一个基于Python的自动化程序,用于每日从Bing获取高清壁纸并保存到本地,同时提供API服务以方便用户获取随机或最新的壁纸。该程序结合了Flask框架、SQLite数据库以及定时任务调度器,能够高效地完成壁纸的下载、存储和分发。
一、程序概述
该程序的主要功能是从Bing每日壁纸中获取高清图片,并将其保存到本地文件夹中。同时,程序会将壁纸的相关信息(如标题、版权信息等)存储到SQLite数据库中。通过Flask框架,程序提供了多个API接口,用户可以通过这些接口获取随机壁纸或最新壁纸的URL及其详细信息。此外,程序还配置了定时任务,每天自动下载最新的壁纸。
二、程序功能
1.获取Bing每日壁纸
程序通过访问Bing的API接口(https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN)获取每日壁纸的URL、标题、版权信息等数据。获取到的壁纸URL会被调整为UHD(超高清)格式,以确保图片质量。
API 接口参数含义
参数 | 含义 | 是否必须包含 |
---|---|---|
format | 数据返回形式 js : json xml : xml | 否 |
idx | 截至天数 0 : 今天 -1 : 明天 1 : 昨天(0-15以此类推) | 否 |
n | 返回数量 | 是 |
mkt | 地区(zh-CN 国区) | 否 |
代码如下:
# 获取Bing每日壁纸信息
def download_bing_image():
url = "https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN"
response = requests.get(url, verify=False)
if response.status_code != 200:
logger.error("Failed to get Bing image data.")
return None, None, None, None
pic_data = response.json()
try:
image_url = "https://www.bing.com" + pic_data['images'][0]['url']
# 将 1920x1080 替换为 uhd
image_url = image_url.replace('1920x1080', 'UHD')
image_date = pic_data['images'][0]['enddate']
image_title = pic_data['images'][0]['title']
image_copyright = pic_data['images'][0]['copyright']
# 格式化日期
date_str = datetime.strptime(str(image_date), '%Y%m%d').strftime('%Y-%m-%d')
# 处理版权信息
word = image_copyright.split('(')[0].strip()
return image_url, date_str, image_title, word
except KeyError:
logger.error("Error parsing the Bing response.")
return None, None, None, None
由于通过API获取到的图片默认为1920*1080分辨率的,所以需要将返回的链接中的1920x1080替换为UHD即可获取4K壁纸
2.保存壁纸到本地及数据库操作
程序会将获取到的壁纸保存到指定的本地文件夹中。保存时,程序会从URL中提取文件名,并清理文件名中的无效字符,以确保文件名的合法性。保存成功后,壁纸的相关信息会被存储到SQLite数据库中。
代码如下:
# 保存图片并保存信息到数据库
def save_image(image_url, savepath, date, title, word):
if savepath and os.path.exists(savepath):
# 使用正则表达式从URL中提取文件名(去掉OHR.并提取编号和UHD)
match = re.search(r'id=OHR.(.*?)_UHD', image_url)
if match:
# 获取提取的文件名部分并加上_UHD.jpg
original_filename = match.group(1) + "_UHD.jpg"
else:
logger.error(f"无法提取文件名: {image_url}")
return False
# 生成文件名并清理无效字符
cleaned_filename = re.sub(r'[<>:"/\|?*]', '_', original_filename)
save_file_path = os.path.join(savepath, cleaned_filename)
# 下载图片并保存
response = requests.get(image_url, stream=True)
if response.status_code == 200:
with open(save_file_path, 'wb') as file:
shutil.copyfileobj(response.raw, file)
logger.info(f"Image saved to: {save_file_path}")
else:
logger.error("Failed to download image.")
return False
# 将信息保存到SQLite数据库
save_to_db(cleaned_filename, title, word)
return True
return False
# 保存图片信息到数据库
def save_to_db(filename, title, copyright):
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
cursor.execute('''
INSERT OR REPLACE INTO wallpapers (filename, title, copyright)
VALUES (?, ?, ?)
''', (filename, title, copyright))
conn.commit()
conn.close()
3.提供API服务
程序通过Flask框架提供了以下API接口:
-
随机壁纸URL:/rand_UHD,返回一个随机壁纸的URL。
-
随机壁纸详细信息:/rand_UHD_Detail,返回一个随机壁纸的URL、标题和版权信息。
-
最新壁纸URL:/latest_UHD,返回最新壁纸的URL。
-
最新壁纸详细信息:/latest_UHD_Detail,返回最新壁纸的URL、标题和版权信息。
代码如下:
# API 1: 返回随机图片的URL
@app.route('/rand_UHD', methods=['GET'])
def random_image_url():
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
# 生成随机OFFSET值,避免使用ORDER BY RANDOM()
cursor.execute('SELECT COUNT(*) FROM wallpapers')
total_rows = cursor.fetchone()[0]
random_offset = random.randint(0, total_rows - 1)
cursor.execute('SELECT filename FROM wallpapers LIMIT 1 OFFSET ?', (random_offset,))
# cursor.execute('SELECT filename FROM wallpapers ORDER BY RANDOM() LIMIT 1')
result = cursor.fetchone()
conn.close()
if result:
image_url = f"https://www.bing.com/th?id=OHR.{result[0]}"
return redirect(image_url)
return "No images found in the database."
# API 2: 返回随机图片及其title和copyright
@app.route('/rand_UHD_Detail', methods=['GET'])
def random_image():
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
# 生成随机OFFSET值,避免使用ORDER BY RANDOM()
cursor.execute('SELECT COUNT(*) FROM wallpapers')
total_rows = cursor.fetchone()[0]
random_offset = random.randint(0, total_rows - 1)
cursor.execute('SELECT filename, title, copyright FROM wallpapers LIMIT 1 OFFSET ?', (random_offset,))
# cursor.execute('SELECT filename, title, copyright FROM wallpapers ORDER BY RANDOM() LIMIT 1')
result = cursor.fetchone()
conn.close()
if result:
image_url = f"https://www.bing.com/th?id=OHR.{result[0]}"
response = {
"url": image_url,
"title": result[1],
"copyright": result[2]
}
return jsonify(response)
return jsonify({"error": "No images found in the database."})
# API 3: 返回最新图片的URL
@app.route('/latest_UHD', methods=['GET'])
def latest_image_url():
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
cursor.execute('SELECT filename FROM wallpapers ORDER BY ROWID DESC LIMIT 1')
result = cursor.fetchone()
conn.close()
if result:
image_url = f"https://www.bing.com/th?id=OHR.{result[0]}"
return redirect(image_url)
return "No images found in the database."
# API 4: 返回最新图片及其title和copyright
@app.route('/latest_UHD_Detail', methods=['GET'])
def latest_image():
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
cursor.execute('SELECT filename, title, copyright FROM wallpapers ORDER BY ROWID DESC LIMIT 1')
result = cursor.fetchone()
conn.close()
if result:
image_url = f"https://www.bing.com/th?id=OHR.{result[0]}"
response = {
"url": image_url,
"title": result[1],
"copyright": result[2]
}
return jsonify(response)
return jsonify({"error": "No images found in the database."})
4. 定时任务
程序使用APScheduler库配置了定时任务,每天在8:00和20:00自动执行壁纸下载任务。定时任务会调用job()函数,该函数负责获取最新的壁纸并保存到本地。
代码如下:
def job():
logger.info("Starting scheduled job...")
# 获取Bing每日壁纸信息
image_url, date, title, word = download_bing_image()
if not image_url or not date or not title or not word:
return
# 保存图片
save_image(image_url, savepath, date, title, word)
logger.info("Scheduled job completed.")
# 添加定时任务:每天 8:00 和 20:00 执行 job()
scheduler.add_job(
id='download_bing_image', # 任务唯一ID
func=job, # 定时任务函数
trigger='cron', # 使用 cron 触发器
hour='8,20', # 在 8:00 和 20:00 触发
minute=0 # 在整点触发
)
5.完整代码
import os
import threading
import requests
import shutil
import re
import sqlite3
import urllib3
from datetime import datetime
import random
from flask import Flask, jsonify, redirect
import logging
from flask_apscheduler import APScheduler
# 禁用 SSL 警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 配置日志文件
logging.basicConfig(filename='/app/log/cron.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger()
# 配置部分:设置保存路径
savepath = "/app/BingWallpapers" # 设置你的保存文件夹路径
db_file = '/app/wallpapers.db' # SQLite数据库文件
# 创建Flask应用
app = Flask(__name__)
app.config['SCHEDULER_API_ENABLED'] = True
app.config['SCHEDULER_TIMEZONE'] = 'Asia/Shanghai'
scheduler = APScheduler()
scheduler.init_app(app)
scheduler.start()
# 创建数据库并初始化表
def create_db():
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS wallpapers (
filename TEXT PRIMARY KEY,
title TEXT,
copyright TEXT
)
''')
conn.commit()
conn.close()
# 获取Bing每日壁纸信息
def download_bing_image():
url = "https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN"
response = requests.get(url, verify=False)
if response.status_code != 200:
logger.error("Failed to get Bing image data.")
return None, None, None, None
pic_data = response.json()
try:
image_url = "https://www.bing.com" + pic_data['images'][0]['url']
# 将 1920x1080 替换为 uhd
image_url = image_url.replace('1920x1080', 'UHD')
image_date = pic_data['images'][0]['enddate']
image_title = pic_data['images'][0]['title']
image_copyright = pic_data['images'][0]['copyright']
# 格式化日期
date_str = datetime.strptime(str(image_date), '%Y%m%d').strftime('%Y-%m-%d')
# 处理版权信息
word = image_copyright.split('(')[0].strip()
return image_url, date_str, image_title, word
except KeyError:
logger.error("Error parsing the Bing response.")
return None, None, None, None
# 保存图片并保存信息到数据库
def save_image(image_url, savepath, date, title, word):
if savepath and os.path.exists(savepath):
# 使用正则表达式从URL中提取文件名(去掉OHR.并提取编号和UHD)
match = re.search(r'id=OHR.(.*?)_UHD', image_url)
if match:
# 获取提取的文件名部分并加上_UHD.jpg
original_filename = match.group(1) + "_UHD.jpg"
else:
logger.error(f"无法提取文件名: {image_url}")
return False
# 生成文件名并清理无效字符
cleaned_filename = re.sub(r'[<>:"/\|?*]', '_', original_filename)
save_file_path = os.path.join(savepath, cleaned_filename)
# 下载图片并保存
response = requests.get(image_url, stream=True)
if response.status_code == 200:
with open(save_file_path, 'wb') as file:
shutil.copyfileobj(response.raw, file)
logger.info(f"Image saved to: {save_file_path}")
else:
logger.error("Failed to download image.")
return False
# 将信息保存到SQLite数据库
save_to_db(cleaned_filename, title, word)
return True
return False
# 保存图片信息到数据库
def save_to_db(filename, title, copyright):
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
cursor.execute('''
INSERT OR REPLACE INTO wallpapers (filename, title, copyright)
VALUES (?, ?, ?)
''', (filename, title, copyright))
conn.commit()
conn.close()
# API 1: 返回随机图片的URL
@app.route('/rand_UHD', methods=['GET'])
def random_image_url():
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
# 生成随机OFFSET值,避免使用ORDER BY RANDOM()
cursor.execute('SELECT COUNT(*) FROM wallpapers')
total_rows = cursor.fetchone()[0]
random_offset = random.randint(0, total_rows - 1)
cursor.execute('SELECT filename FROM wallpapers LIMIT 1 OFFSET ?', (random_offset,))
# cursor.execute('SELECT filename FROM wallpapers ORDER BY RANDOM() LIMIT 1')
result = cursor.fetchone()
conn.close()
if result:
image_url = f"https://www.bing.com/th?id=OHR.{result[0]}"
return redirect(image_url)
return "No images found in the database."
# API 2: 返回随机图片及其title和copyright
@app.route('/rand_UHD_Detail', methods=['GET'])
def random_image():
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
# 生成随机OFFSET值,避免使用ORDER BY RANDOM()
cursor.execute('SELECT COUNT(*) FROM wallpapers')
total_rows = cursor.fetchone()[0]
random_offset = random.randint(0, total_rows - 1)
cursor.execute('SELECT filename, title, copyright FROM wallpapers LIMIT 1 OFFSET ?', (random_offset,))
# cursor.execute('SELECT filename, title, copyright FROM wallpapers ORDER BY RANDOM() LIMIT 1')
result = cursor.fetchone()
conn.close()
if result:
image_url = f"https://www.bing.com/th?id=OHR.{result[0]}"
response = {
"url": image_url,
"title": result[1],
"copyright": result[2]
}
return jsonify(response)
return jsonify({"error": "No images found in the database."})
# API 3: 返回最新图片的URL
@app.route('/latest_UHD', methods=['GET'])
def latest_image_url():
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
cursor.execute('SELECT filename FROM wallpapers ORDER BY ROWID DESC LIMIT 1')
result = cursor.fetchone()
conn.close()
if result:
image_url = f"https://www.bing.com/th?id=OHR.{result[0]}"
return redirect(image_url)
return "No images found in the database."
# API 4: 返回最新图片及其title和copyright
@app.route('/latest_UHD_Detail', methods=['GET'])
def latest_image():
conn = sqlite3.connect(db_file)
cursor = conn.cursor()
cursor.execute('SELECT filename, title, copyright FROM wallpapers ORDER BY ROWID DESC LIMIT 1')
result = cursor.fetchone()
conn.close()
if result:
image_url = f"https://www.bing.com/th?id=OHR.{result[0]}"
response = {
"url": image_url,
"title": result[1],
"copyright": result[2]
}
return jsonify(response)
return jsonify({"error": "No images found in the database."})
def job():
logger.info("Starting scheduled job...")
# 获取Bing每日壁纸信息
image_url, date, title, word = download_bing_image()
if not image_url or not date or not title or not word:
return
# 保存图片
save_image(image_url, savepath, date, title, word)
logger.info("Scheduled job completed.")
# 添加定时任务:每天 8:00 和 20:00 执行 job()
scheduler.add_job(
id='download_bing_image', # 任务唯一ID
func=job, # 定时任务函数
trigger='cron', # 使用 cron 触发器
hour='8,20', # 在 8:00 和 20:00 触发
minute=0 # 在整点触发
)
# 启动Web服务器和定时任务
if __name__ == "__main__":
# 创建数据库表
create_db()
# 启动Flask应用
try:
app.run(host="0.0.0.0", port=5000)
except (KeyboardInterrupt, SystemExit):
logger.info("Shutting down...")
三、Docker打包及部署
1.编写 Dockerfile
# 使用官方 Python 镜像作为基础镜像
FROM python:3.9-slim
# 设置容器的环境变量,确保使用 UTF-8 编码
ENV LANG=C.UTF-8
ENV TZ=Asia/Shanghai
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 设置工作目录
WORKDIR /app
# 将当前目录的内容复制到容器的工作目录
COPY ../ /app
# 安装所需的Python库
RUN pip install --no-cache-dir -r requirements.txt
# 创建日志目录并赋予权限
RUN mkdir -p /app/log && chmod -R 777 /app/log
# 设置环境变量,允许在运行时指定数据库和图片路径
ENV DB_PATH=/app/wallpapers.db
ENV IMAGE_PATH=/app/BingWallpapers
ENV SSL_PATH=/app/ssl
ENV log_PATH=/app/log/cron.log
# 暴露容器端口
EXPOSE 5000
# 使用 gunicorn 启动应用,指定 workers 数量
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "1", "--timeout", "120", "--certfile", "/app/ssl/server.crt", "--keyfile", "/app/ssl/server.key", "main:app"]
当前dockerfile中使用gunicorn启动应用,配置了https连接,所以指定了SSL证书的地址,如不需要可不加,代码如下:
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "1", "--timeout", "120", "main:app"]
2.打包镜像
新建docker文件夹,将需要打包的文件放入文件夹,目录如下:
进入控制台,cd路径到docker文件夹中,执行docker命令
docker build -t bing-wallpaper-app-ssl:1.0 .
3.运行镜像
docker run -d -p 5000:5000 -v your_db_path:/app/wallpapers.db -v your_BingWallpapers_path:/app/BingWallpaper -v your_ssl_file_path:/app/ssl -v your_log_path:/app/log/cron.log --name bing-wallpaper-ssl bing-wallpaper-app-ssl
若上面打包时未使用ssl,这里可不添加路径
示例运行命令:
docker run -d -p 5000:5000 -v /home/ubuntu/bingWallpapers/BingWallpapers:/app/BingWallpapers -v /home/ubuntu/bingWallpapers/database/wallpapers.db:/app/wallpapers.db -v /home/ubuntu/bingWallpapers/ssl:/app/ssl -v /home/ubuntu/bingWallpapers/log/cron.log:/app/log/cron.log --name bing-wallpaper-ssl bing-wallpaper-app-ssl
4.测试api是否可用
-
随机壁纸URL:https://localhost:5000/rand_UHD
四、无需打包,直接拉取镜像使用
docker pull registry.cn-hangzhou.aliyuncs.com/egmx/bing-wallpaper-ssl
运行部署操作参考上面的内容