自毕业后,就再也没有用过QQ,QQ空间里记录的是些并不精彩的青葱岁月,但好歹也是份回忆,近日想着学以致用,用Python把QQ空间相册的所有照片爬取下来,以作备份。

分析QQ空间

登录QQ空间

爬取第一步,分析站点,首先需要知道如何登录QQ空间。最初想法是用requests库配置登录请求,模拟登录,但是不久便放弃了这一思路,请看下图↓

根据登录按钮绑定的监听事件可以追踪到该按钮的点击事件如下:

账号加密是必然的,但这一堆堆的代码真心不好解析,有耐心的勇士尽情一试!

在排除这种登录方法后,选择selenium模拟用户登录不失为省时省力的方法,而且我们只是需要通过selenium完成登录,获取到Cookies和后面讲述的g_tk参数后,就可以停用了,所以效率并不太低。

分析空间相册

登录以后,页面会跳转至 [https://user.qzone.qq.com/{QQ_NUMBER}](javascript:;), 这时把鼠标移到导航栏你会发现,所有的导航栏链接都是javascript:; ?。没错就是这么坑,一切都是暗箱操作。

当然这并不难处理,使用调试工具捕获点击后产生的请求,然后过滤出正确的请求包即可。因为网络包非常多,那么怎么过滤呢,猜想相册数据的API必然会返回个列表list,尝试过滤list然后逐个排除,最后定位到请求包。下面是通过fcg_list过滤后的数据包,列表信息以jsonp格式返回,稍作处理即可当做json格式来读取(后面有讲)。

HeadersResponse可以分别获取到两组重要信息:

  1. request 获取相册列表所需的请求信息,包括请求链接和参数
  2. response 数据包包含的所有相册的信息,是每个相册所含照片对应的请求包参数的数据来源

先看请求包:

# url
https://h5.qzone.qq.com/proxy/domain/photo.qzone.qq.com/fcgi-bin/fcg_list_album_v3# args
g_tk: 477819917
callback: shine0_Callback
t: 691481346
hostUin: 123456789
uin: 123456789
appid: 4
inCharset: utf-8
outCharset: utf-8
source: qzone
plat: qzone
format: jsonp
notice: 0
filter: 1
handset: 4
pageNumModeSort: 40
pageNumModeClass: 15
needUserInfo: 1
idcNum: 4
callbackFun: shine0
_: 1551788226819
复制代码

其中hostUin, uin都是QQ号,g_tk是必须的且每次重新登录都会更新(后面有讲如何获取),其它有些参数不是必须的,我尝试后整理出如下请求参数:

query = {'g_tk': self.g_tk,'hostUin': self.username,'uin': self.username,'appid': 4,'inCharset': 'utf-8','outCharset': 'utf-8','source': 'qzone','plat': 'qzone','format': 'jsonp'
}
复制代码

接下来看jsonp格式的跨域响应包:

shine0_Callback({"code":0,"subcode":0,"message":"","default":0,"data":
{"albumListModeSort" : [{"allowAccess" : 1,"anonymity" : 0,"bitmap" : "10000000","classid" : 106,"comment" : 11,"createtime" : 1402661881,"desc" : "","handset" : 0,"id" : "V13LmPKk0JLNRY","lastuploadtime" : 1402662103,"modifytime" : 1408271987,"name" : "毕业季","order" : 0,"pre" : "http:\/\/b171.photo.store.qq.com\/psb?\/V13LmPKk0JLNRY\/eSAslg*mYWaytEtLysg*Q*5Km91gIWfGuwSk58K2rQY!\/a\/dIY29GUbJgAA","priv" : 1,"pypriv" : 1,"total" : 4,"viewtype" : 0},
复制代码

shine0_Callback是请求包的callbackFun参数决定的,如果没这个参数,响应包会以_Callback作为默认名,当然这都不重要。所有相册信息以json格式存入albumListModeSort中,上面仅截取了一个相册的信息。

相册信息中,name代表相册名称,id作为唯一标识可用于请求该相册内的照片信息,而pre仅仅是一个预览缩略图的链接,无关紧要。

分析单个相册

与获取相册信息类似,进入某一相册,使用cgi_list过滤数据包,找到该相册的照片信息

同样的道理,根据数据包可以获取照片列表信息的请求包和响应信息,先看请求:

# url
https://h5.qzone.qq.com/proxy/domain/photo.qzone.qq.com/fcgi-bin/cgi_list_photo# args
g_tk: 477819917
callback: shine0_Callback
t: 952444063
mode: 0
idcNum: 4
hostUin: 123456789
topicId: V13LmPKk0JLNRY
noTopic: 0
uin: 123456789
pageStart: 0
pageNum: 30
skipCmtCount: 0
singleurl: 1
batchId:
notice: 0
appid: 4
inCharset: utf-8
outCharset: utf-8
source: qzone
plat: qzone
outstyle: json
format: jsonp
json_esc: 1
question:
answer:
callbackFun: shine0
_: 1551790719497
复制代码

其中有几个关键参数:

  1. g_tk - 与相册列表参数一致
  2. topicId - 与相册列表参数中的id一致
  3. pageStart - 本次请求照片的起始编号
  4. pageNum - 本次请求的照片数量

为了一次性获取所有照片,可以将pageStart设为0,pageNum设为所有相册所含照片的最大值。

同样可以对上面的参数进行简化,在相册列表请求参数的基础上添加topicIdpageStartpageNum三个参数即可。

下面来看返回的照片列表信息:

shine0_Callback({"code":0,"subcode":0,"message":"","default":0,"data":
{"limit" : 0,"photoList" : [{"batchId" : "1402662093402000","browser" : 0,"cameratype" : " ","cp_flag" : false,"cp_x" : 455,"cp_y" : 388,"desc" : "","exif" : {"exposureCompensation" : "","exposureMode" : "","exposureProgram" : "","exposureTime" : "","flash" : "","fnumber" : "","focalLength" : "","iso" : "","lensModel" : "","make" : "","meteringMode" : "","model" : "","originalTime" : ""},"forum" : 0,"frameno" : 0,"height" : 621,"id" : 0,"is_video" : false,"is_weixin_mode" : 0,"ismultiup" : 0,"lloc" : "NDN0sggyKs3smlOg6eYghjb0ZRsmAAA!","modifytime" : 1402661792,"name" : "QQ图片20140612104616","origin" : 0,"origin_upload" : 0,"origin_url" : "","owner" : "123456789","ownername" : "123456789","photocubage" : 91602,"phototype" : 1,"picmark_flag" : 0,"picrefer" : 1,"platformId" : 0,"platformSubId" : 0,"poiName" : "","pre" : "http:\/\/b171.photo.store.qq.com\/psb?\/V13LmPKk0JLNRY\/eSAslg*mYWaytEtLysg*Q*5Km91gIWfSk58K2rQY!\/a\/dIY29GUbJgAA&bo=pANtAgAAAAABCeY!","raw" : "http:\/\/r.photo.store.qq.com\/psb?\/V13LmPKk0JLNRY\/eSAslg*mYWaytEtLysg*Q*5Km91gIWfSk58K2rQY!\/r\/dIY29GUbJgAA","raw_upload" : 1,"rawshoottime" : 0,"shoottime" : 0,"shorturl" : "","sloc" : "NDN0sggyKs3smlOg6eYghjb0ZRsmAAA!","tag" : "","uploadtime" : "2014-06-13 20:21:33","url" : "http:\/\/b171.photo.store.qq.com\/psb?\/V13LmPKk0JLNRY\/eSAslg*mYWaytEtLysg*Q*5Km91gIWfSk58K2rQY!\/b\/dIY29GUbJgAA&bo=pANtAgAAAAABCeY!","width" : 932,"yurl" : 0},// ...]"t" : "952444063","topic" : {"bitmap" : "10000000","browser" : 0,"classid" : 106,"comment" : 1,"cover_id" : "NDN0sggyKs3smlOg6eYghjb0ZRsmAAA!","createtime" : 1402661881,"desc" : "","handset" : 0,"id" : "V13LmPKk0JLNRY","is_share_album" : 0,"lastuploadtime" : 1402662103,"modifytime" : 1408271987,"name" : "毕业季","ownerName" : "707922098","ownerUin" : "707922098","pre" : "http:\/\/b171.photo.store.qq.com\/psb?\/V13LmPKk0JLNRY\/eSAslg*mYWaytEtLysg*Q*5Km91gIWfGuwSk58K2rQY!\/a\/dIY29GUbJgAA","priv" : 1,"pypriv" : 1,"share_album_owner" : 0,"total" : 4,"url" : "http:\/\/b171.photo.store.qq.com\/psb?\/V13LmPKk0JLNRY\/eSAslg*mYWaytEtLysg*Q*5Km91gIWfGuwSk58K2rQY!\/b\/dIY29GUbJgAA","viewtype" : 0},"totalInAlbum" : 4,"totalInPage" : 4
}
复制代码

返回的照片信息都存于photoList, 上面同样只截取了一张照片的信息,后面一部分返回的是当前相册的一些基本信息。totalInAlbumtotalInPage存储了当前相册总共包含的照片数及本次返回的照片数。而我们需要下载的图片链接则是url

OK, 到此,所有请求和响应数据都分析清楚了,接下来便是coding的时候了。

确定爬取方案

  1. 创建qqzone类,初始化用户信息
  2. 使用Selenium模拟登录
  3. 获取Cookiesg_tk
  4. 使用requests获取相册列表信息
  5. 遍历相册,获取照片列表信息并下载照片

创建qqzone类

class qqzone(object):"""QQ空间相册爬虫"""def __init__(self, user):self.username = user['username']self.password = user['password']
复制代码

模拟登录

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import WebDriverExceptio# ...def _login_and_get_args(self):"""登录QQ,获取Cookies和g_tk"""opt = webdriver.ChromeOptions()opt.set_headless()driver = webdriver.Chrome(chrome_options=opt)driver.get('https://i.qq.com/')# time.sleep(2)logging.info('User {} login...'.format(self.username))driver.switch_to.frame('login_frame')driver.find_element_by_id('switcher_plogin').click()driver.find_element_by_id('u').clear()driver.find_element_by_id('u').send_keys(self.username)driver.find_element_by_id('p').clear()driver.find_element_by_id('p').send_keys(self.password)driver.find_element_by_id('login_button').click()time.sleep(1)driver.get('https://user.qzone.qq.com/{}'.format(self.username))
复制代码

此处需要注意的是:

  1. 使用selenium需要安装对应的webdriver
  2. 可以通过webdriver.Chrome()指定浏览器位置,否则默认从环境变量定义的路径查找
  3. 如果电脑打开浏览器较慢,可能需要在driver.getsleep几秒

获取 Cookies

使用selenium获取Cookies非常方便

self.cookies = driver.get_cookies()
复制代码

获取 g_tk

获取g_tk最开始可以说是本爬虫最大的难点,因为从网页中根本找不到直接写明的数值,只有各种函数调用。为此我全局搜索,发现好多地方都有其获取方式。

最后选择了其中一处,通过selenium执行脚本的功能成功获取到了g_tk

self.g_tk = driver.execute_script('return QZONE.FP.getACSRFToken()')
复制代码

到此,selenium的使命就完成了,剩下的将通过requests来完成。

初始化 request.Session

接下来需要逐步生成请求然后获取数据。但是为方便起见,这里使用会话的方式请求数据,配置好cookieheaders,省的每次请求都设置一遍。

def _init_session(self):self.session = requests.Session()for cookie in self.cookies:self.session.cookies.set(cookie['name'], cookie['value'])self.session.headers = {'Referer': 'https://qzs.qq.com/qzone/photo/v7/page/photo.html?init=photo.v7/module/albumList/index&navBar=1','User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36'}
复制代码

请求相册信息

获取相册信息,需要先封装好请求参数,然后通过session.get爬取数据,再通过正则匹配以json格式读取jsonp数据,最后解析所需的nameid

def _get_ablum_list(self):"""获取相册的列表信息"""album_url = '{}{}'.format('https://h5.qzone.qq.com/proxy/domain/photo.qzone.qq.com/fcgi-bin/fcg_list_album_v3?',self._get_query_for_request())logging.info('Getting ablum list id...')resp = self.session.get(album_url)data = self._load_callback_data(resp)album_list = {}for item in data['data']['albumListModeSort']:album_list[item['name']] = item['id']return album_list
复制代码

其中的参数组合来自下面的函数_get_query_for_request函数。

def _get_query_for_request(self, topicId=None, pageStart=0, pageNum=100):"""获取请求相册信息或照片信息所需的参数Args:topicId: 每个相册对应的唯一标识符pageStart: 请求某个相册的照片列表信息所需的起始页码pageNum: 单次请求某个相册的照片数量Returns:一个组合好所有请求参数的字符串"""query = {'g_tk': self.g_tk,'hostUin': self.username,'uin': self.username,'appid': 4,'inCharset': 'utf-8','outCharset': 'utf-8','source': 'qzone','plat': 'qzone','format': 'jsonp'}if topicId:query['topicId'] = topicIdquery['pageStart'] = pageStartquery['pageNum'] = pageNumreturn '&'.join('{}={}'.format(key, val) for key, val in query.items())
复制代码

其中的jsonp解析函数如下,主体部分就是一个正则匹配,非常简单。

def _load_callback_data(self, resp):"""以json格式解析返回的jsonp数据"""try:resp.encoding = 'utf-8'data = loads(re.search(r'.*?\(({.*}).*?\).*', resp.text, re.S)[1])return dataexcept ValueError:logging.error('Invalid input')
复制代码

解析并下载照片

获取相册列表后,逐个请求照片列表信息,进而逐一下载

def _get_photo(self, album_name, album_id):"""获取单个相册的照片列表信息,并下载该相册所有照片"""photo_list_url = '{}{}'.format('https://h5.qzone.qq.com/proxy/domain/photo.qzone.qq.com/fcgi-bin/cgi_list_photo?',self._get_query_for_request(topicId=album_id))logging.info('Getting photo list for album {}...'.format(album_name))resp = self.session.get(photo_list_url)data = self._load_callback_data(resp)if data['data']['totalInPage'] == 0:return Nonefile_dir = self.get_path(album_name)for item in data['data']['photoList']:path = '{}/{}.jpg'.format(file_dir, item['name'])logging.info('Downloading {}-{}'.format(album_name, item['name']))self._download_image(item['url'], path)
复制代码

下载图片也是通过request,记得设置超时时间。

def _download_image(self, url, path):"""下载单张照片"""try:resp = self.session.get(url, timeout=15)if resp.status_code == 200:open(path, 'wb').write(resp.content)except requests.exceptions.Timeout:logging.warning('get {} timeout'.format(url))except requests.exceptions.ConnectionError as e:logging.error(e.__str__)finally:pass
复制代码

爬取测试

  • 爬取过程
  • 爬取结果

写在最后

  1. 如果将请求参数中的formatjsonp改成json,则可以直接获取json数据
  2. 本用例并未使用多进程或多线程,所以速率不算快,还有待优化的地方
  3. 该爬虫已存放至开源项目Github capturer,欢迎交流

本文首发于www.litreily.top

Python网络爬虫5 - 爬取QQ空间相册相关推荐

  1. Python网络爬虫:爬取并下载个性化签名图片 附GUI窗体版

    Hello,大家好,我是wangzirui32,最近我开了个新的系列,是"Python网络爬虫"分类,还请大家多多支持! 今天,我们来学习如何爬取并下载个性化签名图片,开始学习吧! ...

  2. Python_小林的爬取QQ空间相册图片链接程序

    前言 昨天看见某人的空间有上传了XXXX个头像,然后我就想着下载回来[所以本质上这是一个头像下载程序],但是一个个另存为太浪费时间了,上网搜索有没有现成的工具,居然要注册码,还卖45一套.你们的良心也 ...

  3. 【python爬虫】爬取QQ空间说说

    前言 虽然扒qq空间不是很地道,但咱还是干干吧--正好也是闲来无事,我这个应该不会被别人发现 tao) 技术路线 selenium + beautifulsoup + pyautogui + echa ...

  4. Python网络爬虫:爬取CSDN热搜数据 并保存到本地文件中

    hello,大家好,我是wangzirui32,今天我们来学习如何爬取CSDN热搜数据,并保存到Excel表格中. 开始学习吧! 学习目录 1. 数据包抓取 2. 编写代码 1. 数据包抓取 打开CS ...

  5. 【爬虫实战】10应用Python网络爬虫——定向爬取百度百科文字

    python百度百科爬虫 网页源代码分析 编程实现 小结 网页源代码分析 首先找一下需要爬取的正文: 对应的源代码有两个地方: 上图往后翻会发现省略号,所以下面这张图才是我们需要爬取的部分: 编程实现 ...

  6. Python网络爬虫之爬取微博热搜

    前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取 python免费学习资 ...

  7. Python网络爬虫:爬取豆瓣上《小王子》书评

    本文为南大<用Python玩转数据>学习笔记 用到的库或模块: 1.Requests第三方库 用于中小型网络爬虫的信息抓取. 基本方法: requests.get() 请求获取指定YRL位 ...

  8. Python 网络爬虫:爬取4K高清美图

    爬取4K高清美图 这篇爬虫主要是用来爬取网站高清4K图片,这也是笔者学初学爬虫的时候最先写一个的爬虫脚本,现在回过头来重新梳理一下,也算是对从前知识的一个总结,希望对大家有所帮助! 文章目录 爬取4K ...

  9. Python网络爬虫8 - 爬取彼岸图网美图

    彼岸图网收集了大量美图,是个不错的爬取对象.话不多说,直接上图. 分析站点 分类列表 爬取之前,自然要分析一波,这个站点的框架比较简单,从分类着手,共包含12个分类项. 4K人物 4K动漫 4K动物 ...

最新文章

  1. Java项目:前后端分离网上手机商城平台系统设计和实现(java+vue+redis+springboot+mysql+ssm)
  2. [uboot]Issue list
  3. 《互联网理财一册通》一一第12章 移动互联网“指尖上的理财”
  4. 阿里巴巴 Kubernetes 应用管理实践中的经验与教训
  5. JZOJ__Day 6:【普及模拟】团队背包(team)
  6. linux配置本地yum源 centos7.9为例
  7. c语言二次函数拟合,二次函数拟合算法
  8. 索引存储模型-二分查找
  9. 快速学习一个新的模块
  10. linux 5识别网卡,CentOS 5.5系统识别不了Atheros AR8151网卡怎么办?
  11. raft协议中统计一条log被多少节点复制
  12. 视频版权检测算法​​
  13. 一个人开始变富时,会有这4个征兆,坚持下去,路越走越宽
  14. 如何在数学建模比赛中稳拿奖——个人100%获奖经验分享
  15. js实现双人对战五子棋
  16. MD5文件如何解密zip文件
  17. Kali 利用setoolkit制作钓鱼网站
  18. 蔡氏电路matlab仿真实代码验,基于蔡氏电路的MATLAB仿真
  19. 以太坊五周年:从涅槃中苏醒
  20. mysql uuid分页优化_MySQL性能优化之分页查询优化

热门文章

  1. HTML函数多个条件并列,Excel满足两个条件的多解法
  2. 健康管理系统2.0(微信小程序 + JQuery + html + 爬虫 + MySQL + spring boot 只用于学习,不可商用)
  3. flutter BlocProvider context that does not contain a Cubit of type #### 错误
  4. python nonlocal_Python闭包与nonlocal
  5. 分类的性能度量(准确率、精确度、召回率、F1值、Kappa系数)
  6. Multi-Modal 3D Object Detection in Autonomous Driving: a Survey(自动驾驶中的多模态3D目标检测综述)论文笔记
  7. cifar10和cifar100(简介可视化)
  8. 相声专场:这届互联网大佬,真不行!
  9. MySQL快捷键注释
  10. 一款2018非常好玩的合击传奇版本