客至汲泉烹茶, 抚琴听者知音

python+mongodb+php个人管理图片方案

前一段时间我搭建了自己的图床(见hidove图床搭建教程),利用它自带的api,我可以一次性批量上传好多图片。但是使用过程中,我也发现我一些需要的功能它没有:比如图片查重,批量删除,批量导出url链接等等,于是我决定自己写代码补充这些功能(当然不是改程序源码,毕竟我一点php基础都没有,更别说thinkphp了),主要使用的工具有python、mongodb,以及我临时入门的php。

[scode type="yellow"]作者是个编程小白,代码写得稀烂,而且大家用的图床程序和我的不一定一样,所以代码仅供参考[/scode]

图片查重

虽然查看数据库发现其实图片上传时程序已经计算了sha1和md5,理论上来说应该是能直接查重的,但可能有其他原因,总之该功能没有。那就自己写吧,我的想法是在本地就检测其sh1值,如果已经存在就不上传了,直接返回链接,这样能加快上传效率(小水管伤不起),也避免了后续复杂的查重操作。

在上传之前,建议先下一个Duplicate Cleaner(自行找破解版,本站不提供),它可以识别指定文件夹下的相似图片,并对相似的文件进行批量操作(比如每个相似组仅留一个最大的图片,其他的删除等等)。这样做的目的是:某些一样的图片,其中几张经过了修改(压缩等)那它的hash值就变了,也就是说它会被当成一个未重复的图片进行上传,但本质上它仍然是重复的,我们希望避免这种情况,所以先进行一波预查重。

接下来就是上传,本质上和sm上传图片那篇文章的代码是一样的,稍微修改一下就行。数据都存在MongoDB中。

from requests import post
from hashlib import sha1
import pymongo
import os
import random

def calchash(filepath):  # 计算图片hash值
    with open(filepath,'rb') as f:
        sha1obj = sha1()
        sha1obj.update(f.read())
        hash = sha1obj.hexdigest()
        return hash
        
def upload(file_path,file,mycol,pic_path,up_list):    
    if ('\u4e00' <= file <= '\u9fff') or (file in up_list): 
    #检查图片是否为中文名或是否该名已经存在于已经上传图片名列表中
        last = file[file.index(r'.'):]  # 后缀
        file = "".join(random.sample('zyxwvutsrqponmlkjihgfedcba0123456789',15))+last
        # 中文文件名变英文+数字
        while file in up_list:
             file = "".join(random.sample('zyxwvutsrqponmlkjihgfedcba0123456789',15))+last            
        new_file = os.path.join(pic_path,file) # 新文件名路径
        os.rename(file_path,new_file)  # 重命名本地文件
        file_path = new_file

    url='yourapiurl' #你的api上传链接
    files = {'image': open(file_path,'rb')} 
    header={"apitype":"this,ali,Jd","token":"yourtoken"}
    try:  
        pic_hash = calchash(file_path)
        # 先检查是否上传过,若无才进行上传
        if mycol.find_one({"hash": pic_hash}):
            print('查询mongodb,图片',file,'已存在','分发链接为',mycol.find_one({"hash": pic_hash})["url"]['distribute'])
        else:
            print(file,'不存在,正在上传……')
            data_result=post(url,files = files,data=header).json()
            if data_result["code"] == 200:
                mycol.insert_one({"hash":pic_hash,"name":file, "url":data_result["data"]["url"]}) 
                print(' '*4,file,'上传成功,链接为',data_result["data"]["url"]["distribute"])
    except FileNotFoundError:
        print(' '*4,file,'图片未找到,请检查该图是否存在')
    except ConnectionError:
        print("网络状况不佳,1分钟后再进行上传")

def main():
    pic_path = r'E:\壁纸\待上传壁纸'  # 要上传的文件夹
    up_list = os.listdir(r'E:\壁纸\已上传壁纸') # 已经上传的壁纸文件夹图片列表
    myclient = pymongo.MongoClient("mongodb://localhost:27017/")  # 连接数据库
    mydb = myclient["mdpicture"]   # 创建数据库
    mycol = mydb["acg"]   # 创建集合(数据表)
    for file in os.listdir(pic_path):
        file_path = os.path.join(pic_path, file)
        upload(file_path,file,mycol,pic_path,up_list)
        
if __name__ == '__main__':
    main()

需要注意的是,当文件名是中文的时候,上传会失败,干脆直接随机重命名一个,另外为了后期维护考虑,最好图片名也不要有重复的。

另外,我只写了两个错误情况,实际上还可能会出现图片过大而上传失败的问题,大家自行加个if条件即可。

批量移动图片

这一步只是在本地操作。想想一下这样一个情况:你的壁纸文件夹中有几千张图片,某天要把它们上传到图床上,上传了一半,突然遇到某个问题(断网等)上传失败了,那你就要重新上传。尽管有hash值检测,可以免去已上传图片重复上传,但是计算hash也要一定时间啊(尽管比较短),此时就需要另外一个函数:批量将已上传的图片移动到另外一个文件夹内。

import pymongo
import os,shutil

def move(pic_path,target_path):
    myclient = pymongo.MongoClient("mongodb://localhost:27017/")  # 连接数据库
    mydb = myclient["mdpicture"]   # 创建数据库
    mycol = mydb["acg"]     
    for file in os.listdir(pic_path):
        print('正在查找',file)
        if mycol.find_one({"name": file}):
            print(file,"已存在正在移动至",target_path)
            file_path = os.path.join(pic_path, file)            
            shutil.move(file_path,target_path+'\\'+file)
            print(file,"移动成功")

if __name__ == '__main__':
    pic_path = r'E:\壁纸\待上传壁纸' # 待上传壁纸文件夹
    target_path = r'E:\壁纸\已上传壁纸'  # 移动目的地
    move(pic_path,target_path)

这一步我们直接查询文件名是否存在于MongoDB中,速度快了很多(这就是上一步我建议文件名不重复的原因)

批量提取链接

分为两种情况吧:一种是无脑提取所有已上传的图片链接,另外一种是提取指定的文件链接。

提取所有已上传的图片链接

import pymongo

def mongo_ext(txtpath):
    myclient = pymongo.MongoClient("mongodb://localhost:27017/")  # 连接数据库
    mydb = myclient["mdpicture"]   # 创建数据库
    mycol = mydb["acg"] 
    with open(txtpath,'r+') as handler:
        handler.seek(0)
        handler.truncate()        
        for items in mycol.find():
            handler.write(items["url"]["distribute"]+'\n')

if __name__ == '__main__':
    txtpath = r'E:\壁纸\acg.txt' # txt文件路径

运行之后,所有MongoDB中的链接都被提取出来并写入至指定的txt文档内,一行一个。

提取指定的文件链接

首先你要对图片进行一番整理,如果你有图片管理软件(比如eagle),这一步应该很轻松。总之将你想要提取的图片都放到一个文件夹内,比如E:\壁纸\风景壁纸目录

import pymongo
from hashlib import sha1
import os

def pic_ext(txtpath,pic_path):
    myclient = pymongo.MongoClient("mongodb://localhost:27017/")  # 连接数据库
    mydb = myclient["mdpicture"]   # 创建数据库
    mycol = mydb["acg"]     
    with open(txtpath,'r+') as handler:
        handler.seek(0)
        handler.truncate()
        for file in os.listdir(pic_path):
            file_path = os.path.join(pic_path, file)
            try:
                data = mycol.find_one({"name": file}) # 直接查询文件名
                handler.write(str(data["url"]["distribute"]+'\n')) 
            except TypeError: # 若mongodb中不存在此文件名,则可能是经过了修改,所以查询hash值
                print(file,"没有找到,尝试查询hash值")
                pic_hash = calchash(file_path)
                data = mycol.find_one({"hash": pic_hash})
                new_name = os.path.join(pic_path,data["name"])
                print(file,"已找到,mongodb中名字为",data["name"])
                os.rename(file_path,new_name)
                handler.write(str(data["url"]["distribute"]+'\n'))

if __name__ == '__main__':
    txtpath = r'E:\壁纸\acg.txt' # txt文件路径
    pic_path = r'E:\壁纸\风景壁纸目录' # 需要提取的图片所在文件夹的路径
    pic_ext(txtpath,pic_path) #从文件夹中读取所有文件并寻找其url写入txt中

因为本地文件名可能经过了修改,所以查询的时候先查名字,再查hash值,并且将mongo与本地图片名不一致的情况更正过来。

批量删除

假如说有一天你发现有几张图怎么看怎么不顺眼,决定把它们删掉,想想要怎么做?

如果你是个强迫症患者,那么首先在MongoDB中删掉记录,然后去图床的图片管理中删掉这张图,几张还好,要是很多张……对不起打扰了。

不过我们仍然可以通过代码解决。首先把想要删除的图片移动到指定文件夹(即remove_path),然后删掉MongoDB中的记录

def mongo_remove(remove_path):
    myclient = pymongo.MongoClient("mongodb://localhost:27017/")  # 连接数据库
    mydb = myclient["mdpicture"]   # 创建数据库
    mycol = mydb["acg"] 
    for file in os.listdir(remove_path):
        print('正在查找',file)
        if mycol.find_one({"name": file}):
            mycol.delete_one({"name": file})
            print(file,"记录删除成功")

服务器上的删除分为两部分:数据库记录删除,服务器图片删除

我的图床用的是Mysql,直接本地连接进行操作即可,注意要开放数据库的访问权限。

那服务器上的图片呢?我尝试了大家都推荐的paramiko库,但是一直报错,算了,直接写个php批量删除代码放到服务器上吧。

因为Hidove图床会自动将上传的图片重命名(但是仍然会有原始图片名记录,可以用它来查询),所以远程连接Mysql的时候也要将图片地址保存下来,方便删除。

import pymysql
def sql_remove(remove_path):
    conn = pymysql.connect(host="ip",user="user",password="password",database="database",charset="utf8")
    cursor = conn.cursor() # 获取一个光标
    alist = []
    for file in os.listdir(remove_path):
        filename = '"'+file+'"'
        sql_f = 'SELECT * FROM  {0} WHERE filename={1}'.format("hipic_imageinfo", filename)
        sql_r = 'DELETE FROM  {0} WHERE filename={1}'.format("hipic_imageinfo", filename)
        try:   # 根据图片名查询mysql记录,并将相应的服务器地址存入alist中                        
            cursor.execute(sql_f)
            results = cursor.fetchall()
            for row in results:
                alist.append(row[4])
        except:
            print('Error')
            break
        try:  # 删除mysql记录
            cursor.execute(sql_r)
            conn.commit()
            print(file,"sql删除成功")
        except:
            print(file,"sql删除失败")
    with open(r'E:\壁纸\condition.txt','r+') as handler: 
    # 将mysql中获取到的图片地址(服务器地址)保存到txt文档中
        handler.seek(0)
        handler.truncate()        
        for path in alist:
            handler.write(path+'\n')

if __name__ == '__main__':
    remove_path = r'E:\壁纸\已上传又不想要' # 不想要的图片文件夹
    sql_remove(remove_path)

ok,sql记录已经删除,并且所有要删除的图片路径都已经存到condition.txt中了,接下来就是删除服务器上的图片。

在网站根目录(我的是/public)中新建一个delete.php的空白文档,写入以下代码:

<?php 
$path = [];
$fs = fopen("condition.txt", "r");
while(!feof($fs)){
    $line=trim(fgets($fs));
    if($line!=''){
        array_push($path, $line);
    }
}
function delete_file($array){
    if (empty($array)){
        echo "无符合删除条件的文件!";
    }
    
    foreach ($array as $file){
        if (is_file($file)=== true){
            if (!unlink($file)){
            echo ("$file 删除失败,<br>");
            }else{
            echo ("$file 删除成功,<br>");
            }
        }
        else{
            echo ("$file 未找到,<br>");
        }
    }
}

delete_file($path);

然后将condition.txt上传至网站根目录,访问yoururl/delete.php即可批量删除condition.txt中所有指定的图片。

这一步比较复杂,总的来说就是:先运行mongo_remove()sql_remove(),然后把自动生成的condition.txt上传至网站根目录,然后访问yoururl/delete.php

后记

从代码的编写上,你就能看出我是个小白了,包括php都是我今天刚学的(也只是粗略地看了一下大概语法,然后就照着别人的例子抄了一遍)。可能有更好的方案吧,但是我已经比较满意了。

添加新评论