本文最后更新于 2026年4月13日 晚上
微软必应的每日一图收录了来自全世界的自然风光和人文摄影。这些唯美的照片不仅是 bing.com 的默认背景,微软也官方提供了「必应壁纸」应用供用户将其设为桌面。
这些图片质感极佳,我很喜欢,于是决定用脚本把它们每天自动保存下来,并提取最新的一张作为本站的首页头图。

探秘必应每日一图 API
必应每日一图的元数据可以通过 https://www.bing.com/HPImageArchive.aspx 获取。直接访问该地址不会返回任何有效内容,我们需要为其追加查询参数:
format:返回数据的格式。可选:js,xml(默认),rss。
idx(必需):图片日期相对今天的偏移量。可选:0-7(大于 7 按 7 处理)。
n(必需):获取的图片数量。可选:1-8(大于 8 按 8 处理)。
mkt:地区代码,大小写不敏感。不同地区的每日一图可能有所不同。
中国大陆的 IP 直接请求只能获取中国大陆(ZH-CN)的结果,mkt参数会失效。如果需要获取全球其他地区的图片,需要配置网络代理。
经过多次测试,mkt取以下值时能基本覆盖必应全球每日更新的图片库:EN-US JA-JP ZH-CN EN-IN DE-DE ES-ES FR-FR IT-IT EN-GB PT-BR EN-CA。
接下来,我们发一条 GET 请求看看返回结构:
https://www.bing.com/HPImageArchive.aspx?idx=0&n=1&mkt=zh-cn
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <images> <image> <startdate>20231230</startdate> <fullstartdate>202312300800</fullstartdate> <enddate>20231231</enddate> <url>/th?id=OHR.ThailandNewYears_ZH-CN2058192262_1920x1080.jpg&rf=LaDigue_1920x1080.jpg&pid=hp</url> <urlBase>/th?id=OHR.ThailandNewYears_ZH-CN2058192262</urlBase> <copyright>帕那空奇里上空的烟花,碧武里府,泰国 (© noomcpk/Shutterstock)</copyright> <copyrightlink>https://www.bing.com/search?q=%E8%B7%A8%E5%B9%B4%E5%A4%9C&form=hpcapt&mkt=zh-cn</copyrightlink> <headline>明年见!</headline> <drk>1</drk> <top>1</top> <bot>1</bot> <hotspots /> </image> <tooltips> </tooltips> </images>
|
分析 XML 数据,提取我们需要的部分:
enddate:图片实际发布的日期。
urlBase:图片的基础路径。我们可以基于它拼接出不同分辨率的图片地址(例如追加_1920x1080.jpg)。同时,这个字符串里还藏着图片的主题(如ThailandNewYears)。
copyright:图片的拍摄位置与版权信息。
headline:图片的简短描述。
基于urlBase,我们只需向 https://www.bing.com/th?id=OHR.ThailandNewYears_ZH-CN2058192262_1920x1080.jpg 发送请求,就能直接拿到这张图片了。

编写 Zsh 自动化脚本
手动抓取不仅繁琐,还容易遗漏。为了实现真正的自动化,我们需要编写一个 Zsh 脚本来处理数据提取、去重、下载和归档。
遍历全球地区
我们想要的不仅是本地图片,而是全球范围内的优质摄影。因此,我们需要遍历之前整理出的所有地区代码:
1 2 3 4
| for mkt in {EN-US,JA-JP,ZH-CN,EN-IN,DE-DE,ES-ES,FR-FR,IT-IT,EN-GB,PT-BR,EN-CA} do done
|
获取并解析元数据
使用 cURL 获取 XML,再交由xmllint通过 XPath 提取所需字段:
1 2 3 4 5
| image=$(curl -sG -d idx=0 -d n=1 -d mkt=$mkt https://www.bing.com/HPImageArchive.aspx) enddate=$(xmllint --xpath '//enddate/text()' - <<< $image) urlBase=$(xmllint --xpath '//urlBase/text()' - <<< $image) headline=$(xmllint --xpath '//headline/text()' - <<< $image) copyright=$(xmllint --xpath '//copyright/text()' - <<< $image)
|
注:-s参数用于静默模式隐藏进度条,-G强制使用 GET 请求,-d用于拼接查询参数。
文件命名与数据沉淀
如何命名下载的图片?enddate并不是好选择,因为同一天多地区可能有不同图片,同一图片也可能在不同日期出现在不同地区。最合理的做法是利用urlBase中包含的标题。
这里利用 Zsh 内置的字符串截断语法,将/th?id=OHR.ThailandNewYears_ZH-CN2058192262中的ThailandNewYears剥离出来:
| 语法 |
方向 |
程度 |
${str#*.} |
删除.左侧的内容 |
最小匹配 |
${str##*.} |
删除.左侧的内容 |
最大匹配 |
${str%_*} |
删除_右侧的内容 |
最小匹配 |
${str%%_*} |
删除_右侧的内容 |
最大匹配 |
结合运用,就能精准提取文件名:
1
| filename=${${urlBase#*.}%%_*}
|
为了方便日后查阅,我们同时将获取的各项元数据存储在metadata/目录下的 CSV 文件中,并将图片下载至img/目录:
1 2 3 4 5
| print -n -- "$enddate,$filename,$mkt," >> metadata.csv print -n -- "$(grep -oE '[0-9]+' <<< ${urlBase##*_})," >> metadata.csv print -n -- "\"$headline\"," >> metadata.csv print -- "\"$copyright\"" >> metadata.csv curl -so img/$filename.jpg https://www.bing.com${urlBase}_1920x1080.jpg
|

异常处理与防重复下载
网络请求总有失败的可能。如果未获取到image数据,强行解析会导致空变量和坏文件。同时,多地区往往存在重复推图的情况。我们需要添加校验逻辑:
1 2 3 4 5 6 7
| while [[ -z $image ]] do image=$(curl -sG -d idx=0 -d n=1 -d mkt=$mkt https://www.bing.com/HPImageArchive.aspx) done
[[ -f img/$filename.jpg ]] && continue
|
指定「最新图」
因为我的博客首页需要固定读取每日最新图,我们不能每次都修改源码里的图片名称。解决方案是生成一个latest.jpg副本。
为了不浪费磁盘空间,这里使用硬链接,让latest.jpg和刚下载的图片指向硬盘上的同一物理位置:
1
| ln -f img/$filename.jpg img/latest.jpg
|
在推送到 Git 仓库时,Git 仍会将其作为两份独立的文件树进行追踪,但这在本地管理中确实是个优雅的技巧。
用 Git LFS 托管至 GitHub
由于图片体积较大且数量会不断增加,我们不推荐直接把图片丢进普通 Git 仓库里,而是使用 Git LFS (Large File Storage)。它将大文件替换为文本指针进行版本控制,不仅不影响拉取代码的速度,GitHub 还免费提供了 10 GiB 的 LFS 额度,绰绰有余。
安装并配置 Git LFS(macOS 使用 Homebrew):
1 2
| brew install git-lfs git lfs install
|
告诉 LFS 追踪所有.jpg文件,并保存配置规则:
1 2
| git lfs track "*.jpg" git add .gitattributes
|
最后,只需将git add、commit和push写入我们的脚本末尾,抓图后即可自动推送到 GitHub 仓库。
完成推送后,latest.jpg 在外网的长期有效访问地址就是:
1
| https://media.githubusercontent.com/media/<你的用户名>/<仓库名>/main/img/latest.jpg
|
把这个直链放到博客配置里,就能实现首页图日更了!

最终完整脚本
结合上述所有思路,最终的 Zsh 脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #!/bin/zsh
local -i idx=${1:-0} n=1 (( idx > 14 )) && print -u2 -- 'index too large' && return $idx (( idx > 7 )) && (( n = idx - 6 )) local mkt image for mkt in {EN-US,JA-JP,ZH-CN,EN-IN,DE-DE,ES-ES,FR-FR,IT-IT,EN-GB,PT-BR,EN-CA} do image='' while [[ -z $image ]] do image=$(curl -sG -d idx=$idx -d n=$n -d mkt=$mkt https://www.bing.com/HPImageArchive.aspx) done local enddate=$(xmllint --xpath '//enddate/text()' - <<< $image | head -n$n) local urlBase=$(xmllint --xpath '//urlBase/text()' - <<< $image | head -n$n) local filename=${${urlBase#*.}%%_*} print -- "$enddate,$filename,$mkt,$(grep -oE '[0-9]+' <<< ${urlBase##*_}),\"$(xmllint --xpath '//headline/text()' - <<< $image | head -n$n)\",\"$(xmllint -- xpath '//copyright/text()' - <<< $image | head -n$n)\"" >> metadata/$mkt.csv [[ -f img/$filename.jpg ]] || curl -so img/$filename.jpg https://www.bing.com${urlBase}_1920x1080.jpg ln -f img/$filename.jpg img/latest.jpg done [[ $(git status --porcelain) ]] || return git add img metadata git commit -m "Fetch: $enddate" (($#1)) || git push
|
定时自动执行
脚本写好了,最后一步就是让它自动跑起来。这里提供两种方案。
方案 A:macOS 本地任务(launchd)
如果你有一台经常开机的 Mac,可以使用自带的launchd服务。它通过 XML(plist)文件配置。将以下内容保存为~/Library/LaunchAgents/top.tauyoung.imagearchive.plist:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>top.tauyoung.imagearchive</string> <key>Program</key> <string>/Users/tauyoung/imageArchive/fetch.sh</string> <key>EnvironmentVariables</key> <dict> <key>ALL_PROXY</key> <string>socks5://localhost:7890</string> <key>PATH</key> <string>/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string> </dict> <key>WorkingDirectory</key> <string>/Users/tauyoung/imageArchive</string> <key>StartCalendarInterval</key> <dict> <key>Hour</key> <integer>13</integer> <key>Minute</key> <integer>0</integer> </dict> <key>StandardOutPath</key> <string>/Users/tauyoung/Library/Logs/top.tauyoung.imagearchive.log</string> <key>StandardErrorPath</key> <string>/Users/tauyoung/Library/Logs/top.tauyoung.imagearchive.err</string> </dict> </plist>
|
<key>Hour</key><integer>13</integer>表示在本地时间的每日 13:00 触发执行。
- launchd 中必须全部使用绝对路径,不能使用
~缩写。
加载定时任务:
1
| launchctl load ~/Library/LaunchAgents/top.tauyoung.imagearchive.plist
|
GitHub Actions 云端任务(推荐)
云端执行不受本机休眠状态影响,更重要的是 GitHub Actions 自带海外网络环境,完美绕过了拉取其他国家/地区图片时的网络限制。
在仓库的根目录下创建.github/workflows/github-fetch.yml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| name: Fetch
on: schedule: - cron: '0 5 * * *'
permissions: contents: write
jobs: fetch: runs-on: macos-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Configure Git run: | git config user.name <user.name> git config user.email <user.email> - name: Fetch run: ./fetch.zsh
|
保存并推送后,GitHub 就会在每天按时为你打工收图了。一切就是这么简单!