Skip to content

获取微软必应每日背景图

约 2242 字大约 7 分钟

WindowsBing

2025-01-14

介绍

微软必应(Microsoft Bing)首页每日都会更新一张背景图,看起来非常漂亮。

现在有个需求,需要把博客首页图片设置为和微软必应类似,每日更新为最新的背景图片。

思考 像图片这种静态资源,大公司肯定有CDN加速的,所以我们就没必要从其他地方访问资源,直接引用微软必应墙纸图片的URL即可。

项目地址:Github bingwallpaper 项目效果:获取今日微软bing壁纸

查看每日图片地址

检查元素获取

  1. 打开浏览器,访问https://cn.bing.com/
  2. 右键选择检查页面元素。
<div class="hp_top_cover" style="background-image: url("https://s.cn.bing.net/th?id=OHR.CadizSpain_ZH-CN0032172399_1920x1080.webp&qlt=50"); opacity: ; display: block;"><div class="hp_top_cover_dim" style="opacity: 0;"></div></div>

可以看到图片的地址是https://s.cn.bing.net/th?id=OHR.CadizSpain_ZH-CN0032172399_1920x1080.webp&qlt=50

接口获取

手动获取局限性比较大,我们不可能每天都去获取一下图片的地址,然后再更新到我们的博客中。

还好我们可以使用get请求接口https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1获取最近的图片。

接口中的参数:

  • format:返回的数据格式,js表示返回json格式。
  • idx:返回的图片索引,0表示返回最新的图片,1表示昨天的图片。
  • n:返回的图片数量,1表示返回最新一张图片, 1表示返回最新两张图片(即今天和昨天的图片)。

接口响应如下:

{
  "images": [
    {
      "startdate": "20250113",
      "fullstartdate": "202501131600",
      "enddate": "20250114",
      "url": "/th?id=OHR.CadizSpain_ZH-CN0032172399_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp",
      "urlbase": "/th?id=OHR.CadizSpain_ZH-CN0032172399",
      "copyright": "Village of Zahara de la Sierra, Cádiz province, Spain (© SEN LI/Getty Images)",
      "copyrightlink": "https://www.bing.com/search?q=%E8%90%A8%E9%98%BF%E6%8B%89%C2%B7%E5%BE%B7%E6%8B%89%E8%B0%A2%E6%8B%89&form=hpcapt&mkt=zh-cn",
      "title": "宁静之地",
      "quiz": "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20250113_CadizSpain%22&FORM=HPQUIZ",
      "wp": true,
      "hsh": "281d51d6434aba29274dbf3434ddf2c2",
      "drk": 1,
      "top": 1,
      "bot": 1,
      "hs": []
    }
  ],
  "tooltips": {
    "loading": "正在加载...",
    "previous": "上一个图像",
    "next": "下一个图像",
    "walle": "此图片不能下载用作壁纸。",
    "walls": "下载今日美图。仅限用作桌面壁纸。"
  }
}

其中url字段就是图片的地址。在前面加上域名,https://cn.bing.comhttps://s.cn.bing.net就是图片的地址。

如上面示例中的图片地址为:https://cn.bing.com/th?id=OHR.CadizSpain_ZH-CN0032172399_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hphttps://s.cn.bing.net/th?id=OHR.CadizSpain_ZH-CN0032172399_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp

自定义接口做转发

上面的https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1接口虽然能获取最近的图片信息。但是我们需要的是一个图片的url。我们又不能每天手动去获取地址再更新到博客中。我们需要能自动获取到最新的图片地址,我们可以写一个接口来获取最新的图片,然后重定向到最新的图片地址。

我们只需要每天显示最新的图片即可,在接口中我们请求https://cn.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1地址,获取到最新的图片地址,然后重定向到最新的图片地址即可。

提示 使用了接口重定向之后,我们还可以在接口中添加其他逻辑,比如:重定向到一周之内的一个随机图片,或者重定向到指定日期(比如去年同期)的图片等。这样可操作空间就比较大了。

实现

这里我选用Pythonfastapi框架来实现接口。项目框架直接使用我之前写的其他项目框架改造而来。对于实现这个项目来说有些冗余,但是具有很好的衍生性。

思路

实现的思路是,GET请求接口/wallpaper/bing/last。先查询Redis中是否有缓存,如果有就直接重定向到缓存里对应的图片地址,如果没有缓存就去获取最新的图片地址,然后保存到Redis中,并重定向到最新的图片地址。

部署的测试接口获取今日微软bing壁纸

接口里面还可以实现很多逻辑,比如:

  • 随机返回一张,最近一周或几天的图片
  • 随机返回一张,今年、去年等同年同月同日的图片
  • 随机返回一张,你喜欢的图片
  • 甚至可以做到,通过用户名称、喜好匹配一张它可能喜欢的图片。(这里需要接入用户信息,匹配可以通过AI系统来完成)

提示 没有做不到,只有想不到,更多的思路需要我们自己去发掘。

目录结构

项目结构如下:

$ tree -I "venv"
.
├── Dockerfile
├── LICENSE
├── README.md
├── app
   ├── __init__.py
   ├── config
   ├── __init__.py
   ├── app_config.py
   └── validate_template_config.py
   ├── errors
   ├── __init__.py
   ├── app_error.py
   ├── http_error.py
   └── validation_error.py
   ├── middleware
   ├── __init__.py
   └── usetime_middleware.py
   ├── router
   ├── __init__.py
   └── wallpaper
       ├── __init__.py
       └── api.py
   ├── server
   ├── __init__.py
   ├── app_fastapi.py
   └── app_server.py
   ├── types
   ├── __init__.py
   ├── enum_types.py
   ├── redis_types.py
   ├── response
   ├── __init__.py
   └── http_response.py
   └── wallpaper_types.py
   └── utils
       ├── __init__.py
       ├── redis_util.py
       └── time_util.py
├── gunicorn_config.py
├── hooks
   └── auto-requirements.sh
├── logs
   ├── log_2025-01-17.log
   └── log_2025-01-18.log
├── main.py
└── requirements.txt

13 directories, 34 files

这里忽略了虚拟环境venv目录。我们只需要关注具体的实现文件app/router/wallpaper/api.py即可,其他基本都是项目的支持文件。

核心代码

app/router/wallpaper/api.py文件内容如下:

'''
Author: matiastang
Date: 2024-04-22 10:19:59
LastEditors: matiastang
LastEditTime: 2025-01-17 12:14:03
FilePath: /bingwallpaper/app/router/wallpaper/api.py
Description: bing壁纸
'''
import json
import requests
from loguru import logger
from fastapi import APIRouter

from starlette.responses import RedirectResponse
from app.types.response.http_response import HttpResponse, ResponseFail
from app.types.wallpaper_types import BingWallpaperImage
from app.types.enum_types import TimezoneName
from app.utils import redis_util, time_util

# 缓存key
WALLPAPER_BING_REDIS_KEY = 'last'

# 路由
router = APIRouter(
    prefix="/wallpaper/bing",
    tags=["wallpaper.bing"],
)


async def get_bing_wallpaper(idx: int = 0, num: int = 1):
    """
    获取bingwallpaper的壁纸信息

    Args:
        idx (int, optional): 图片索引. Defaults to 0.
        num (int, optional): 图片数量. Defaults to 1.

    Returns:
        list[BingWallpaperImage]: 图片列表
    """

    # 目标URL
    url = 'https://cn.bing.com/HPImageArchive.aspx'

    # 请求头
    params = {
        'format': 'js',
        'idx': idx,
        'n': num,
    }

    # 发送GET请求
    res = requests.get(url, params=params)

    # 打印响应内容
    data = res.json()
    images = data.get('images', [])
    return [BingWallpaperImage(**image) for image in images]


@router.get('/last')
async def get_bing_wallpaper_last() -> HttpResponse:
    """ 获取bing的最新一张壁纸 """

    info = await redis_util.get_cache(WALLPAPER_BING_REDIS_KEY)
    if info:
        try:
            images = json.loads(info)
            if isinstance(images, list) and len(images) > 0:
                wallpapers = [BingWallpaperImage(**image) for image in images]
                first_wallpapers = wallpapers[0]
                wallpaper_url = first_wallpapers.url
                url = f"https://cn.bing.com{wallpaper_url}"
                return RedirectResponse(url=url, status_code=302)
        except Exception as e:
            logger.error('-' * 10 + '[LAST]解析缓存壁纸信息失败' + '-' * 10)
            logger.error(e)

    wallpapers = await get_bing_wallpaper()
    if len(wallpapers) > 0:
        try:
            json_str = json.dumps([wallpaper.model_dump() for wallpaper in wallpapers])
            ex = time_util.get_remaining_seconds_in_day(TimezoneName.SHANGHAI)
            status = await redis_util.set_cache(WALLPAPER_BING_REDIS_KEY, json_str, ex)
            if not status:
                logger.error('-' * 10 + '[LAST]壁纸缓存失败' + '-' * 10)
            first_wallpapers = wallpapers[0]
            wallpaper_url = first_wallpapers.url
            url = f"https://cn.bing.com{wallpaper_url}"
            return RedirectResponse(url=url, status_code=302)
        except Exception as e:
            logger.error('-' * 10 + '[LAST]处理壁纸结果失败' + '-' * 10)
            logger.error(e)
    return ResponseFail('获取壁纸失败', 500)

app/types/enum_types.py文件内容如下:

'''
Author: matiastang
Date: 2025-01-17 12:09:15
LastEditors: matiastang
LastEditTime: 2025-01-17 12:11:24
FilePath: /bingwallpaper/app/types/enum_types.py
Description: 枚举
'''
from enum import Enum


class TimezoneName(Enum):
    """时区名称"""

    UTC = 'UTC'

    SHANGHAI = 'Asia/Shanghai'
    """上海"""

app/utils/time_util.py文件内容如下:

#!/Users/matias/matias/MT/MTGithub/bingwallpaper/venv/bin/python3
# coding=utf-8
'''
Author: matiastang
Date: 2025-01-17 11:26:02
LastEditors: matiastang
LastEditTime: 2025-01-17 12:55:38
FilePath: /bingwallpaper/app/utils/time_util.py
Description: 时间
'''
import pytz

from datetime import datetime
from app.types.enum_types import TimezoneName


def get_remaining_seconds_in_day(timezone_name: TimezoneName = TimezoneName.UTC) -> int:
    """
    获取指定时区当天剩余的时间(秒数)

    Args:
        timezone_name (str): 时区名称(如 "Asia/Shanghai", "UTC")

    Raises:
        ValueError: 未知时区

    Returns:
        int: 当天剩余时间的秒数
    """
    try:
        # 获取指定时区
        timezone = pytz.timezone(timezone_name.value)

        # 当前时区时间
        # now = datetime.now(timezone)
        now = datetime.now()

        # 当天结束时间(午夜)
        # end_of_day = datetime(now.year, now.month, now.day, 23, 59, 59, tzinfo=timezone)
        end_of_day = datetime(now.year, now.month, now.day, 23, 59, 59)

        end_of_day_test = datetime(now.year, now.month, now.day, 23, 59, 59, tzinfo=timezone)

        # 转换时间
        now_time_tz = timezone.localize(now)
        end_of_day_time_tz = timezone.localize(end_of_day)

        print(end_of_day, end_of_day_test, end_of_day_time_tz)

        # 计算剩余时间
        print(end_of_day, now)
        print(end_of_day_time_tz, now_time_tz)
        # remaining_time = end_of_day - now
        remaining_time = end_of_day_time_tz - now_time_tz
        return int(remaining_time.total_seconds())
    except pytz.UnknownTimeZoneError:
        raise ValueError(f"未知时区: {timezone_name}")


if __name__ == "__main__":
    """
    测试
    python3 -m app.utils.time_util
    """
    print(get_remaining_seconds_in_day(TimezoneName.SHANGHAI))

注意 这里有个细节,就是Redis的缓存时间,我这里是取的当天剩余的时间,这样就会同时更新。(这里时区默认使用的东八区,我也默认认为https://cn.bing.com/HPImageArchive.aspx接口获取的图片是每天0点更新,如果不是后面可以调整逻辑)

完整项目代码可以查看Github bingwallpaper

思考

这里使用了一个框架实现一个简单的接口,想想都觉得麻烦。其实在做这个项目之前,我就在想这种简单的处理,应该通过Nginx的配置加上脚本就能实现,我也去查了一下,暂时得出的结论是可以的,但我没有试过,等后面有空了再试一下。

Nginx 可以通过 cgi-bin 配置运行简单的脚本(例如 ShellPython 脚本)。不过,这需要使用第三方模块如 fcgiwrap,并不常用。

既然感觉有更简单的方式,那我为什么还要用FastAPI这个相对复杂一点儿的框架来做呢?主要还是想对之前做FastAPI项目做一下简单的整理,记录一下。

参考

Github bingwallpaper

获取微软必应每日一图

历史微软必应壁纸

bing

博客

博客

Github Pages