多线程爬取 unsplash 图库

题图:by nasa from Instagram

我公众号文章的封面配图都在 Unsplash 上找的。因为 Unsplash 是一个完全免费的、无版权的高清图片资源网站。

所谓的「无版权」是指这个网站上的图片由创作者自愿分享出来,完全免费提供给任何人作为任何用途使用。Unsplash 的原话是「do whatever you want」,进一步说明是「你可以免费对图片进行复制、修改、分发,包括用作商业目的,无需经过允许即可使用」。

自己发现之前在寻找图片上还是挺花费时间的。先在 Unsplash 上浏览图片,当发现觉得还不错的图片就会下载下来。另外,下载图片还需要自己点击下载按钮。这确实挺花费时间。现在自己学会了网络爬虫,是时候改善下情况。

分析

Unsplash 网站采用瀑布流样式来呈现图片。首页以开始只会呈现一部分图片,当我们滑动滚动条到底部时,网页才会继续加载部分图片。这网站经常使用 Ajax 技术来加载图片。针对动态渲染网页,我会选择 Selenium 来爬取。但是,我这次为了追求高效率下载图片,势必要使用多线程。因此,只能放弃使用 Selenium,转而通过抓包方式来分析网站。

我使用浏览器的开发者工具来查看网络请求。

点击查看大图查看首页的数据包,只能得到知首页是经过重定向的信息。

接着, 自己满怀期待查看 main.js 文件。因为名字的原因,所以自己怀疑这个 js 文件的作用是发起请求网络。自己只要在代码中搜索下 http 字样,说不定还有意外的收获。

点击查看大图结果希望又落空。只能接着分析。

经过一番漫长的分析之后,最后发现两个很有价值的信息。

点击查看大图

点击查看大图

这个 url 地址十有八九是图片的请求地址。

自己使用浏览器访问这个地址,证实自己的猜想是正确的。

点击查看大图

根据英语单词,可以推断出各个参数的意思。page 表示页数, 从前面的信息得知目前一共有 71131 个页面;per_page 表示每页拉去的图片数, order_by 表示按时间从现在到以前的顺序来拉取图片。

我自己使用 Requests 库编写一段简单的请求代码。目的是验证网站是否有反爬虫机制,结果发现没有。

点击查看大图

爬取思路

因为多线程需要考虑线程安全的问题,所以我决定使用 Queue 队列模块来存储所有的的 url 地址。Queue 模块中提供了同步的、线程安全的队列类,其中就有 FIFO(先入先出)队列 Queue。Queue 内部实现了锁原语,帮我们实现加锁和释放锁的操作。因此,我们能够在多线程中直接使用。

最终的思路是: 1) 计算出所有图片的 url 地址,然后使用 Queue 存储起来 2) 创建并启动多个线程,然后每个线程要完成以下工作:使用 requests 库请求 url 地址、使用 JSON 库解析的 JSON 形式的响应体,获取图片的下载地址、使用 urllib 库下载图片到本地。

代码实现

先定义一些常量, 方便后续操作。

# 使用队列保存存放图片 url 地址, 确保线程同步
url_queue = Queue()
# 线程总数
THREAD_SUM = 5
# 存储图片的位置
IMAGE_SRC = 'D://Unsplash/'

计算出所有的 url 地址, 然后存放到 url_queue 队列中。

def get_all_url():
    """ 循环计算出所有的 url 地址, 存放到队列中 """
    base_url = 'https://unsplash.com/napi/photos?page={}&per_page=1&order_by=latest'
    page = 1
    max_page = 71131
    while page <= max_page:
        url = base_url.format(page)
        url_queue.put(url)
        page += 1
    print('计划下载', url_queue.qsize(), '张图片')

创建多个线程,然后逐个启动。

if __name__ == '__main__':
    get_all_url()
    for i in range(THREAD_SUM):
        unsplash = Unsplash(i+1)
        unsplash.start()

原生的 Thread 类无法满足场景的需求。我新建一个名为 Unsplash 的类,该类继承threading.Thread 来自定义线程类,接着重写 run 方法。

class Unsplash(threading.Thread):

    NOT_EXIST = 0

    def __init__(self, thread_id):
        threading.Thread.__init__(self)
        self.thread_id = thread_id

    def run(self):
        while not self.NOT_EXIST:
            # 队列为空, 结束线程
            if url_queue.empty():
                NOT_EXIST = 1
                break

            url = url_queue.get()
            self.get_data(url)
            time.sleep(random.randint(3, 5))

    def get_data(self, url):
        """ 根据 url 获取 JSON 格式的图片数据"""
        headers = {
            'User-agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 UBrowser/6.2.3964.2 Safari/537.36',
            'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
            'referer': 'https://unsplash.com/',
            'path': url.split('com')[1],
            'authority': 'unsplash.com',
            'viewport-width': '1920',
        }
        response = requests.get(url, headers=headers)
        print('请求第[ ' + url + ' ], 状态码为 ', response.status_code)
        self.get_image_url(response.text)

    def get_image_url(self, response):
        """
        使用 json.loads(response) 将其转化为字典类型, 以便采用 key-value 形式获取值
        raw:包含Exif信息的全尺寸原图,此类图片的容量很大
        full:全尺寸分辨率的图片,去除了Exif信息并且对内容进行了压缩,图片容量适中
        normal:普通尺寸的图片,去除了Exif信息,并且对分辨率和内容进行了压缩,图片容量较小;
        """
        image_url = json.loads(response)[0]['urls']['full']
        self.save_img(image_url)

    def save_img(self, image_url):
        print('线程', self.thread_id, ' | 正在下载', image_url)
        try:
            if not os.path.exists(IMAGE_SRC):
                os.mkdir(IMAGE_SRC)
            filename = IMAGE_SRC + image_url.split('com')[1].split('?')[0] + '.jpg'
            # 下载图片,并保存到文件夹中
            urllib.request.urlretrieve(image_url, filename=filename)
        except IOError as e:
            print('保存图片出现异常失败', e)

爬取结果

按照计划,执行完一次程序代码。硬盘的 D 盘目录中会有一个 Unsplash 的文件夹,里面会有 71131 张图片。我是将爬虫程序运行在云主机上,所以就只显示本地爬取的 100 多张图片。

原文发布于微信公众号 - 极客猴(Geek_monkey)

原文发表时间:2018-06-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏互联网杂技

如何学习用Typescript写Reactjs?

首先扫盲一下,先从搭建环境开始: 1.安装node,因为ts的编译器是js/ts写的; 安装node后同时获得npm命令,这是nodejs世界里的包管理器...

57012
来自专栏社区的朋友们

fetch api 浅谈

作为传说中的xhr替代品,现在fetch api已经被开始在一些前端项目中使用了,比如阿里的一些产品已经将jq的ajax模块切换到fetch下了。个人感觉fet...

6340
来自专栏前端萌媛的成长之路

spa

2235
来自专栏布尔

富文本编辑器的一键排版功能

在做CMS系统的时候,用户常常会从word粘贴一些东西到编辑器中,早起的富文本编辑器也都提供了去除word格式的功能(尽管有时候比较难用),甚至有时候用户要求打...

48010
来自专栏向治洪

ECMAScript 6 入门简介

ECMAScript 6.0(以下简称ES6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了。它的目标,是使得JavaScript语言...

1947
来自专栏Seebug漏洞平台

TCTF/0CTF2018 XSS Writeup

刚刚4月过去的TCTF/0CTF2018一如既往的给了我们惊喜,其中最大的惊喜莫过于多道xss中Bypass CSP的题目,其中有很多应用于现代网站的防御思路。...

1173
来自专栏生信宝典

别人的电子书,你的电子书,都在bookdown

bookdown是著名R包作者谢益辉开发的,支持采用Rmarkdown (R代码可以运行)或普通markdown编写文档,然后编译成HTML, WORD, PD...

46111
来自专栏晨星先生的自留地

从协议提取到多功能RDP识别脚本

2798
来自专栏Jack-Cui

Python3网络爬虫快速入门实战解析

Python版本: Python3.x 运行平台: Windows IDE: Sublime text3 一 前言 强烈建议:请在电脑的陪同下,阅读...

7958
来自专栏前端安全

和XSS漏洞对抗的日子

前端安全日益受到业内的关注,最近笔者和团队在和XSS漏洞对抗的这段时间,总结了部分常见的漏洞和修复方法,下面将结合具体业务对这些漏洞类型进行分析。

37015

扫码关注云+社区