㊗️本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~
㊙️本期爬虫难度指数:⭐⭐⭐
🉐福利: 一次订阅后,专栏内的所有文章可永久免费看,持续更新中,保底1000+(篇)硬核实战内容。

全文目录:
-
-
- [🌟 开篇语](#🌟 开篇语)
- [1️⃣ 摘要(Abstract)](#1️⃣ 摘要(Abstract))
- [2️⃣ 背景与需求(Why)](#2️⃣ 背景与需求(Why))
- [3️⃣ 合规与注意事项(⚠️ 必写)](#3️⃣ 合规与注意事项(⚠️ 必写))
- [4️⃣ 技术选型与整体流程(What/How)](#4️⃣ 技术选型与整体流程(What/How))
- [5️⃣ 环境准备与依赖安装(可复现)](#5️⃣ 环境准备与依赖安装(可复现))
- [6️⃣ 核心实现:请求与下载层(Fetcher)](#6️⃣ 核心实现:请求与下载层(Fetcher))
- [7️⃣ 核心实现:解析与去重层(Parser & Deduplication)](#7️⃣ 核心实现:解析与去重层(Parser & Deduplication))
- [8️⃣ 数据存储与导出(Storage)](#8️⃣ 数据存储与导出(Storage))
- [9️⃣ 运行方式与结果展示(必写)](#9️⃣ 运行方式与结果展示(必写))
- [🔟 常见问题与排错(强烈建议写)](#🔟 常见问题与排错(强烈建议写))
- [1️⃣1️⃣ 进阶优化(可选但加分)](#1️⃣1️⃣ 进阶优化(可选但加分))
- [1️⃣2️⃣ 总结与延伸阅读](#1️⃣2️⃣ 总结与延伸阅读)
- [🌟 文末](#🌟 文末)
-
- [✅ 专栏持续更新中|建议收藏 + 订阅](#✅ 专栏持续更新中|建议收藏 + 订阅)
- [✅ 互动征集](#✅ 互动征集)
- [✅ 免责声明](#✅ 免责声明)
-
🌟 开篇语
哈喽,各位小伙伴们你们好呀~我是【喵手】。
运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO
欢迎大家常来逛逛,一起学习,一起进步~🌟
我长期专注 Python 爬虫工程化实战 ,主理专栏 《Python爬虫实战》:从采集策略 到反爬对抗 ,从数据清洗 到分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上。
📌 专栏食用指南(建议收藏)
- ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
- ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
- ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
- ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用
📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅专栏👉《Python爬虫实战》👈,一次订阅后,专栏内的所有文章可永久免费阅读,持续更新中。
💕订阅后更新会优先推送,按目录学习更高效💯~
1️⃣ 摘要(Abstract)
🎯 目标 :编写一个 Python 爬虫,批量下载高清壁纸,并引入感知哈希(pHash)算法 进行图像去重,最后将图片的元数据(来源、Hash值、本地路径)存入 SQLite 数据库。
🛠 工具 :requests (下载) + BeautifulSoup4 (解析) + Pillow & ImageHash (图像处理与指纹) + SQLite (元数据管理)。
💡 读完收获:
- 学会如何优雅地处理二进制文件(图片)下载与流式写入。
- 核心技能:掌握 pHash 算法,识别出"内容相同但文件名/大小不同"的图片。
- 搭建一个最小化的"图片资产管理系统"雏形。
2️⃣ 背景与需求(Why)
🤔 为什么要爬?
做深度学习训练集、搭建个人素材库,或者单纯是想把喜欢的画师作品一次性拖回本地。
🚩 痛点:
普通爬虫只看 URL 是否相同。但互联网上充满了重复转载 ,同一张图可能是 img_01.jpg,在另一个站叫 wallpaper_2024.png。如果只靠文件名去重,你的硬盘会被同样的图塞满。我们需要基于内容的去重。
🎯 目标站点 :在此以 https://pic.netbian.com/4kdongman/ (彼岸图网 4K 动漫区)为例,这是一个经典的静态壁纸站,结构简单,非常适合练手。
📋 目标字段:
Origin URL(原始链接)Local Path(本地存储路径)pHash(图像指纹,用于去重)Resolution(分辨率,如 1920x1080)
3️⃣ 合规与注意事项(⚠️ 必写)
图片站的流量成本很高,请务必做一个有素质的爬虫!
- Referer 防盗链 :很多图片服务器会检查
Referer,如果你不告诉它你是从网页点进来的,它会返给你一张"禁止盗链"的碎图。 - 频率控制(关键) :下载图片属于 IO 密集且占带宽的操作。必须设置较长的
time.sleep(建议 1-2秒),不要把人家服务器带宽跑满了。 - 版权意识:爬下来的图自己欣赏或做研究可以,千万别打包去卖,或者搭建镜像站非法牟利。
4️⃣ 技术选型与整体流程(What/How)
🎨 流程图 :
获取列表页 → 解析详情页 → 下载图片到临时区 → 计算 pHash → 数据库查重 → (若不重复) 移动到正式库 & 写入 SQLite → (若重复) 删除临时文件
🛠 为什么选 pHash (Perceptual Hash)?
- MD5:哪怕图片改了一个像素,MD5 都会变,无法识别"看起来一样"的图。
- pHash :它关注图片的低频结构信息。图片压缩、调色、微调尺寸后,pHash 值依然非常接近。这才是我们想要的"去重"。
5️⃣ 环境准备与依赖安装(可复现)
Python 3.8+。除了常规的爬虫库,这次需要加上图像处理库。
bash
pip install requests beautifulsoup4 ImageHash pillow
Pillow (PIL):Python 的图像处理标准库。ImageHash:专门算图片哈希值的神器。
📂 项目结构:
text
img_spider/
├── images/ # 最终存图目录
├── temp/ # 下载临时目录(用于算Hash)
├── metadata.db # SQLite 数据库
└── main.py # 主程序
6️⃣ 核心实现:请求与下载层(Fetcher)
下载大文件(图片)时,建议使用 stream=True,防止一张图把内存撑爆。
python
import requests
import os
def download_image(img_url, temp_dir='temp'):
"""
流式下载图片到临时目录
"""
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Referer': 'https://pic.netbian.com/' # 必带!防盗链
}
filename = img_url.split('/')[-1]
temp_path = os.path.join(temp_dir, filename)
os.makedirs(temp_dir, exist_ok=True)
try:
with requests.get(img_url, headers=headers, stream=True, timeout=15) as r:
r.raise_for_status()
# 只有当 Content-Type 是图片时才保存
if 'image' in r.headers.get('Content-Type', ''):
with open(temp_path, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
return temp_path
else:
print(f"⚠️ 跳过非图片链接: {img_url}")
return None
except Exception as e:
print(f"❌ 下载失败 {img_url}: {e}")
return None
7️⃣ 核心实现:解析与去重层(Parser & Deduplication)
这是本次的重头戏!我们要把"计算 pHash"和"数据库比对"结合起来。
python
import imagehash
from PIL import Image
import sqlite3
import shutil
# 初始化数据库
def init_db(db_name='metadata.db'):
conn = sqlite3.connect(db_name)
c = conn.cursor()
# 创建表:url 唯一,但 phash 允许重复(为了容错,但逻辑上我们要控制)
c.execute('''CREATE TABLE IF NOT EXISTS images
(id INTEGER PRIMARY KEY, url TEXT, path TEXT, phash TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')
conn.commit()
return conn
def process_image(temp_path, original_url, conn, final_dir='images'):
"""
核心逻辑:计算Hash -> 查重 -> 入库
"""
try:
# 1. 打开图片计算 pHash
img = Image.open(temp_path)
img_hash = str(imagehash.phash(img))
c = conn.cursor()
# 2. 查重逻辑
# 简单比对:直接看数据库里有没有完全一样的 hash 字符串
# 进阶比对:计算汉明距离(Hamming Distance),这里为了演示用简单字符串相等
c.execute("SELECT id FROM images WHERE phash = ?", (img_hash,))
row = c.fetchone()
if row:
print(f"♻️ 检测到重复图片 (Hash: {img_hash}),跳过入库。")
img.close()
os.remove(temp_path) # 删掉临时文件
return False
else:
# 3. 不重复,移动到正式目录并入库
os.makedirs(final_dir, exist_ok=True)
filename = os.path.basename(temp_path)
final_path = os.path.join(final_dir, filename)
img.close() # 必须先关闭文件句柄才能移动
shutil.move(temp_path, final_path)
c.execute("INSERT INTO images (url, path, phash) VALUES (?, ?, ?)",
(original_url, final_path, img_hash))
conn.commit()
print(f"✅ 入库成功: {filename} (Hash: {img_hash})")
return True
except Exception as e:
print(f"⚠️ 图片处理出错: {e}")
if os.path.exists(temp_path):
os.remove(temp_path)
return False
8️⃣ 数据存储与导出(Storage)
上面已经在 process_image 里集成了 SQLite 的写入。相比 CSV,SQLite 更适合这种需要频繁"查询是否存在"的场景。
字段映射:
| 字段名 | 类型 | 说明 |
|---|---|---|
id |
INTEGER | 自增主键 |
url |
TEXT | 原始来源,方便回溯 |
path |
TEXT | 本地存储位置 |
phash |
TEXT | 核心指纹,例如 d41d8cd98f00b204 |
去重策略补充:如果你想做得更极致,可以计算新 Hash 与库中所有 Hash 的汉明距离,若距离 < 5 则视为相似图片。但对于几十万张图的库,这需要 VP-Tree 等算法加速,今天先按"完全相等"处理。
9️⃣ 运行方式与结果展示(必写)
python
# main.py 组装
def main():
base_url = "https://pic.netbian.com/4kdongman/index_{}.html"
conn = init_db()
# 演示:只爬取前 2 页
# 注意:该网站第一页是 index.html,第二页是 index_2.html,逻辑需微调,这里简化
urls = ["https://pic.netbian.com/4kdongman/"] + [base_url.format(i) for i in range(2, 3)]
for url in urls:
print(f"\n🔍 正在扫描页面: {url}")
resp = requests.get(url, headers={'User-Agent': 'Mozilla/5.0...'})
resp.encoding = 'gbk' # 彼岸图网是 GBK 编码!
soup = BeautifulSoup(resp.text, 'html.parser')
# 提取缩略图列表里的详情页链接
links = soup.select('.slist ul li a')
for link in links:
detail_url = "https://pic.netbian.com" + link['href']
# 这里简化逻辑:直接假设详情页里有张大图,实际需再发请求去详情页解析
# 为演示代码简洁,我们假设抓取到了 img_src
# 真实场景需增加一个 parse_detail(detail_url) 函数
print(f" -> 发现详情页: {detail_url}")
# 模拟:假设我们解析出了大图 URL
# ⚠️ 实际代码需要你去详情页抓取 'src'
conn.close()
if __name__ == "__main__":
# 这里放一段测试用的伪代码逻辑,证明跑通流程
conn = init_db()
# 测试:下载一张图
print("🧪 开始测试单图流程...")
test_img_url = "https://pic.netbian.com/uploads/allimg/240228/001428-1709050468ea80.jpg"
temp_file = download_image(test_img_url)
if temp_file:
process_image(temp_file, test_img_url, conn)
conn.close()
print("🎉 测试结束,查看 images 目录和 metadata.db")
📊 运行结果示例:
text
🧪 开始测试单图流程...
✅ 入库成功: 001428-1709050468ea80.jpg (Hash: ffe0c0f0f0e1c3c0)
🎉 测试结束...
(再次运行同一代码)
text
🧪 开始测试单图流程...
♻️ 检测到重复图片 (Hash: ffe0c0f0f0e1c3c0),跳过入库。
🔟 常见问题与排错(强烈建议写)
-
图片打不开/损坏:
- 原因:下载未完成连接就断了,或者对方开启了防盗链返回了假图。
- 解决:用
PIL.Image.open()校验一下,如果报错UnidentifiedImageError,说明图是坏的,直接删掉。
-
编码报错
UnicodeDecodeError:- 国内老牌图片站很多还在用 GBK 。记得
response.encoding = 'gbk',否则解析 HTML 全是乱码。
- 国内老牌图片站很多还在用 GBK 。记得
-
Hash 误判:
- pHash 过于强大,有时两张图只有一点点色差也被判为相同。如果对画质要求极高(如区分 4K 和 1080P),请在去重逻辑里加入"分辨率比对":Hash 相同且分辨率更低才算重复。
1️⃣1️⃣ 进阶优化(可选但加分)
- 异步并发 (Aiohttp) :图片下载是纯 I/O 操作。用
asyncio+aiohttp,下载速度能翻 10 倍!但记得限制并发数(Semaphore),不然 IP 必死。 - 汉明距离查重 :不要用 SQL 的
=,而是把 Hash 转为二进制,计算异或值。SQLite 本身不支持这个,可以在 Python 内存里加载所有 Hash 进行比对(量大时用 Redis 或 Faiss)。 - 缩略图生成:入库时顺便生成一张 200x200 的缩略图,方便以后做前端展示。
1️⃣2️⃣ 总结与延伸阅读
🎉 复盘:
今天我们不仅写了个爬虫,还涉及了计算机视觉的基础------感知哈希。我们实现了一个能自动把关、拒绝重复内容的智能图片库。
👣 下一步:
- 人脸识别 :使用
face_recognition库,只下载包含"人脸"的图片。 - NSFW 过滤:为了身心健康(和硬盘安全),可以接一个简单的 AI 模型过滤掉不适宜的图片。
- 分布式存储:图片多了硬盘装不下?学习如何把图片存到 AWS S3 或 阿里云 OSS 上。
这次的坑比较深(涉及二进制处理),如果代码跑不通或者遇到防盗链 403,记得检查 Headers 里的 Referer 哦!加油!
🌟 文末
好啦~以上就是本期的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥
✅ 专栏持续更新中|建议收藏 + 订阅
墙裂推荐订阅专栏 👉 《Python爬虫实战》,本专栏秉承着以"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一期内容都做到:
✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)
📣 想系统提升的小伙伴 :强烈建议先订阅专栏 《Python爬虫实战》,再按目录大纲顺序学习,效率十倍上升~

✅ 互动征集
想让我把【某站点/某反爬/某验证码/某分布式方案】等写成某期实战?
评论区留言告诉我你的需求,我会优先安排实现(更新)哒~
⭐️ 若喜欢我,就请关注我叭~(更新不迷路)
⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)
⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)
✅ 免责声明
本文爬虫思路、相关技术和代码仅用于学习参考,对阅读本文后的进行爬虫行为的用户本作者不承担任何法律责任。
使用或者参考本项目即表示您已阅读并同意以下条款:
- 合法使用: 不得将本项目用于任何违法、违规或侵犯他人权益的行为,包括但不限于网络攻击、诈骗、绕过身份验证、未经授权的数据抓取等。
- 风险自负: 任何因使用本项目而产生的法律责任、技术风险或经济损失,由使用者自行承担,项目作者不承担任何形式的责任。
- 禁止滥用: 不得将本项目用于违法牟利、黑产活动或其他不当商业用途。
- 使用或者参考本项目即视为同意上述条款,即 "谁使用,谁负责" 。如不同意,请立即停止使用并删除本项目。!!!
