技术好文共享:老司机教你下载tumblr上视频和图片的正确姿势

简介: 技术好文共享:老司机教你下载tumblr上视频和图片的正确姿势

本文面向初学者。


很多同学问我:“我非常想学Python编程,但是找不到兴趣点”。 还有的同学呢,找到了很好的兴趣点,但是无从下手,“玄魂老师,我想下载tumblr上的视频, 怎么下载,Python能实现吗?你懂得(这里有一个淫笑的表情)”。


好吧,我表示对他所要表达的意思秒懂了,宅男都喜欢看别人开车。今天本人姑且装一把老司机, 带大家来分析下如何下载tumblr上的图片和视频。请大家准备好纸巾,哦不,是准备好开发工具, 我们开始写代码。


1.1 需求分析


下载一个站点上的图片和视频,无非就是写一个简易的爬虫,这里我不去使用现有的爬虫框架, 也可以很容易的完成任务。编写指定页面的爬虫,需要对目标页面的HTML结构进行分析,如果 是AJAX请求,需要进行多次的请求分析;如果存在身份验证,则要进一步处理Cookie等数据。一般的 爬虫的基本结构如下图:


即使编写一个最小的爬虫,我们也要有一个任务调度器,用来生成任务条目到队列中, 针对不同的任务类型,要编写不同的下载器。不考虑分布式部署的情况下,每一个下载器(Downloader) 应该在一个线程中执行任务。今天我们的要编写的爬虫略微简单,考虑到入门级同学,过多的 概念就不介绍了,以免发蒙。


如果你还不知道Tumblr是什么的话,请百度。 Tumblr(中文名:汤博乐) 成立于2007年,是目前全球最大的轻博客网站,也是轻博客网站的始祖。Tumblr(汤博乐)是一种介于传统博客和微博之间的全新媒体形态,既注重表达,又注重社交,而且注重个性化设置,成为当前最受年轻人欢迎的社交网站之一。雅虎公司董事会2013年5月19日决定,以11亿美元收购Tumblr。


这是一个高大上的网站,很多设计师,动图爱好者的聚集地。不过目前需要FQ访问。


下面我们来看一下tumblr的个人空间。


如图,每个tumblr的个人空间都是一个二级域名,你甚至可以绑定你自己的域名。在个人主页上, 是一个微博式的消息列表,有文字,图片,视频等形式。消息的展现,是页面上的JavaScript脚本 通过请求Tumblr的Api来获取返回信息,然后添加到页面上的。通过API,可以省掉很多麻烦,至少 我们不必分析整个页面的html来提取需要的信息了。


下面我们看一下接口:


上面的代码是一个接口模板,第一个参数是要访问的用户空间的用户名;第二个参数是媒体类型, 图片为“photo”,视频为“video”;第三个参数为请求的资源数;第四个参数为从第几个资源开始 请求。 下面我们构造一个photo的请求,看看返回的数据是什么样的。


我们看到返回的数据是XML格式的数据,基本的层级为Tumblr>posts>post。图片的URL在post的photo-url字段中,视频与此类似,就不再演示了。 获取到媒体资源的url之后,就可以进行下载了。


我们再构造一个video类型的请求。


video类型的资源的url,需要从player属性中进行进一步匹配才能得出最后的结果。


在具体编码之前,我们需要对可能遇到的技术难点进行一个评估,并找到解决方案。


1.2 技术点分析


1.2.1 如何发送http请求


这里推荐 request模块(在我们要实现的功能 中,直接使用request模块的get方法就可以了。


1.2.2 如何处理xml数据


使用request模块发送并接收数据之后,要处理返回的XML数据,因为我们只需要获取photo-uri字段的 值就可以了,所以这里推荐使用xmltodict模块( xmltodic模块,将xml文档处理成类似Json对象,方便我们对数据进行访问。


1.2.3 如何实现Queue


python中自带Queue模块,可以满足我们目前的队列需求,由于python2.7和python3.0中 对queue模块的命名进行的变更,编程的时候需要注意。如果考虑兼容两个版本的话,可以 考虑引入six模块(模块是一个专门用于解决 从python2.x到python3.x的兼容性问题的模块,它对python版本变更导致到部分模块不能应用的问题 进行了内部处理,需要处理类似兼容问题的时候,可以考虑或者参考该模块的实现方式。


1.2.4 如何实现多线程


关于Python多线程,请自行搜索相关文章进行学习,例子很多,这里就不详细说明了。


1.2.5 如何处理json


考虑到Tumblr需要FQ访问,如果本机不使用VPN的话,可能需要配置代理,代理采用json配置方式。 处理.使用python内置的json模块(就可以了。


1.2.6 如何使用正则表达式


为了精确匹配url信息,我们需要使用正则表达式对xml数据的中字段值进行进一步处理,使用 内置的re模块(就可以了。


1.3 搭建程序基本框架


通过上面的分析,我们编写一个下载Tumblr图片和视频的简易爬虫已经没有技术障碍了,下面 我们搭建基本框架。


以下代码非玄魂原创,参考自做了部分修改


1 # 设置请求超时时间


2 TIMEOUT = 10


3


4 # 尝试次数


5 RETRY = 5


6


7 # 分页请求的起始点


8 START = 0


9


10 # 每页请求个数


11 MEDIA_NUM = 50


12


13 # 并发线程数


14 THREADS = 10


15


16 # 是否下载图片


17 ISDOWNLOADIMG=True


18


19 #是否下载视频


20 ISDOWNLOADVIDEO=True


21


22 #任务执行类


23 class DownloadWorker(Thread):


24 def init(self, queue, proxies=None):


25 Thread.init(self)


26 self.queue = queue


27 self.proxies = proxies


28


29 def run(self):


30 while True:


31 medium_type, post, target_folder = self.queue.get()


32 self.download(medium_type, post, target_folder)


33 self.queue.task_done()


34


35 def download(self, medium_type, post, target_folder):


36 pass


37


38 def _handle_medium_url(self, medium_type, post):


39 pass


40


41 def _download(self, medium_type, medium_url, target_folder):


42 //代码效果参考:http://hnjlyzjd.com/xl/wz_25240.html

pass

43


44 #调度类


45 class CrawlerScheduler(object):


46


47 def init(self, sites, proxies=None):


48 self.sites = sites


49 self.proxies = proxies


50 self.queue = Queue.Queue()


51 self.scheduling()


52


53 def scheduling(self):


54 pass


55


56


57


58 def download_videos(self, site):


59 pass


60


61 def download_photos(self, site):


62 pass


63


64 def _download_media(self, site, medium_type, start):


65 pass


66


67 #程序入口


68 #初始化配置


首先,我们定义了一些全局变量,看注释就明白用途了,不做过多解释。


现在看上面的类和方法的定义。DownloadWorker类,执行具体的下载任务,因为每个下载任务 要在单独的线程中完成,所以我们将DownloadWorker类继承Thread类。DownloadWorker接收从CrawlerScheduler 传递过来的Queue,它会从queue中请求任务来执行。同时如果用户配置了代理,在执行http请求的时候会使用代理。 run方法是线程启动方法,它会不停的从queue中请求任务,执行任务。download方法,首先调用_handle_medium_url 方法,获取当前任务的url,然后调用_download方法执行具体的下载任务。


CrawlerScheduler类,根据配置中需要处理的用户名,创建任务队列,初始化任务线程,启动线程执行任务。 scheduling方法,创建并启动工作线程,然后调用download_videos和download_photos方法。 download_videos和download_photos方法分别调用_download_media方法,创建具体的任务队列。_download_media 方法,首先根据传入的site创建对应的本地文件夹,然后请求Tumblr的接口,获取用户所有的图片或者视频数据压入队列。


除了上面的核心方法之外,我们创建两个配置文件proxies.json和sites.txt文件。proxies.json用来配置 代理,默认为空。


{}


可以根据你使用的代理,进行具体的配置,比如:


{


"http": "",


"https": "127.0.0.1:8787"


}


或者


{


"http": "socks5://user:pass@host:port",


"https": "socks5://127.0.0.1:1080"


}


sites.txt文件用来配置我们要请求的用户空间,只需要配置用户名即可,例如:


want2580,luoli-qaq


1.4 具体实现


这里大家使用最新的python版本就可以了,安装Python的时候一定要将pip一同安装。 根据1.2节的分析,我们需要安装如下模块:


requests>=2.10.0


xmltodict


six


PySocks>=1.5.6


为了方便,可以将这些依赖放到一个requirements.txt文件中。


然后执行命令:


pip install -r requirements.txt


基本环境准备完毕之后,在具体实现逻辑之前先引入依赖的模块。


# -- coding: utf-8 --


import os


import sys


import requests


import xmltodict


from six.moves import queue as Queue


from threading import Thread


import re


import json


下面我们先来完善CrawlerScheduler类的scheduling方法。


def scheduling(self):


# 创建工作线程


for x in range(THREADS):


worker = DownloadWorker(self.queue,


proxies=self.proxies)


#设置daemon属性,保证主线程在任何情况下可以退出


worker.daemon = True


worker.start()


for site in self.sites:


if ISDOWNLOADIMG:


self.download_photos(site)


if ISDOWNLOADVIDEO:


self.download_videos(site)


根据全局变量THREADS定义的最大线程数,创建DownloadWorker对象,并调用start方法,启动线程。 接下来根据传入的sites,循环调用download_photos和download_videos方法。下面我们看download_photos和download_videos方法 的实现。


def download_videos(self, site):


self._download_media(site, "video", START)


# 等待queue处理完一个用户的所有请求任务项


self.queue.join()


print("视频下载完成 %s" % site)


def download_photos(self, site):


self._download_media(site, "photo", START)


# 等待queue处理完一个用户的所有请求任务项


self.queue.join()


print("图片下载完成 %s" % site)


这两个方法,只是调用了_download_media方法,传入各自的类型,和分页请求的其实索引值,目前都是从0开始。 下面看核心的_download_media方法。


def _download_media(self, site, medium_type, start):


#创建存储目录


current_folder = os.getcwd()


target_folder = os.path.join(current_folder, site)


if not os.path.isdir(target_folder):


os.mkdir(target_folder)


base_url = ""


start = START


while True:


media_url = base_url.format(site, medium_type, MEDIA_NUM, start)


response = requests.get(media_url,


proxies=self.proxies)


data = xmltodict.parse(response.content)


try:


posts = data【"tumblr"】【"posts"】【"post"】


for post in posts:


# select the largest resolution


# usually in the first element


self.queue.put((medium_type, post, target_folder))


start += MEDIA_NUM


except KeyError:


break


_download_media方法,会在当前程序执行目录创建以用户名命名的子文件夹,用来存储图片和视频文件。这里 使用os.getcwd()来获取当前程序执行的觉得路径,然后通过os.path.join(current_folder, site)构造目标文件夹 路径,通过os.path.isdir(target_folder)来判断是否已经存在该文件夹,如果没有则通过os.mkdir(target_folder) 创建文件夹。


接下来,_download_media方法循环进行分页请求,来获取图片或视频资源信息。通过requests.get(media_url,proxies=self.proxies) 发送http get请求,通过response.content获取返回的数据,然后利用xmltodict.parse(response.content)来 反序列化xml数据到data对象。调用 data【"tumblr"】【"posts"】【"post"】,获取当前返回数据中的所有媒体资源。 然后循环调用self.queue.put((medium_type, post, target_folder))方法,将每一个post字段压入队列。此时压入队列的post包含了 一个图片或者视频的各项数据,需要在worker线程执行的时候进一步处理才能得到具体的url,后面我们继续分析。


scheduling类的实现已经完成了,在scheduling类的scheduling方法中启动了线程,每个worker对象的run方法 都会执行。


def run(self):


while True:


medium_type, post, target_folder = self.queue.get()


self.download(medium_type, post, target_folder)


self.queue.task_done()


run 方法,通过self.queue.get()方法,从任务队列中获取一条任务,每个任务包含媒体类型(图片或则视频), 每个媒体的post信息以及下载文件保存的目标文件夹。run方法将这些信息传入download方法。


def download(self, medium_type, post, target_folder):


try:


medium_url = self._handle_medium_url(medium_type, post)


if medium_url is not None:


self._download(medium_type, medium_url, target_folder)


except TypeError:


pass


download方法首先通过_handle_medium_url方法获取具体的资源的url,然后调用_download执行下载。


def _handle_medium_url(self, medium_type, post):


try:


if medium_type == "photo":


return post【"photo-url"】【0】【"#text"】


if medium_type == "video":


video_player = post【"video-player"】【1】【"#text"】


hd_pattern = re.compile(r'."hdUrl":("(【^\s,】)"|false),')


hd_match = hd_pattern.match(video_player)


try:


if hd_match is not None and hd_match.group(1) != 'false':


return hd_match.group(2).replace('\', '')


except IndexError:


pass


pattern = re.compile(r'.src="(\S)" ', re.DOTALL)


match = pattern.match(video_player)


if match

相关文章
|
4天前
|
定位技术
技术好文共享:(官网)虚幻3
技术好文共享:(官网)虚幻3
|
5天前
|
前端开发
技术好文共享:第二十二webchat(2)
技术好文共享:第二十二webchat(2)
|
5天前
|
传感器 存储 数据采集
技术好文共享:焕新!CANape19真香!
技术好文共享:焕新!CANape19真香!
|
2月前
|
监控 JavaScript 安全
《VitePress 简易速速上手小册》第5章:社交媒体和网络互动(2024 最新版)
《VitePress 简易速速上手小册》第5章:社交媒体和网络互动(2024 最新版)
59 1
|
9月前
|
消息中间件 缓存 数据库
GitHub置顶半个月!78w字百亿级并发设计(全彩PDF),竟出自京东
想要设计一个高并发的系统,就要从根源出发。为什么会有高并发?高并发又有哪些厉害之处呢?其实很简单,刚开始系统都是连接数据库的,但是数据库在支撑到每秒并发两三千上万的时候,很多系统是支撑不住的。因此很多公司刚开始做的时候,技术如果不是很好,业务又发展太快,就有可能导致系统无法承受压力,发生宕机的情况,因此需要设计一个高并发的系统。
|
2月前
|
消息中间件 缓存 算法
太全!不愧是Github爆火的阿里812页高并系统设计核心技术全解
某乎上有这样一个问答:现在好点的互联网公司招聘基本都要求有高并发经验,但没有高并发的经验的人感觉只有在好点的互联网才获得高并发经验,这难道不是死循环?没有高并发经验的人如何才能获取高并发方面的经验呢? 如何获取高并发经验? 其实并不是去了大公司就能获得高并发的经验,高并发只是一个结果,并不是过程。在来自全人类的高并发访问面前,一切都有可能发生,所以我们经常能看到顶级网站的颤抖。想要获得高并发经验基础最重要,这包括算法,操作系统,jvm,数据库,缓存,多线程等等。这些独立而又关联的知识其实书本里都有,我们需要时不时地去温习,不能一直痴迷于框架,框架只会挡住你的眼睛,让你觉得什么都不重要。大并发
|
2月前
|
NoSQL Java 关系型数据库
阿里巴巴Github星标57.9KJava面试突击汇总(全彩版)首次公开
Java面试 现在互联网大环境不好,互联网公司纷纷裁员并缩减HC,更多程序员去竞争更少的就业岗位,整的IT行业越来越卷。身为Java程序员的我们就更不用说了,上班8小时需要做好本职工作,下班后还要不断提升技能、技术栈,才能从容应对现在互联网公司的面试! 但事实是:很多Java程序员,对自身是没有一个清楚的认知的,甚至不知道自己短板在哪?这样不做准备的就去面试,你肯定会离心仪的offer越来越远!我今天写这篇文章的意义就在于劝诫大家如果面试准备阶段没有方向的话,不妨暂时停下来,看一下自己怎么才能更加系统、有条理地去备战面试,建立起一个系统的查漏补缺体系;怎么才能从自己的实际出发,了解自身与互联
64 0
|
9月前
|
Dubbo Java 应用服务中间件
神作《Java多线程核心技术3》开源几分钟被下架!GitHub昙花一现
两天一次地分享好书环节又来了:高洪岩老师的 《Java多线程编程核心技术(第3版)》,网上还没有开源版本!阿嘴会在文末附电子版免费下载方式。
|
10月前
|
网络协议 架构师 数据中心
GitHub已标星93K!网易首席架构师闭关3月撰写1200页网络协议笔记
网络协议介绍 网络协议为计算机网络中进行数据交换而建立的规则、标准或约定的集合。
|
10月前
|
JSON 缓存 NoSQL
Github上线就星标120k!这份阿里独有的高并发实战笔记首次曝光
总有些小伙伴问当下最火的热门技术是什么?
97 0