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

分析QQ空间

登录QQ空间

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

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

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

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

分析空间相册

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

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

从Headers和Response可以分别获取到两组重要信息:request 获取相册列表所需的请求信息,包括请求链接和参数

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过滤数据包,找到该相册的照片信息

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

# urlhttps://h5.qzone.qq.com/proxy/domain/photo.qzone.qq.com/fcgi-bin/cgi_list_photo# argsg_tk:477819917callback:shine0_Callbackt:952444063mode:0idcNum:4hostUin:123456789topicId:V13LmPKk0JLNRYnoTopic:0uin:123456789pageStart:0pageNum:30skipCmtCount:0singleurl:1batchId:notice:0appid:4inCharset:utf-8outCharset:utf-8source:qzoneplat:qzoneoutstyle:jsonformat:jsonpjson_esc:1question:answer:callbackFun:shine0_:1551790719497

其中有几个关键参数:g_tk - 与相册列表参数一致

topicId - 与相册列表参数中的id一致

pageStart - 本次请求照片的起始编号

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

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

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

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, 上面同样只截取了一张照片的信息,后面一部分返回的是当前相册的一些基本信息。totalInAlbum, totalInPage存储了当前相册总共包含的照片数及本次返回的照片数。而我们需要下载的图片链接则是url!

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

确定爬取方案创建qqzone类,初始化用户信息

使用Selenium模拟登录

获取Cookies和g_tk

使用requests获取相册列表信息

遍历相册,获取照片列表信息并下载照片

创建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 WebDriverException

# ...

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))

此处需要注意的是:使用selenium需要安装对应的webdriver

可以通过webdriver.Chrome()指定浏览器位置,否则默认从环境变量定义的路径查找

如果电脑打开浏览器较慢,可能需要在driver.get后sleep几秒

获取 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

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

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数据,最后解析所需的name和id。

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'] = topicId

query['pageStart'] = pageStart

query['pageNum'] = pageNum

return '&'.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 data

except 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 None

file_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

爬取测试爬取过程爬取结果

写在最后如果将请求参数中的format由jsonp改成json,则可以直接获取json数据

本用例并未使用多进程或多线程,所以速率不算快,还有待优化的地方

该爬虫已存放至开源项目,欢迎交流Litreily/capturer​github.com

博客原文:

Python网络爬虫5 - 爬取QQ空间相册​www.litreily.top

python爬取别人qq空间相册_Python网络爬虫5 - 爬取QQ空间相册相关推荐

  1. python 爬虫热搜_Python网络爬虫之爬取微博热搜

    微博热搜的爬取较为简单,我只是用了lxml和requests两个库 1.分析网页的源代码:右键--查看网页源代码. 从网页代码中可以获取到信息 (1)热搜的名字都在 的子节点里 (2)热搜的排名都在 ...

  2. python爬虫微博热搜_Python网络爬虫之爬取微博热搜

    微博热搜的爬取较为简单,我只是用了lxml和requests两个库 url= https://s.weibo.com/top/summary?Refer=top_hot&topnav=1&am ...

  3. python古诗默写_Python网络爬虫:爬取古诗文中的某个制定诗句来实现搜索

    Python网络爬虫:爬取古诗文中的某个制定诗句来实现搜索 发布时间:2020-07-20 23:48:19 来源:51CTO 阅读:883 python编译练习,为了将自己学习过的知识用上,自己找了 ...

  4. lofter 爬虫_Python网络爬虫1 - 爬取网易LOFTER图片

    LOFTER是网易出品的优质轻博客,灵感源于国外的tumblr,但比之更加文艺,更加本地化.本人非常喜欢LOFTER的UI设计,以及其中的优质用户和内容,似乎网易并不擅长推广,所以受众并不广泛.这都是 ...

  5. python表情包爬虫程序_Python网络爬虫7 - 爬取表情包

    为了逗女朋友开心,想找一堆表情包,那么作为一名程序员,自然是会想到用程序来完成这个事情,而Python爬虫就是一个非常好的方法. 我先找到了一个专门发布表情包的网站,就叫做 分析站点 为了不引起不适, ...

  6. python中data.find_all爬取网站为空列表_Python网络爬虫之Scrapy 框架-分布式【第二十九节】...

    1. 介绍scrapy-redis框架 scrapy-redis 一个三方的基于redis的分布式爬虫框架,配合scrapy使用,让爬虫具有了分布式爬取的功能. github地址: https://g ...

  7. python爬取新浪博客_python网络爬虫 新浪博客篇

    上次写了一个爬世纪佳缘的爬虫之后,今天再接再厉又写了一个新浪博客的爬虫.写完之后,我想了一会儿,要不要在博客园里面写个帖子记录一下,因为我觉得这份代码的含金量确实太低,有点炒冷饭的嫌疑,就是把上次的代 ...

  8. python网页数据存入数据库_python网络爬虫抓取动态网页并将数据存入数据库MySQL...

    简述 以下的代码是使用python实现的网络爬虫,抓取动态网页 http://hb.qq.com/baoliao/ .此网页中的最新.精华下面的内容是由JavaScript动态生成的.审查网页元素与网 ...

  9. python网络爬虫与信息提取视频_Python网络爬虫与信息提取入门5

    Part19 实例5:IP地址归属地的自动查询 怎么查询一个IP地址的归属呢?比如说某一个IP地址他是来自于北京.上海还是美国呢?我们用一个python 程序来判断.当然你要判断一个地址的归属地,你必 ...

  10. python网络爬虫_爬图片

    python网络爬虫_爬图片 1.安装 Beautifulsoup4 #解析返回的html与json数据pip install Beautifulsoup4 使用 :           运行后输入要 ...

最新文章

  1. 简单易上手编译FFmpeg
  2. 二维码识别器PC版(电脑版)
  3. cryptogen (2)generate 生成证书再举例
  4. word整个表格首行缩进_Word排版对不齐?别忘了这个明星按键
  5. Windows 10 May 2020 中 WSL 与 WSL2 的性能比较
  6. (2)把BlackBerry作为插件安装到已有的Eclipse中
  7. Bom Shanka Machines psymmetrix Delay for Mac(音频延迟效果器)
  8. MySQL手册chm格式文档
  9. Windows XP_修改登录背景图案
  10. GNURadio(一)
  11. 最小二乘法、加权最小二乘法——直线拟合
  12. 2015年,我从国内二线城市来到日本东京工作生活
  13. MATLAB中复数矩阵的转置、共轭及共轭转置
  14. Latex编写数学公式的常用语法
  15. 基于深度学习的遥感应用
  16. 首域金融BOSCTIME_关于首域金融BOSCTIME|首域金融资料
  17. Adobe Audition 入门教程
  18. 网络安全漏洞原理利用与渗透
  19. Python实现德州扑克游戏
  20. springboot基于ssm框架实现的家具商城管理系统

热门文章

  1. I LOVE YOU TOO密码解析
  2. 北斗心脏——高精度时间频率系统
  3. (疑似问题)用IDM某些版本可能会导致系统永久性开机黑屏
  4. DOS处理 CMD提速 BAT恶搞
  5. SIM800A上传数据到Onenet平台命令
  6. SIM800A/C只能发短信不能收短信解决方案
  7. php毕设周记,毕设周记录如何写的
  8. php composer 无法下载,composer给laravel下载扩展包 无法下载的问题
  9. lua luarocks luacheck linux安装
  10. PTA 基础编程题目集 7-15 计算圆周率 C语言