🔹 引言

壁纸网站上有很多精美的高清图,但如果一个个手动保存,既耗时又麻烦。


今天我们用 Python 写一个完整的爬虫脚本,批量下载 城市分类下的高清壁纸,优先获取最大分辨率(8K/5K/4K),并自动保存到本地。

目标站点: https://backiee.com/categories/city

🔹 技术栈

  • Python 3.9+

  • requests:网络请求

  • BeautifulSoup4:HTML 解析

  • re:正则解析 ID 和分辨率

  • os / time:文件操作 & 下载限速

🔹 爬虫逻辑设计

  1. 获取总页数

    • 解析分页信息 "Page 1 of 20" → 得到总页数

  2. 获取详情页链接

    • 遍历分页,提取所有壁纸详情页 URL

  3. 解析原图地址

    • <img src/srcset><a href> 找到最大分辨率

    • 若未找到 → 根据常见分辨率(8K/5K/4K…)+ 壁纸 ID 猜测原图直链

  4. 下载图片

    • 自动重命名:分辨率-文件名.jpg

    • 已存在则跳过,避免重复

  5. 限速保护

    • 每次请求之间 time.sleep(0.1),避免触发反爬

import os
import re
import time
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin

BASE_URL = "https://backiee.com/categories/city"
SITE = "https://backiee.com"
SAVE_DIR = "city_full"
os.makedirs(SAVE_DIR, exist_ok=True)

HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/115.0 Safari/537.36"
    ),
    "Accept-Language": "en-US,en;q=0.9",
}

# 常见大尺寸(从大到小)
COMMON_RES = [
    "7680x4320",  # 8K
    "5120x2880",  # 5K
    "3840x2160",  # 4K
    "3440x1440",
    "2560x1600",
    "2560x1440",
    "2560x1080",
    "1920x1200",
    "1920x1080",
]

# 已知的缩略尺寸目录(全部剔除)
THUMB_RES = {"560x315", "336x189", "456x257"}

session = requests.Session()
session.headers.update(HEADERS)
session.timeout = 15

def get_total_pages():
    r = session.get(BASE_URL)
    r.raise_for_status()
    soup = BeautifulSoup(r.text, "html.parser")
    pager_text = soup.select_one(".pager-text").get_text(strip=True)
    total = int(re.search(r"of (\d+)", pager_text).group(1))
    return total

def get_detail_links(page: int):
    url = f"{BASE_URL}?page={page}"
    r = session.get(url)
    r.raise_for_status()
    soup = BeautifulSoup(r.text, "html.parser")
    links = []
    for a in soup.select(".col-sm-4.col-md-4 a[href*='/wallpaper/']"):
        href = a.get("href")
        if not href:
            continue
        links.append(href if href.startswith("http") else urljoin(SITE, href))
    return sorted(set(links))

def parse_res_from_url(url: str):
    # 解析 /static/wallpapers/{WxH}/{id}.jpg 里的 WxH
    m = re.search(r"/static/wallpapers/(\d+x\d+)/", url)
    if not m:
        return None, 0
    res = m.group(1)
    try:
        w, h = res.split("x")
        area = int(w) * int(h)
    except Exception:
        return res, 0
    return res, area

def is_valid_wallpaper_url(url: str):
    if "/static/wallpapers/" not in url:
        return False
    res, _ = parse_res_from_url(url)
    if not res:
        return False
    if res in THUMB_RES:
        return False
    if "placeholder" in url:
        return False
    return True

def best_from_detail_html(html: str):
    soup = BeautifulSoup(html, "html.parser")
    candidates = set()

    # 1) img 的 src / srcset
    for img in soup.select("img"):
        src = img.get("src") or ""
        srcset = img.get("srcset") or ""
        if src:
            candidates.add(src)
        if srcset:
            for part in srcset.split(","):
                u = part.strip().split(" ")[0]
                if u:
                    candidates.add(u)

    # 2) a 标签可能直接给下载链接
    for a in soup.select("a[href]"):
        href = a.get("href") or ""
        if href:
            candidates.add(href)

    # 归一化、过滤缩略图和无关资源
    normalized = []
    for u in candidates:
        if not u.startswith("http"):
            u = urljoin(SITE, u)
        if is_valid_wallpaper_url(u):
            res, area = parse_res_from_url(u)
            normalized.append((area, res, u))

    if not normalized:
        return None

    # 按分辨率面积从大到小选最大那张
    normalized.sort(reverse=True, key=lambda x: x[0])
    return normalized[0][2]

def probe_full_by_id(detail_url: str):
    """
    如果页面没给原图直链,就用 id 猜测常见分辨率直链并探测。
    观察到缩略图文件名经常是 id +/- 100000 的变体,所以一并尝试。
    """
    m = re.search(r"/wallpaper/[^/]+/(\d+)", detail_url)
    if not m:
        return None
    base_id = int(m.group(1))
    id_candidates = [base_id, base_id + 100000, base_id + 200000]

    for res in COMMON_RES:
        for fid in id_candidates:
            test_url = f"{SITE}/static/wallpapers/{res}/{fid}.jpg"
            try:
                # 用 GET 探测(有些站点对 HEAD 处理不一致)
                resp = session.get(test_url, stream=True, timeout=10)
                if resp.status_code == 200 and "image" in resp.headers.get("Content-Type", ""):
                    return test_url
            except requests.RequestException:
                pass
            finally:
                try:
                    resp.close()
                except Exception:
                    pass
    return None

def get_full_image(detail_url: str):
    # 先用详情页 HTML 解析最大分辨率
    r = session.get(detail_url)
    r.raise_for_status()

    # 优先从 HTML 提取分辨率最大的 /static/wallpapers/{WxH}/...jpg
    best = best_from_detail_html(r.text)
    if best:
        return best

    # 退而求其次:按常见分辨率 + id 探测直链
    fallback = probe_full_by_id(detail_url)
    return fallback

def safe_name(url: str):
    clean_url = url.split("?")[0]
    basename = os.path.basename(clean_url)
    res, _ = parse_res_from_url(url)
    prefix = f"{res}-" if res else ""
    name = prefix + basename
    # 清理非法字符
    name = re.sub(r'[\\/*?:"<>|]', "_", name)
    return name

def download_image(url: str):
    name = safe_name(url)
    path = os.path.join(SAVE_DIR, name)
    if os.path.exists(path):
        print(f"⏩ 已存在: {name}")
        return
    try:
        with session.get(url, stream=True) as resp:
            resp.raise_for_status()
            with open(path, "wb") as f:
                for chunk in resp.iter_content(chunk_size=1024 * 64):
                    if chunk:
                        f.write(chunk)
        print(f"✅ 下载成功: {name}")
    except Exception as e:
        print(f"❌ 下载失败: {url} 错误: {e}")

def main():
    total_pages = get_total_pages()
    print(f"📄 共 {total_pages} 页")
    # 先小范围测试
    for p in range(1, total_pages + 1):
        print(f"=== 正在处理第 {p} 页 ===")
        links = get_detail_links(p)
        print(f"🔍 找到 {len(links)} 个详情页链接")
        for link in links:
            full = get_full_image(link)
            if full:
                download_image(full)
            else:
                print(f"⚠️ 未找到原图: {link}")
            time.sleep(0.1)  # 轻微限速,避免过于频繁
    print("完成前2页测试。确认无误后,把 range(4, 10) 改为 range(1, total_pages + 1) 跑全量。")

if __name__ == "__main__":
    main()
# 🏙️ Backiee 壁纸爬虫

批量爬取 [Backiee](https://backiee.com) 网站的高清壁纸。  
支持 **城市分类**(默认),可下载 **8K/5K/4K 壁纸**,自动选择最大分辨率并保存到本地。

---

## ⚙️ 功能特性

- ✅ 自动获取所有分页的壁纸详情页
- ✅ 解析详情页 HTML,提取最大分辨率原图
- ✅ 如果未找到直链,则尝试常见分辨率进行探测
- ✅ 自动重命名:`分辨率-文件名.jpg`
- ✅ 已下载的文件自动跳过(断点续传)
- ✅ 简单限速,防止过快触发反爬

---

## 📖 使用说明书

### 1. 环境准备

确保你已安装 Python 3.9+,并安装依赖库:

```bash
pip install requests beautifulsoup4

2. 使用方法

  1. 将爬虫代码保存为 backiee_spider.py

  2. 在命令行运行:

 
python backiee_spider.py
  1. 程序会自动:

    • 访问 Backiee 城市分类

    • 遍历所有分页,解析详情页

    • 下载最大分辨率壁纸

    • 保存到 city_full 文件夹


3. 参数说明

  • BASE_URL
    壁纸分类页面,例如:

     
    BASE_URL = "https://backiee.com/categories/city"

    如果要下载自然风景分类,可以改为:

     
    BASE_URL = "https://backiee.com/categories/nature"
  • SAVE_DIR
    壁纸保存目录,例如:

     
    SAVE_DIR = "city_full"
  • COMMON_RES
    常见大分辨率,按顺序优先探测(8K → 5K → 4K → …)


4. 注意事项

  • 脚本会自动跳过已下载的文件,支持断点续传

  • 默认限速 0.1s,避免过快触发反爬

  • 若网络不稳定,可能提示 ❌ 下载失败,不会影响继续运行

  • 请遵守目标网站的 Robots 协议 与使用规范


5. 常见问题

  • Q: 下载中断后能继续吗?
    A: 可以,已存在的文件会自动跳过。

  • Q: 想下载其他分类怎么办?
    A: 修改 BASE_URL 为对应分类地址,修改 SAVE_DIR 为保存目录。

💻 运行效果示例

执行后控制台输出:

 
📄 共 20 页 === 正在处理第 1 页 ===
🔍 找到 30 个详情页链接
✅ 下载成功: 7680x4320-123456.jpg
⏩ 已存在: 3840x2160-789012.jpg
⚠️ 未找到原图: https://backiee.com/wallpaper/xxxx

最终壁纸会保存在:

city_full/
├─ 7680x4320-123456.jpg
├─ 5120x2880-234567.jpg
 └─ 3840x2160-345678.jpg
 

⚠️ 免责声明

本工具仅供学习与研究 Python 爬虫技术使用。
请勿用于商业或非法用途。下载的图片版权归 Backiee 及原作者所有。