去年帮一个做二手交易的朋友排查过一件事:他挂在平台的商品图,被人扒出了拍摄地的大致位置,精确到小区。他一口咬定自己没写地址。后来我把那几张原图拖进工具一看——GPS 经纬度、拍摄时间、连手机型号都老老实实躺在文件里。他自己一点都不知道。
这就是 EXIF。这篇把它讲透:EXIF 里到底存了什么、怎么读出来、发图前怎么清掉,以及一个很多人忽略的诚实边界——清了 EXIF 不等于你就匿名了。
EXIF 是什么,为什么它会跟着照片跑
EXIF(Exchangeable Image File Format)是相机和手机在拍照那一刻写进图片文件里的一段元数据。它藏在 JPEG/TIFF 文件的头部,肉眼看图看不出来,但用任何能读元数据的程序都能翻出来。HEIC、部分 PNG、WebP 也可能带类似信息。
关键点在于:它是拍摄设备自动写的,不是你手动加的。 所以绝大多数人根本不知道自己发出去的图带着这些。你把原图直接甩到聊天、论坛、二手平台、简历附件里,这段数据就一起过去了。
常见 EXIF 字段与它对应的隐私风险
下面这张表是我平时排查时最关注的几类字段:
| EXIF 字段 | 存的是什么 | 隐私风险 |
|---|---|---|
| GPSLatitude / GPSLongitude | 拍摄点经纬度 | 直接暴露家/公司/常去地点,可反查地址 |
| DateTimeOriginal | 拍摄的年月日时分秒 | 暴露作息、行踪时间线 |
| Make / Model | 手机或相机品牌型号 | 设备指纹,可用于关联多张图是同一人拍的 |
| LensModel / Software | 镜头、修图软件版本 | 辅助设备指纹、判断处理链路 |
| SerialNumber | 部分相机的机身序列号 | 唯一标识,跨图强关联到同一台设备 |
| ImageDescription / Artist / Copyright | 作者、版权、描述 | 可能带真名或 ID |
| Orientation / 缩略图 | 方向、内嵌小图 | 缩略图有时保留了原始未裁剪画面(历史上出过事故) |
其中 GPS 和时间是最要命的两个。GPS 直接给坐标,时间给行为规律。两者一凑,一个人的生活轨迹就出来了。回到开头那个二手交易的例子:对方并不需要什么黑客手段,只是把几张商品图的 GPS 坐标提出来标到地图上,几张图坐标聚集的地方,八成就是卖家住处。再配上拍摄时间,连对方大概几点在家都能推。设备型号则起「串联」作用——哪怕换了账号发图,只要机型、序列号一致,就能判断是同一个人。单看一个字段可能不起眼,怕的是它们凑在一起互相印证。
补一句格式上的坑:现在 iPhone 默认拍 HEIC,安卓不少机型也在推自家格式,这些容器同样带 GPS 和时间,别以为「不是 JPEG 就没 EXIF」。转格式、发图前的清理逻辑对它们一样要走一遍。
怎么读取 EXIF
命令行:exiftool
排查单张或批量,exiftool 是最顺手的。它几乎认所有格式:
# 看全部元数据
exiftool photo.jpg
# 只看 GPS
exiftool -gps:all photo.jpg
# 批量扫一个目录,只列出带 GPS 的文件
exiftool -if '$gpslatitude' -filename -gpsposition -r ./images
Python:Pillow / piexif
要在代码里处理,用 Pillow 读,piexif 做精细操作:
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS
def read_exif(path):
img = Image.open(path)
raw = img._getexif() or {}
result = {}
for tag_id, value in raw.items():
tag = TAGS.get(tag_id, tag_id)
if tag == "GPSInfo":
value = {GPSTAGS.get(k, k): v for k, v in value.items()}
result[tag] = value
return result
data = read_exif("photo.jpg")
print(data.get("Make"), data.get("Model"))
print(data.get("DateTimeOriginal"))
print(data.get("GPSInfo"))
GPS 存的是「度分秒 + 参考方向」,要自己换算成十进制经纬度:
def to_decimal(dms, ref):
d, m, s = [float(x) for x in dms]
val = d + m / 60 + s / 3600
return -val if ref in ("S", "W") else val
发图前怎么清干净
清 EXIF 的思路分两种:精确删字段 和 重编码整张图。
方案一:exiftool 一把梭
# 删掉全部元数据(会生成 photo.jpg_original 备份)
exiftool -all= photo.jpg
# 只删 GPS,其它保留(比如想留拍摄时间)
exiftool -gps:all= photo.jpg
# 批量清目录,覆盖原文件不留备份
exiftool -all= -overwrite_original -r ./images
方案二:piexif 精细操作
想在 Python 里保留图像质量、只剥元数据:
import piexif
piexif.remove("photo.jpg") # 原地删除 EXIF 段
方案三:前端 Canvas 重绘
Web 上传场景,最省事的是让浏览器把图重画一遍。Canvas 的 drawImage 只搬像素,不搬元数据,出来的图天然干净:
function stripExif(file) {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
canvas.getContext("2d").drawImage(img, 0, 0);
canvas.toBlob(resolve, "image/jpeg", 0.92);
};
img.src = URL.createObjectURL(file);
});
}
注意 Canvas 重绘会重新有损压缩,画质有轻微损失;而且旧版本某些浏览器不读 Orientation,重绘后图可能被转向,需要先按 EXIF 方向摆正再画。
方案四:后端上传时统一 strip
我个人更倾向把「清元数据」这件事放到服务端兜底,不指望每个客户端都做对。Pillow 重存一遍是最简单的做法——Image.save() 默认不带原 EXIF:
from PIL import Image, ImageOps
def sanitize(src, dst):
img = Image.open(src)
img = ImageOps.exif_transpose(img) # 先去年帮一个做二手交易的朋友排查过一件事:他挂在平台的商品图,被人扒出了拍摄地的大致位置,精确到小区。他一口咬定自己没写地址。后来我把那几张原图拖进工具一看——GPS 经纬度、拍摄时间、连手机型号都老老实实躺在文件里。他自己一点都不知道。
这就是 EXIF。这篇把它讲透:EXIF 里到底存了什么、怎么读出来、发图前怎么清掉,以及一个很多人忽略的诚实边界——清了 EXIF 不等于你就匿名了。
## EXIF 是什么,为什么它会跟着照片跑
EXIF(Exchangeable Image File Format)是相机和手机在拍照那一刻写进图片文件里的一段元数据。它藏在 JPEG/TIFF 文件的头部,肉眼看图看不出来,但用任何能读元数据的程序都能翻出来。HEIC、部分 PNG、WebP 也可能带类似信息。
关键点在于:**它是拍摄设备自动写的,不是你手动加的。** 所以绝大多数人根本不知道自己发出去的图带着这些。你把原图直接甩到聊天、论坛、二手平台、简历附件里,这段数据就一起过去了。
## 常见 EXIF 字段与它对应的隐私风险
下面这张表是我平时排查时最关注的几类字段:
| EXIF 字段 | 存的是什么 | 隐私风险 |
|---|---|---|
| GPSLatitude / GPSLongitude | 拍摄点经纬度 | 直接暴露家/公司/常去地点,可反查地址 |
| DateTimeOriginal | 拍摄的年月日时分秒 | 暴露作息、行踪时间线 |
| Make / Model | 手机或相机品牌型号 | 设备指纹,可用于关联多张图是同一人拍的 |
| LensModel / Software | 镜头、修图软件版本 | 辅助设备指纹、判断处理链路 |
| SerialNumber | 部分相机的机身序列号 | 唯一标识,跨图强关联到同一台设备 |
| ImageDescription / Artist / Copyright | 作者、版权、描述 | 可能带真名或 ID |
| Orientation / 缩略图 | 方向、内嵌小图 | 缩略图有时保留了原始未裁剪画面(历史上出过事故) |
其中 GPS 和时间是最要命的两个。GPS 直接给坐标,时间给行为规律。两者一凑,一个人的生活轨迹就出来了。回到开头那个二手交易的例子:对方并不需要什么黑客手段,只是把几张商品图的 GPS 坐标提出来标到地图上,几张图坐标聚集的地方,八成就是卖家住处。再配上拍摄时间,连对方大概几点在家都能推。设备型号则起「串联」作用——哪怕换了账号发图,只要机型、序列号一致,就能判断是同一个人。单看一个字段可能不起眼,怕的是它们凑在一起互相印证。
补一句格式上的坑:现在 iPhone 默认拍 HEIC,安卓不少机型也在推自家格式,这些容器同样带 GPS 和时间,别以为「不是 JPEG 就没 EXIF」。转格式、发图前的清理逻辑对它们一样要走一遍。
## 怎么读取 EXIF
### 命令行:exiftool
排查单张或批量,`exiftool` 是最顺手的。它几乎认所有格式:
```bash
# 看全部元数据
exiftool photo.jpg
# 只看 GPS
exiftool -gps:all photo.jpg
# 批量扫一个目录,只列出带 GPS 的文件
exiftool -if '$gpslatitude' -filename -gpsposition -r ./images
Python:Pillow / piexif
要在代码里处理,用 Pillow 读,piexif 做精细操作:
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS
def read_exif(path):
img = Image.open(path)
raw = img._getexif() or {}
result = {}
for tag_id, value in raw.items():
tag = TAGS.get(tag_id, tag_id)
if tag == "GPSInfo":
value = {GPSTAGS.get(k, k): v for k, v in value.items()}
result[tag] = value
return result
data = read_exif("photo.jpg")
print(data.get("Make"), data.get("Model"))
print(data.get("DateTimeOriginal"))
print(data.get("GPSInfo"))
GPS 存的是「度分秒 + 参考方向」,要自己换算成十进制经纬度:
def to_decimal(dms, ref):
d, m, s = [float(x) for x in dms]
val = d + m / 60 + s / 3600
return -val if ref in ("S", "W") else val
发图前怎么清干净
清 EXIF 的思路分两种:精确删字段 和 重编码整张图。
方案一:exiftool 一把梭
# 删掉全部元数据(会生成 photo.jpg_original 备份)
exiftool -all= photo.jpg
# 只删 GPS,其它保留(比如想留拍摄时间)
exiftool -gps:all= photo.jpg
# 批量清目录,覆盖原文件不留备份
exiftool -all= -overwrite_original -r ./images
方案二:piexif 精细操作
想在 Python 里保留图像质量、只剥元数据:
import piexif
piexif.remove("photo.jpg") # 原地删除 EXIF 段
方案三:前端 Canvas 重绘
Web 上传场景,最省事的是让浏览器把图重画一遍。Canvas 的 drawImage 只搬像素,不搬元数据,出来的图天然干净:
function stripExif(file) {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
canvas.getContext("2d").drawImage(img, 0, 0);
canvas.toBlob(resolve, "image/jpeg", 0.92);
};
img.src = URL.createObjectURL(file);
});
}
注意 Canvas 重绘会重新有损压缩,画质有轻微损失;而且旧版本某些浏览器不读 Orientation,重绘后图可能被转向,需要先按 EXIF 方向摆正再画。
方案四:后端上传时统一 strip
我个人更倾向把「清元数据」这件事放到服务端兜底,不指望每个客户端都做对。Pillow 重存一遍是最简单的做法——Image.save() 默认不带原 EXIF:
from PIL import Image, ImageOps
def sanitize(src, dst):
img = Image.open(src)
img = ImageOps.exif_transpose(img) # 先按 EXIF 摆正方向
img.convert("RGB").save(dst, "JPEG", quality=92) # 重存,丢弃元数据
exif_transpose 这步不能省,否则用户竖着拍的图存完会躺下。
顺手盘一下常用的元数据/图片处理工具
按使用场景,我平时会用到这些:
- 命令行/批量:exiftool(读写元数据的瑞士军刀)、ImageMagick(
-strip一键去元数据) - 代码库:Python 的 Pillow、piexif、exifread
- 在线查看/处理:一些浏览器里就能跑的图片工具站,上传后能看元数据或做压缩清理,比如 squoosh、tinypng,以及像 tudingai.cn 这类在线修图站(换背景、放大之类的处理链路通常也会重编码,副作用是元数据一起被丢掉)
- 系统自带:macOS 预览「显示简介」、Windows「属性-详细信息」也能看和删部分字段
在线工具胜在不用装环境,但涉及隐私敏感的原图,能本地用 exiftool 处理就别上传——图交出去了,主动权就不完全在你了。
诚实的边界:清了 EXIF ≠ 匿名
这部分比前面的代码更重要,别被「我清了 EXIF」的安全感骗了。
-
截图基本不带 GPS/设备 EXIF,但会带别的。手机截图不是拍摄产物,通常没有相机 EXIF;可它可能暴露系统语言、状态栏时间、通知横幅里的人名、输入法候选词。信息泄露从来不只在 EXIF 里。
-
画面内容本身才是最大的泄露源。窗外的地标建筑、快递面单、门牌、车牌、工牌、屏幕反光——这些清元数据一个都动不了。真正防隐私要先看「画面里有什么」。
-
平台行为不可控。很多社交平台上传后会自动重编码、顺手抹掉 EXIF,但这是平台的实现细节,不是承诺,不同平台不一样,别把它当你的防线。
-
删了不一定删干净。有些工具只是把 EXIF 段标记为删除,或者留了内嵌缩略图、XMP/IPTC 等另一套元数据没清。想确认,清完再用 exiftool 回读一遍,看返回是不是真空了,这一步别省。
-
元数据也有正当用途。摄影作品的版权信息、专业工作流里的拍摄参数(光圈、快门、ISO),是要留的。「清元数据」不是无脑全删,而是按场景决定留什么、删什么:发给陌生人的、公开挂出去的,尽量清干净;自己归档、给协作方的,该留的参数留着。这个判断没法一刀切,得结合图的去向来定。
一句话收尾
EXIF 是那种「不知道就一直在裸奔,知道了三行代码就能治」的问题。给自己定个习惯:对外发的图,尤其是原图,过一遍 strip;敏感场景清完用 exiftool 回读验收。 同时记住它只是隐私链条的一环——画面里的内容、截图里的边角信息,往往比那串经纬度更容易出卖你。按 EXIF 摆正方向 img.convert("RGB").save(dst, "JPEG", quality=92) # 重存,丢弃元数据
`exif_transpose` 这步不能省,否则用户竖着拍的图存完会躺下。
## 顺手盘一下常用的元数据/图片处理工具
按使用场景,我平时会用到这些:
- **命令行/批量**:exiftool(读写元数据的瑞士军刀)、ImageMagick(`-strip` 一键去元数据)
- **代码库**:Python 的 Pillow、piexif、exifread
- **在线查看/处理**:一些浏览器里就能跑的图片工具站,上传后能看元数据或做压缩清理,比如 squoosh、tinypng,以及像 tudingai.cn 这类在线修图站(换背景、放大之类的处理链路通常也会重编码,副作用是元数据一起被丢掉)
- **系统自带**:macOS 预览「显示简介」、Windows「属性-详细信息」也能看和删部分字段
在线工具胜在不用装环境,但涉及隐私敏感的原图,能本地用 exiftool 处理就别上传——图交出去了,主动权就不完全在你了。
## 诚实的边界:清了 EXIF ≠ 匿名
这部分比前面的代码更重要,别被「我清了 EXIF」的安全感骗了。
1. **截图基本不带 GPS/设备 EXIF,但会带别的**。手机截图不是拍摄产物,通常没有相机 EXIF;可它可能暴露系统语言、状态栏时间、通知横幅里的人名、输入法候选词。信息泄露从来不只在 EXIF 里。
2. **画面内容本身才是最大的泄露源**。窗外的地标建筑、快递面单、门牌、车牌、工牌、屏幕反光——这些清元数据一个都动不了。真正防隐私要先看「画面里有什么」。
3. **平台行为不可控**。很多社交平台上传后会自动重编码、顺手抹掉 EXIF,但这是平台的实现细节,不是承诺,不同平台不一样,别把它当你的防线。
4. **删了不一定删干净**。有些工具只是把 EXIF 段标记为删除,或者留了内嵌缩略图、XMP/IPTC 等另一套元数据没清。想确认,清完再用 exiftool 回读一遍,看返回是不是真空了,这一步别省。
5. **元数据也有正当用途**。摄影作品的版权信息、专业工作流里的拍摄参数(光圈、快门、ISO),是要留的。「清元数据」不是无脑全删,而是按场景决定留什么、删什么:发给陌生人的、公开挂出去的,尽量清干净;自己归档、给协作方的,该留的参数留着。这个判断没法一刀切,得结合图的去向来定。
## 一句话收尾
EXIF 是那种「不知道就一直在裸奔,知道了三行代码就能治」的问题。给自己定个习惯:**对外发的图,尤其是原图,过一遍 strip;敏感场景清完用 exiftool 回读验收。** 同时记住它只是隐私链条的一环——画面里的内容、截图里的边角信息,往往比那串经纬度更容易出卖你。