本篇仅供学习,请自觉遵守我国法律法规。科学上网,从我做起。

偶然发现,有一个可以返回随机涩图的API https://api.lolicon.app/#/setu,不仅可以涩涩,还涉及了比较有意思的网络知识。这就又能水一篇博客了。 本篇有涉及 json 格式的文本,但没有对 json 作详细介绍,有兴趣的可以了解一下,不知道也不影响阅读。

什么是API

首先要知道什么是API,我从维基百科里找了一段解释:

应用程序接口(英語:application programming interface),缩写为API,是一种计算接口,它定义多个软件中介之间的交互,以及可以进行的调用(call)或请求(request)的种类,如何进行调用或发出请求,应使用的数据格式,应遵循的惯例等。

简单的来说,API 就是一种看不见的工具,它会告诉你怎么去使用这个 API,以及使用 API 后会得到什么样的结果,而我们并不知道其内部是怎么实现的。就好比我们都知道按下电饭煲的按钮就能煮好一锅饭,而不需要知道电饭煲内部的电路结构、电阻电压之类的具体实现。于是,对于一个 API 来说,我们只关心两个东西:用法和结果。API 可以分很多大类,我们将要用到的属于 Web API。

可以试着选择下面的一个链接,复制粘贴到浏览器上,并进行访问,看看会的到什么结果。然后重复操作,重新粘贴再访问(无重定向的情况可以直接刷新),看看得到的结果有什么不同。
https://v1.hitokoto.cn/
https://api.9jojo.cn/hitokoto
https://api.9jojo.cn/acgpic
https://api.9jojo.cn/acgmp3

这里以 https://v1.hitokoto.cn/ 为例,我们用浏览器访问这个链接之后,得到了如下所示的内容:

不难看出,这是一段符合 JSON 语法的文本,将其展开如下:

{
    "id":476,
    "uuid":"7601bbef-a8a5-4d04-929e-05d07d0e793d",
    "hitokoto":"君生我未生,我生君已老。 君恨我生迟,我恨君生早。",
    "type":"g",
    "from":"全唐诗续拾",
    "from_who":null,
    "creator":"Sai",
    "creator_uid":0,
    "reviewer":0,
    "commit_from":"web",
    "created_at":"1468950842",
    "length":25
}

https://v1.hitokoto.cn/一言 的 API 接口,随便百度搜索“一言”就能搜到,这是一个可以返回随机句子的 API,前面说过,我们只关心 API 的用法和结果,那么用浏览器访问这个链接就是一种用法,所返回的 json 对象就是结果,其中包括了句子的内容、长度、来源和其他多种属性。再具体一点,用浏览器访问可以看作是一个 get 请求,那么这就可以运用到我们的程序设计语言中,比如 python,我们只需向这个链接发送一个 get 请求,然后将返回的 json 对象进行解析,就能在程序运行时得到随机句子。

用 python 发 get 请求并解析 json 对象

我们要使用的随机涩图 API 的接口链接是 https://api.lolicon.app/setu/v2,跟前面一样,直接用浏览器访问就能看到效果,返回的也是一个 json 对象,我们先用 python 进行第一步处理:

import requests
response = requests.get("https://api.lolicon.app/setu/v2")
datas = response.json()
print(datas)

在上述代码中,我们使用了 python 的 requests 库,如果没有的话可以使用命令行 pip install requests 进行安装。库如其名,这个库的主要功能就是发送 HTTP 请求和接收其响应,requests.get()函数传入一个链接,响起发送一个 get 请求,然后返回一个 response 对象。我们用浏览器访问时已经知道返回的是一个 json 格式的文本,于是可以调用 json() 函数将其解析,该函数返回一个 python 字典,可以直接用 print() 看到其内容。

这里就不得不吐槽 python 的机制了,字典里可以套列表,列表里面又能套字典,print 又不分行,里面那么多个东西,一眼看过去是很难知道怎么用下标访问的,可以使用数括号的方法,也可以格式化上图中的字典:

{
    'error': '',
    'data': [{
        'pid': 85163311,
        'p': 0,
        'uid': 9009855,
        'title': '爽',
        'author': 'Xo',
        'r18': False,
        'width': 3507, 
        'height': 2480, 
        'tags': ['明日方舟', 'Arknights', 'W', '裸足', '赤脚', '足指', '脚指', 'ペディキュア', '美甲(脚趾)', '美脚', '美腿', 'アークナイツ', 'ギリシャ型', 'Greek foot', 'マニキュア', '美甲'], 
        'ext': 'jpg', 
        'uploadDate': 1603324391000, 
        'urls': {
            'original': 'https://i.pixiv.cat/img-original/img/2020/10/22/08/53/11/85163311_p0.jpg'
        }
    }]
}

可以看到,API 返回的字典中有很多的信息,基本都是键值对的形式,请重点关注其中的urls。我们可以在 API 的官网 https://api.lolicon.app/#/setu 找到各属性的说明,截取其中部分如下图所示。

在这么多属性中,最重要的一个是 urls 中的图片地址,也就是上面的 https://i.pixiv.cat/img-original/img/2020/10/22/08/53/11/85163311_p0.jpg,它的 .jpg 后缀十分清晰地告诉我们,这个链接指向了一个图片文件,那么它就是我们所要找的随机涩图。根据python语法,我们可以用字典的["data"][0]["urls"]["original"]下标来访问链接。

import requests
response = requests.get("https://api.lolicon.app/setu/v2")
datas = response.json()
print(datas["data"][0]["urls"]["original"])

但是,事情还没有这么快结束,假如我们在中国大陆内,在浏览器中输入上述的图片链接,会发现无法访问。我们需要把链接中的主域名 i.pixiv.cat 换成 i.pixiv.re,才能访问。这就涉及到了代理和反向代理的知识。

正向代理和反向代理

代理可以理解为经纪人,我们以客户端-服务端的结构来理解正向代理和反向代理。当没有代理的情况下,我们访问一个链接时,就是作为客户端的身份向服务端发送请求。

对于正向代理,我们考虑一种情况:疫情封校了,同学 A 在校内,而奶茶店在校外,于是同学 A 无法出去买奶茶,但是同学 A 叫了校外的一个路人 B 去买奶茶,领到之后将奶茶通过围栏交到同学 A 手上,于是同学 A 就成功取到了奶茶。在这个过程中,路人 B 就扮演正向代理的角色,对于买奶茶这个行为,路人 B 代替同学 A 做这个事,而奶茶店老板不知道真正买奶茶的人是谁,这点非常关键。

我们常说的代理也就是指正向代理,正向代理的过程,它隐藏了真实的请求客户端,服务端不知道真实的客户端是谁,客户端请求的服务都被代理服务器代替来请求,某些科学上网工具扮演的就是典型的正向代理角色。用浏览器访问 https://www.google.com/ 时,被残忍的block,于是我们可以在国外或香港搭建一台代理服务器,让代理帮我们去请求 google.com,代理把请求返回的相应结构再返回给我们。

对于反向代理:大家都有过这样的经历,拨打10086客服电话,可能一个地区的10086客服有几个或者几十个,你永远都不需要关心在电话那头的是哪一个,叫什么,男的,还是女的,漂亮的还是帅气的,你都不关心,你关心的是你的问题能不能得到专业的解答,你只需要拨通了10086的总机号码,电话那头总会有人会回答你,只是有时慢有时快而已。那么这里的10086总机号码就是我们说的反向代理。客户不知道真正提供服务人的是谁。

反向代理隐藏了真实的服务端,当我们请求 www.baidu.com 的时候,就像拨打10086一样,背后可能有成千上万台服务器为我们服务,但具体是哪一台,你不知道,也不需要知道,你只需要知道反向代理服务器是谁就好了,www.baidu.com 就是我们的反向代理服务器,反向代理服务器会帮我们把请求转发到真实的服务器那里去。Nginx就是性能非常好的反向代理服务器,用来做负载均衡。

用一句话总结就是,正向代理隐藏真实客户端,反向代理隐藏真实服务端

来点涩图!

那代理跟我们的涩图有什么关系呢?有兴趣的小伙伴可以试着搜索一下GFW,维基百科上有解释,简单地说就是,在中国境内,某些国外网站是会被屏蔽的,俗称被“墙”,也就是说,在大陆内无法直接访问某些国外网站,必须要通过代理(正向和反向都可以)。通常会使用香港或台湾上的服务器作代理服务器,让它充当一个中间人的角色,从而实现科学上网。

从随机涩图 API https://api.lolicon.app/#/setu 的官网描述中可知,图片来源于 pixiv 插图网,其图库的官方域名为 i.pximg.net ,该网站含有大量不健康的图片,理所当然的被墙,所以要寻找 i.pximg.net 的代理。使用科学上网访问 https://pixiv.cat/ 可以发现两个反向代理,一个是 i.pixiv.cat,一个是 i.pixiv.re,前者在中国大陆被墙(是的,代理也是能被墙的),后者在大陆内还能用。这也就解释了为什么我们之前访问 https://i.pixiv.cat/img-original/img/2020/10/22/08/53/11/85163311_p0.jpg 不成功,我们需要将其中的 i.pixiv.cat 替换成 i.pixiv.re,就能成功访问。

想用 python 下载网站图片文件的方式有很多,可以自己尝试搜索一下,包括但不限于使用 requestsurllibwgetaiohttp 等库,以及不同的方式使用(比如显示进度条)。在本篇前面我使用了 requests 库,这里也不打算引入新库了,使用 get 函数配合一般的 python 文件读写操作也能做到下载文件,就像下面的代码一样:

import requests
response = requests.get("https://api.lolicon.app/setu/v2")
datas = response.json()

img_url = datas["data"][0]["urls"]["original"]
img_url = img_url.replace('i.pixiv.cat', 'i.pixiv.re')
print(img_url)

img = requests.get(img_url, stream = True, timeout = 10)
with open("1.jpg", "wb") as f:
    f.write(img.content)

我把代码分成了三部分:访问 API 获取图片属性→使用反向代理(替换图片链接url的主域名)→下载图片保存到文件中,在第二次 get 函数的使用中,我添加了 stream 参数和 timeout 参数,它们是跟网站速度、防卡死有关的,这里不细说。后面就是简单地把图片保存到 1.jpg 中。运行上面的代码就能在当前目录的 1.jpg 中看到涩图啦!

更好的涩图!

这部分只是单纯地修改 HTTP 请求的参数而已,从随机涩图 API https://api.lolicon.app/#/setu 的官网描述中可知,我们可以调整参数来限制随机涩图的范围,包括但不限于作者、图片标签、图片规格、时间等等,这个属于网络的基础知识,直接看如何在 python 中运用,如下。

import requests

params = {
    "r18" : 0,
    "tag" : ["崩坏3|原神"],
    "dateAfter" : 1600000000000 - 200 * 86400000,#一天是86,400,000毫秒,一年是31,536,000,000毫秒
}
response = requests.get("https://api.lolicon.app/setu/v2", params=params)
datas = response.json()

img_url = datas["data"][0]["urls"]["original"]
img_url = img_url.replace('i.pixiv.cat', 'i.pixiv.re')
print(img_url)

img = requests.get(img_url, stream = True, timeout = 10)
with open("1.jpg", "wb") as f:
    f.write(img.content)

可以看到,我们可以自行定义一个字典,将字典传入第一次 get 函数,就能影响返回的随机涩图。

更多的涩图!

当我们已经完全掌握如何生成一张自己喜欢的涩图之后,我们就可以运用自己的 python 技巧,通过循环来使程序运行之后不断生成涩图,然后再加入一点合理的人机交互,好让朋友学会使用你的程序。

import requests
import os

r18 = input("0为非 R18,1为 R18,2为混合,请输入 0 或 1 或 2: ")
while r18 not in ["0","1","2"]:
    print("\n非法输入,请重新输入")
    r18 = input("0为非 R18,1为 R18,2为混合,请输入 0 或 1 或 2: ")
r18 = eval(r18)

download_num = eval(input("请输入需要获取的图片数,输入-1则无限下载,期间可按下Ctrl+C或关闭窗口以停止: "))

print("\nr18:", r18)

tag = "崩坏3|原神"
print("tag:",tag)

params = {
    "r18" : r18,
    "tag" : [tag],
    "dateAfter" : 1600000000000 - 200 * 86400000,#一天是86,400,000毫秒,一年是31,536,000,000毫秒
    # "proxy" : 0
}

dirs = os.listdir("./") #获取当前目录下所有目录和文件名
img_i = 1

while download_num != 0:
    download_num -= 1
    while str(img_i) + ".jpg" in dirs or str(img_i) + ".png" in dirs:
        img_i += 1

    print("\nget url from api...")
    response = requests.get("https://api.lolicon.app/setu/v2", params=params)
    datas = response.json()
    # print(response.url)
    # print(datas["data"])
    img_url = datas["data"][0]["urls"]["original"]
    print("image-url:", img_url)
    # img_url = img_url.replace('i.pixiv.cat', 'i.pximg.net')
    img_url = img_url.replace('i.pixiv.cat', 'i.pixiv.re') #i.pixiv.cat在中国被墙了
    # print(img_url)
    print("using reverse proxy, downloading...")


    img_response = requests.get(img_url, timeout = 10)
    img_format =  img_url[-3:]
    img_format = "jpg" #这一行固定生成jpg图片,可以注释掉这一行,就会实际调整jpg或png
    with open(str(img_i)+"."+img_format, "wb") as f:
        f.write(img_response.content)

    print("已将图片保存至当前目录,命名为", str(img_i)+"."+img_format,",  r18?",datas["data"][0]["r18"])
    img_i += 1

os.system("pause")

使用pyinstall打包python程序

记得本篇的标题吗,给朋友做一个随机涩图程序,这个朋友的电脑上可能没有安装 python,它也许不懂任何的计算机知识,只知道双击打开文件,那么我们如何跟朋友一起涩涩呢。这里介绍一个很有趣的工具—— pyinstall,使用它可以将 python 程序生成可直接运行的 .exe 程序,这个程序就可以被分发到其他人的电脑上独立运行。

python 默认并不包含 pyinstaller 模块,因此需要自行安装 pyinstaller 模块。安装 pyinstaller 模块与安装其他 python 模块一样,使用 pip 命令安装即可。在命令行输入如下命令:

pip  install  pyinstaller

假设我的 python 代码保存在 setu.py 中,那么使用 pyinstaller 生成对应的可执行文件的命令行如下,记得要替换自己的代码文件名

pyinstaller  -F  --distpath  .  setu.py

pyinstaller 的具体使用可以上网搜索。执行完上述命令行后,在当前目录下会生成一个跟代码文件名字相同的可执行文件 setu.exe,以及一个 build 目录和一个 .spec 文件,除了可执行文件外的都是可以删除的,它们属于中间文件,删除后并不影响 .exe 的正常运行。此时,双击 .exe 可执行文件,就能运行啦,效果跟原代码文件一模一样。

需要注意的是,pyinstaller 虽然在多平台上都可以使用,但是它不能生成跨平台的可执行文件,也就是说,在 windows 上使用 pyinstaller 打包的文件只能在 windows 上执行,同理,在 macOS 上使用 pyinstaller 打包的文件只能在 macOS 上执行。

结语

i.pixiv.re 这个反向代理,虽然在中国大陆内可用,但是它有点不稳定,有时会变得很卡,于是下载不了图片,遇到这种情况等一会重试就好了。

最后奉劝一句,小涩怡情,大涩伤身,希望各位以学习为重,祝各位身体健康,万事如意。