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

【python】给收藏的二次元图片都找到来源

前言

之前我整理收藏的图片时,都是简单粗暴地直接全部重命名为hash值,然后配合mongodb管理网络链接(参见python+mongodb+php个人管理图片方案),后来也加上了机器学习给不少图片打上了标签(参见用DeepDanbooru为二次元图片打上标签并存入数据库中),并且搞出了一个随机图API(参见用fastapi搭建随机图api)。

但是最近参观了几个随机图API,发现有的返回值更加丰富,不仅有图床链接,而且还有作者ID、标题等。突然觉得有必要也整一个,要不然和转载别人文章不标明出处的行为有什么区别。所以就有了这一篇文章。

首先介绍一下思路,人工找图片来源的方法一般是借助搜索引擎(比如谷歌识图、百度识图),还有些专门针对二次元图片搜索的网站(比如ascii2dsaucenao)。那么只要我借助python批量上传检索,不就可以自动化找到来源了吗?

图片压缩

之所以要进行图片压缩,是为了减少图片上传的耗时,也避免给搜索引擎带来压力导致ip被封。我一开始想的就是直接用python库压缩,但是找了一下,貌似没有比较满意的解决方案。不是压缩效果不理想(Pillow模块),就是需要上传到云端压缩(TinyPNG),或者是压缩时间较长(squoosh)。

所以专业的事情交给专业的软件去做,直接请出大杀器Caesium(大家可以网上找找破解版),它压缩速度快而且效果明显(大小减少很多且清晰度没有明显变化)。7000张图片,大概等了十几分钟就压缩完毕,原本10个G的文件夹一下子就变成了2个G。

这里需要注意的是,压缩版的图片只是为了搜索,所以质量稍微损失一点是没关系的,一定注意不要覆盖掉原始文件夹,最好统一输出到另外一个文件夹。

图片搜索引擎

既然我需要找的都是二次元图片,那么ascii2dsaucenao显然要比谷歌百度要好,不仅是因为它们更精确,而且还会输出原始链接、标题、作者,其中saucenao甚至还会输出p站id。不用考虑,就它们了。

接下来的难题就是如何使用python上传了,我对爬虫这块不太熟,所以在网上找了一下看有没有别人写好的代码。没想到直接搜到了一个完整的轮子,PicImageSearch,它封装了几个搜索引擎,提供了统一风格的API。安装使用也比较简单,参见说明文档

安装:

pip install PicImageSearch -i https://pypi.tuna.tsinghua.edu.cn/simple

看了说明文档我才知道原来saucenao是有API的,不过免费账户有限制,30秒最多搜索5次,24小时最多搜索200次,免费是足够了,但对我7000+的图片来说,有点不够看。原本想的是几个搜索引擎轮番调用,然后多申请几个saucenao账号。但是想了想这又加大了工作量,而且saucenao确实也好用(上传速度快,搜索结果精确,而且返回值更多),所以干脆花钱买省心吧。

直接在这里升级,遗憾的是它只支持信用卡。我尝试给管理员发邮件提出paypal支付,回复说是可以的,于是我就有了个VIP账户。看了一下30秒内可以搜索25次,每天可以搜索5000次,完全满足需求。

数据保存

数据保存方案就选择sqlite了,反正都是结构化的数据。不过这里还需要再配合数据写一个判断条件,当前搜索的图片若已存在于数据库中,就跳过,防止重复搜索。

另外说一下我的图片文件结构。

项目根目录
----压缩后
----搜索完毕
----搜索失败
----缩略图

这里的缩略图也是从saucenao下的,这样方便我们人工核对一遍。

代码

from pathlib import Path

from PicImageSearch import SauceNAO
import sqlite3
import shutil
import time

program_dir = Path(r'D:\素材')

pic_dir = program_dir / '压缩后' # 待搜索的图片文件夹
suo_dir = program_dir / '缩略图' # 搜索完毕后缩略图存放地
SauceNAO_api = '这里填写你的API'
pic_path_list = pic_dir.iterdir()
db_path = program_dir / 'pic.db'
con = sqlite3.connect(db_path)
cur = con.cursor()
# 新建表acg,若已有该表则忽略,
cur.execute("CREATE TABLE IF NOT EXISTS acg(hash TEXT, title TEXT, author TEXT, url TEXT,pixiv_id INT,member_id INT)")
exists_list = cur.execute("SELECT hash FROM acg").fetchall()

exists_list = [x[0] for x in exists_list] # 已存在的hash表


def insert_db(res):
    cur.execute("INSERT INTO acg(hash,title,author,url,pixiv_id,member_id) VALUES(?,?,?,?,?,?)",res)

    con.commit()

def move_pic(pic_path, target_dir):
    target_path = pic_path.parent.parent / target_dir / pic_path.name
    shutil.move(pic_path, target_path)

def search_img(pic_path, source= 'saucenao'):
    flag = True
    pic_hash = pic_path.stem
    if pic_hash in exists_list:
        print(pic_hash,'已存在,跳过')
        return
    suo_path = suo_dir / pic_path.name # 缩略图
    if source == 'saucenao': # 每30秒25个,每天5000个(普通账户5/200)
        saucenao = SauceNAO(api_key=SauceNAO_api,numres = 1,minsim = 80,output_type = 2)
        res = saucenao.search(str(pic_path))
        if res.results_requested > 0:
            title = res.raw[0].title
            author = res.raw[0].author
            url = res.raw[0].url
            pixiv_id = res.raw[0].pixiv_id
            member_id = res.raw[0].member_id
            res.raw[0].download_thumbnail(filename=suo_path)
            result = (pic_hash, title, author, url, pixiv_id, member_id)
            insert_db(result)
            print('{}搜索成功,结果为:'.format(pic_hash), result)
        else:
            print('{}搜索失败'.format(source))
            flag = False
        print('30秒内剩余次数:{},24小时内剩余次数:{}'.format(res.short_remaining, res.long_remaining))
        if res.short_remaining == 0:
            print('一小时内无法使用saucenao')

        if res.long_remaining == 0:
            print('二十四小时内无法使用saucenao')
  
    if flag:
        move_pic(pic_path, '搜索完毕')

    else:
        move_pic(pic_path, '搜索失败')

for pic_path in pic_path_list:
    try:
        search_img(pic_path, source= 'saucenao')
    except:
        time.sleep(10)

效果:

改善计划

打算五一的时候写一个GUI界面,左边展示原图(压缩后),右边展示缩略图,确认搜索结果是正确的点个按钮就到下一页,结果不正确的图片就点个按钮,移到另外文件夹内,最后人工搜一遍。

另外很多推特图title是时间,作者没有。可以写个爬虫从推特上找到作者,title就命名为当时推文的内容好了。

添加新评论