专栏名称: OSC开源社区
OSChina 开源中国 官方微信账号
目录
相关文章推荐
OSC开源社区  ·  动态链接的魔法:Linux下动态链接库机制探讨 ·  昨天  
OSC开源社区  ·  阿里最受欢迎Java开源项目——Nacos开 ... ·  4 天前  
码农翻身  ·  这一次,编程的门槛彻底被打破了! ·  5 天前  
程序员的那些事  ·  C++ 的两个派系之争 ·  6 天前  
51好读  ›  专栏  ›  OSC开源社区

用 python 找出那些被“标记”的照片

OSC开源社区  · 公众号  · 程序员  · 2017-04-23 08:27

正文

#点击图片报名上海源创会#


想必大家对照片这个东西再熟悉不过,但我不知道在座的各位是否知道相片的EXIF信息。简单得说它就是每一张图片的"指纹信息",然后这边写了个小工具,或许社工的时候可以用到。


源码传送门 >>

 https://git.oschina.net/aoii103/exif-finder


环境准备

下面的两个第三方模块都可以直接通过pip快速安装,这里使用py36作为运行环境。

● python3.6

● requests

● exifread


思路

1. 遍历目录

2. 拉取数据集合

3. 遍历集合取得exif

4. exif信息整理,并获取实体地址

5. 拷贝文件到结果样本目录

6. 生成json报告文件


基础知识

下面是现今相片中会存在与GPS相关的关键字,大牛亦可一比带过~

{

    "GPSVersionID": "GPS版本",

    "GPSLatitudeRef": "南北纬",

    "GPSLatitude": "纬度",

    "GPSLongitudeRef": "东西经",

    "GPSLongitude": "经度",

    "GPSAltitudeRef": "海拔参照值",

    "GPSAltitude": "海拔",

    "GPSTimeStamp": "GPS时间戳",

    "GPSSatellites": "测量的卫星",

    "GPSStatus": "接收器状态",

    "GPSMeasureMode": "测量模式",

    "GPSDOP": "测量精度",

    "GPSSpeedRef": "速度单位",

    "GPSSpeed": "GPS接收器速度",

    "GPSTrackRef": "移动方位参照",

    "GPSTrack": "移动方位",

    "GPSImgDirectionRef": "图像方位参照",

    "GPSImgDirection": "图像方位",

    "GPSMapDatum": "地理测量资料",

    "GPSDestLatitudeRef": "目标纬度参照",

    "GPSDestLatitude": "目标纬度",

    "GPSDestLongitudeRef": "目标经度参照",

    "GPSDestLongitude": "目标经度",

    "GPSDestBearingRef": "目标方位参照",

    "GPSDestBearing": "目标方位",

    "GPSDestDistanceRef": "目标距离参照",

    "GPSDestDistance": "目标距离",

    "GPSProcessingMethod": "GPS处理方法名",

    "GPSAreaInformation": "GPS区功能变数名",

    "GPSDateStamp": "GPS日期",

    "GPSDifferential": "GPS修正"

}


初始化

考虑到exifread的模块中有大量的logging输出,这里将它的level级别调到最高。 然后下边的KEY是某站在高德地图API的时候遗留下来的 我也很尴尬。。就当福利了

import os

import time

import json

import random

import logging

import requests

import exifread


logging.basicConfig(level=logging.CRITICAL)

KEY = "169d2dd7829fe45690fabec812d05bc3"


主逻辑函数

def main():

    # 预设后缀列表

    types = ["bmp", "jpg", "tiff", "gif", "png"]


    #结果数据集合

    picex = []


    # 文件存储路径

    saves = "$" + input("| SavePath: ").strip()


    # 文件搜索路径 并遍历所有文件返回文件路径列表

    pools = jpgwalk(input("| FindPath: "), types)


    #存储目录

    savep = "%s/%s" % (os.getcwd().replace("\\", "/"), saves)

    if savep in pools:

        pools.remove(savep)


    # 遍历数据集并获取exif信息

    for path in pools:

        res = getEXIF(path)

        if res:

            picex.append(res)


    # 结果报告

    print("| Result %s" % len(picex))


    # 如果存在结果 保存结果到json并讲相关图片复制到该目录下

    if picex:


        #创建目录

        if not os.path.exists(saves):

            os.mkdir(saves)


        #生成一个4格缩进的json文件        

        with open("%s/%s.json" % (saves, saves), "wb") as f:

            f.write(json.dumps(picex, ensure_ascii=False, indent=4).encode("utf8"))


        #copy图像到该目录

        for item in picex:

            source_path = item["Filename"]

            with open("%s/%s" % (saves, source_path.split("/")[-1]), "wb") as f_in:

                with open(source_path, "rb") as f_out:

                    f_in.write(f_out.read())


遍历方法

遍历指定及其所有下级目录,并返回全部的图片的路径集合,这里要注意的是每次扫描后的拷贝行为都会生成缓存,所以通过指定 $ 来避开。

def main():

    # 预设后缀列表

    types = ["bmp", "jpg", "tiff", "gif", "png"]


    #结果数据集合

    picex = []


    # 文件存储路径

    saves = "$" + input("| SavePath: ").strip()


    # 文件搜索路径 并遍历所有文件返回文件路径列表

    pools = jpgwalk(input("| FindPath: "), types)


    #存储目录

    savep = "%s/%s" % (os.getcwd().replace("\\", "/"), saves)

    if savep in pools:

        pools.remove(savep)


    # 遍历数据集并获取exif信息

    for path in pools:

        res = getEXIF(path)

        if res:

            picex.append(res)


    # 结果报告

    print("| Result %s" % len(picex))


    # 如果存在结果 保存结果到json并讲相关图片复制到该目录下

    if picex:


        #创建目录

        if not os.path.exists(saves):

            os.mkdir(saves)


        #生成一个4格缩进的json文件        

        with open("%s/%s.json" % (saves, saves), "wb") as f:

            f.write(json.dumps(picex, ensure_ascii=False, indent=4).encode("utf8"))


        #copy图像到该目录

        for item in picex:

            source_path = item["Filename"]

            with open("%s/%s" % (saves, source_path.split("/")[-1]), "wb") as f_in:

                with open(source_path, "rb") as f_out:

                    f_in.write(f_out.read())


经纬度格式化

度分秒转浮点,方便api调用查询,因为存在一些诡异的数据比如 1/0,所以默认返回0

def cg(i):

    try:

        _ii = [float(eval(x)) for x in i[1:][:-1].split(', ')]

        _res = _ii[0] + _ii[1] / 60 + _ii[2] / 3600

        return _res

    except ZeroDivisionError:

        return 0


EXIF信息整理

考虑到大部分的设备还未开始支持朝向、速度、测量依据等关键字,这里暂时只使用比较常见的,如有需要的朋友可以自行添加。毕竟得到的信息越多对社工有更大的帮助。

def getEXIF(filepath):


    #基础关键字

    _showlist = [

        'GPS GPSDOP',

        'GPS GPSMeasureMode',

        'GPS GPSAltitudeRef',

        'GPS GPSAltitude',

        'Image Software',

        'Image Model',

        'Image Make'

    ]


    #GPS关键字

    _XYlist = ["GPS GPSLatitude", "GPS GPSLongitude"]


    #时间关键字

    _TimeList = ["EXIF DateTimeOrigina", "Image DateTime", "GPS GPSDate"]


    #初始化结果字典

    _infos = {

        'Filename': filepath

    }

    with open(filepath, "rb") as _files:

        _tags = None


        # 尝试去的EXIF信息

        try:

            _tags = exifread.process_file(_files)

        except KeyError:

            return


        # 判断是否存在地理位置信息

        _tagkeys = _tags.keys()

        if _tags and len(set(_tagkeys) & set(_XYlist)) == 2 and cg(str(_tags["GPS GPSLongitude"])) != 0.0:

            for _item in sorted(_tagkeys):

                if _item in _showlist:

                    _infos[_item.split()[-1]] = str(_tags[_item]).strip()

            # 经纬度取值

            _infos["GPS"] = (cg(str(_tags["GPS GPSLatitude"])) * float(1.0 if str(_tags.get("GPS GPSLatitudeRef", "N")) == "N" else -1.0),

                             cg(str(_tags["GPS GPSLongitude"])) * float(1.0 if str(_tags.get("GPS GPSLongitudeRef", "E")) == "E" else -1.0))


            # 获取实体地址

            _infos["address"] = address(_infos["GPS"])


            # 获取照片海拔高度

            if "GPS GPSAltitudeRef" in _tagkeys:

                try:

                    _infos["GPSAltitude"] = eval(_infos["GPSAltitude"])

                except ZeroDivisionError:

                    _infos["GPSAltitude"] = 0

                _infos["GPSAltitude"] = "距%s%.2f米" % ("地面" if int(

                    _infos["GPSAltitudeRef"]) == 1 else "海平面", _infos["GPSAltitude"])

                del _infos["GPSAltitudeRef"]


            # 获取可用时间

            _timeitem = list(set(_TimeList) & set(_tagkeys))

            if _timeitem:

                _infos["Dates"] = str(_tags[_timeitem[0]])

            return _infos


地址转换

一个简单的爬虫,调用高德地图api进行坐标转换,考虑到原本是跨域,这里添加基础的反防爬代码。这里有个小细节,海外的一律都取不到(包括台湾),可以通过更换googlemap的api来实现全球查询。

def address(gps):

    global KEY

    try:


        # 随机UA

        _ulist = [

            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1",

            "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0",

            "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; InfoPath.2; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; 360SE)",

            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",

            "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",

            "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; Tablet PC 2.0; .NET4.0E)",

            "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)",

            "Mozilla/5.0 (X11; U; Linux i686; rv:1.7.3) Gecko/20040913 Firefox/0.10",

            "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; ja) Presto/2.10.289 Version/12.00",

            "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36"

        ]


        # 伪造header

        _header = {

            "User-Agent": random.choice(_ulist),

            "Accept": "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01",

            "Accept-Encoding": "gzip, deflate, sdch",

            "Accept-Language": "zh-CN,zh;q=0.8",

            "Referer": "http://www.gpsspg.com",

        }

        _res = requests.get(

            "http://restapi.amap.com/v3/geocode/regeo?key={2}&s=rsv3&location={1},{0}&platform=JS&logversion=2.0&sdkversion=1.3&appname=http%3A%2F%2Fwww.gpsspg.com%2Fiframe%2Fmaps%2Famap_161128.htm%3Fmapi%3D3&csid=945C5A2C-E67F-4362-B881-9608D9BC9913".format(gps[0], gps[1], KEY), headers=_header, timeout=(5, 5))

        _json = _res.json()


        # 判断是否取得数据

        if _json and _json["status"] == "1" and _json["info"] == "OK":


            # 返回对应地址

            return _json.get("regeocode").get("formatted_address")

    except Exception as e:

        pass


实例

运行该代码 然后输入保存文件夹名和扫描位置即可


这边可以看到8019张中有396张存在有效的地理位置,打码的地方就不解释了,各位老司机~后期打算加入图像识别,和相似度识别。




推荐阅读

实用贴 | 适用于开发者的最佳火狐插件

12 个学习新的编程语言的方法

MyBatis 思维导图,让 MyBatis 不再难懂(一)

Java 的泛型擦除和运行时泛型信息获取

软件的复杂性:命名的艺术

点击“阅读原文”查看更多精彩内容