⽼司机教你下载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请求
1.2.2 如何处理xml数据
1.2.3 如何实现Queue
1.2.4 如何实现多线程
关于Python多线程,请⾃⾏搜索相关⽂章进⾏学习,例⼦很多,这⾥就不详细说明了。
1.2.5 如何处理json
1.2.6 如何使⽤正则表达式
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#任务执⾏类
23class DownloadWorker(Thread):
24def__init__(self, queue, proxies=None):
25        Thread.__init__(self)
26        self.queue = queue
27        self.proxies = proxies
28
29def run(self):
30while True:
31            medium_type, post, target_folder = ()
32            self.download(medium_type, post, target_folder)
33            self.queue.task_done()
34
35def download(self, medium_type, post, target_folder):
36pass
37
38def _handle_medium_url(self, medium_type, post):
39pass
40
41def _download(self, medium_type, medium_url, target_folder):
42pass
43
44#调度类
45class CrawlerScheduler(object):
46
47def__init__(self, sites, proxies=None):
48        self.sites = sites
49        self.proxies = proxies
50        self.queue = Queue.Queue()
51        self.scheduling()
52
53def scheduling(self):
54pass
55
56
57
58def download_videos(self, site):
59pass
60
61def download_photos(self, site):
62pass
63
64def _download_media(self, site, medium_type, start):
65pass
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和⽂件。proxies.json⽤来配置代理,默认为空。
{}
可以根据你使⽤的代理,进⾏具体的配置,⽐如:
{
"http": "10.10.1.10:3128",
"https": "127.0.0.1:8787"
}
或者
{
"http": "socks5://user:pass@host:port",
"https": "socks5://127.0.0.1:1080"
}
<⽂件⽤来配置我们要请求的⽤户空间,只需要配置⽤户名即可,例如:
want2580,luoli-qaq
1.4 具体实现
这⾥⼤家使⽤最新的python版本就可以了,安装Python的时候⼀定要将pip⼀同安装。根据1.2节的分析,我们需要安装如下模块:
requests>=2.10.0
xmltodict
six
PySocks>=1.5.6
为了⽅便,可以将这些依赖放到⼀个⽂件中。
然后执⾏命令:
pip install -
基本环境准备完毕之后,在具体实现逻辑之前先引⼊依赖的模块。
# -*- coding: utf-8 -*-
import os
import sys
import requests
import xmltodict
ves import queue as Queue
from threading import Thread
import re
import json
下载app里的视频下⾯我们先来完善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 = "{0}.tumblr/api/read?type={1}&num={2}&start={3}"
start = START
while True:
media_url = base_url.format(site, medium_type, MEDIA_NUM, start)
response = (media_url,
proxies=self.proxies)
data = xmltodict.t)
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⽅法循环进⾏分页请求,来获取图⽚或视频资源信息。通过(
media_url,proxies=self.proxies) 发送http get请求,通过t获取返回的数据,然后利⽤xmltodict.t)来反序列化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.download(medium_type, post, target_folder)
self.queue.task_done()
run ⽅法,通过()⽅法,从任务队列中获取⼀条任务,每个任务包含媒体类型(图⽚或则
视频),每个媒体的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 = repile(r'.*"hdUrl":("([^\s,]*)"|false),')
hd_match = hd_pattern.match(video_player)
try:
if hd_match is not None and up(1) != 'false':
return up(2).replace('\\', '')
except IndexError:
pass
pattern = repile(r'.*src="(\S*)" ', re.DOTALL)
match = pattern.match(video_player)
if match is not None:
try:
up(1)
except IndexError:
return None
except:
raise TypeError("不到正确的下载URL "
"请到 "
"github/xuanhun/tumblr-crawler"
"提交错误信息:\n\n"
"%s" % post)
_handle_medium_url⽅法,根据媒体的不同采⽤了不同的资源获取⽅法。不过获取图⽚的⽅法这⾥还是有缺陷的,因为⽤户在⼀个post中可能会发送⼀组图⽚,⽬前的⽅法只处理了第⼀张图⽚。如果是视频,先取出video-player 的⽂本内容,然后通过正则表达式匹配出视频的url,具体匹配原理,你只要参考视频请求返回的xml内容就明⽩了,这⾥就不详细分析了。下⾯我们看如何下载资源。
def _download(self, medium_type, medium_url, target_folder):
medium_name = medium_url.split("/")[-1].split("?")[0]
if medium_type == "video":
if not medium_name.startswith("tumblr"):
medium_name = "_".join([medium_url.split("/")[-2],
medium_name])
medium_name += ".mp4"
file_path = os.path.join(target_folder, medium_name)

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。