A backup tool for renren.com
依赖 Python 3.6.5+(其他版本我没测试过,因为用了 f-string,所以应该是要这个版本起跳)
pipenv --python 3.6.5
pipenv install
pipenv shell
直接运行 fetch.py
即可,相关参数见下,不输入用户名密码是不会抓取的,不带各种抓取参数就是只登陆不抓取
$ python fetch.py --help
usage: fetch.py [-h] [-s] [-g] [-a] [-b] email password
fetch renren data to backup
positional arguments:
email your renren email for login
password your renren password for login
optional arguments:
-h, --help show this help message and exit
-s, --fetch-status fetch status or not
-g, --fetch-gossip fetch gossip or not
-a, --fetch-album fetch album or not
-b, --fetch-blog fetch blog or not
$ python fetch.py [email protected] passwordAtRenren -s -g -a -b
直接运行如下命令,即可在本机浏览器打开 localhost:5000
看到展示
python web.py
把抓取的入口优化了下,用户名密码在抓的时候再输,免得要去改 config 还可能不小心提交到 git 上去
不死心,还是想搞定登录,其实这个应该就是用某个第三方库做了下 AES 之类的加密,爬一下请求过程吧
把 http://s.xnimg.cn/a89037/nx/apps/login/login.js 给拿下来并做格式化并分析
- 第一步是在 865 行,DOM ready 后调 554 行的
Window.getKeys
,向 "key.jsp?generateKeypair=true" 发一个请求申请服务端生成 key - 然后调 389 行的
cryption.getKeys
,向 http://login.renren.com/ajax/getEncryptKey 发请求获得一个 json - 把里面的
rkey
保留下来,回头真正登录时要带上这个参数。用里面的e
,n
,maxdigits
几个参数生成一个 rsa_keyT
。基本可以参考 http://aiddroid.com/javascript-rsa-encryption/ 里的代码 797 行,对应的.e
是里面的factor
,.n
是里面的key
- 最终触发登录操作的是 560 行的
onsubmit
,里面的 u 对应的是前面拿到的 rkey,所以必然会执行后面的两个 encrypt,对应的调用时 410 行,把输入的e
跟之前生成的 rsa_keyT
一起去加密,分别是把邮箱和密码加密,然而最后的提交 507 行的s()
里只用了c
,也就是密码加密后的串
看了一下就是用的上面 aiddroid 里的那个算法,把加密部分看懂了后,密码不会超长,所以直接就是把密码按字节从左到右映射到大数 x 上,然后做 x**e % n 再转成 16 进制就是要的加密密码了
另外吐槽下登录的 URL,参数里带了个时间戳,但是这个戳不是用的实际任何一种语言的 timestamp,而是自己构建了一套,并且中间有些字段是缺失的(比如没有 day_of_month
但有 day_of_week
,也没有分钟数),也不保证等长(上午 9 点就是 9,晚上九点就是 21),还好现在 Py3.6+ 有模板字符串可以直接填
本来还想把分享抓下来,不过看了下请求,分享页面只有 HTTP 拿回来的 HTML,并没有 Json 好拿,而且格式太多,有 日志、视频、相册、照片、链接 等,解析和展示都不方便。另外分享的本地评论不多,但全站评论抓起来规模就非常可观,特别是某些热门的内容(如果跳过抓头像,应该也还好?)
还是先跳过这个,回头有空再说,把已有页面结构梳理好,先做一版纯 HTML 的,方便输出
把日志也都抓了下来。日志列表在刚进入的时候看不到 ajax 请求,一翻页就能拿到 ajax 请求,这下直接摸数据就好
日志详情页不是 ajax 请求来的,也不知道怎么构建,看了下 HTML 格式,直接裸抓了。人人吐出来的 HTML 日志内容部分在一行,那就摸那一行,连正则都不用,直接 find 就可以搞定,只是有个坑的地方是人人 \r
和 \n
乱用,一开始多抓了一部分无效内容
日志的评论还是 ajax 请求来的,但是没看到点赞名单的请求,不想裸解析 HTML,按原来的 URL 和格式猜了个点赞请求,直接能拿到,完美
把展示时的翻页逻辑整合成了一个 Jinja 的 macro,也不用到处复制粘贴重复代码了
考虑了下未来的展示,为了方便查看,还是尽可能的用 Jinja 吐,这样直接保存 HTML,回头也不用启 flask 也可以离线看。当然,从学习和炫技的角度出发,还是可以搞前后端分离,也去学下 Vue.js
抓图的时候偶尔可能会 ConnectionError,果断把超时和重试机制给加上
人人的评论还分当前评论和全站评论,很多好玩的回复都在全站评论里,还是考虑抓个全站评论
抓相册时没找到拿相册列表的 ajax 请求,只能去裸解析页面,不过在页面内嵌的 js 里有相关数据,用不上 requests-html
这么重的东西,直接正则表达式取出来就行
本来打算是按 相册列表 -> 相册 -> 照片列表 -> 照片 的顺序去拿,后面发现其实只要拿到某个相册的任意一张照片,就可以在 ajax 请求里拿到相册信息和里面的所有照片信息,倒是省事了不少
抓到本地来图全挂,不允许跨域调,下一步还是考虑怎么把图片抓到本地来并做映射
目前会挂的图有这么几个来源
- 留言板的头像
- 留言板的附件
- 评论和点赞的用户头像(其实跟 1 差不多,不过 1 里面是留言相关的,不是用户最新头像)
- 相册封面(其实是里面的某张照片)
- 照片(有多个尺寸,我取最大的了,在本地压缩尺寸)
域名也存在好几个,干脆都存到本地,并保持目录结构,映射过来就是 http://xx.xxx.cn/aa/bb/cc/dd.gif
-> /static/img/xx_xxx_cn/aa/bb/cc/dd.gif
,抓的时候去一下重,节省网络开销,然后把本地的展示直接替换就可以了
表情和礼物什么的看了下还不会被跨域封,替换起来比较麻烦,先不处理
本来想弄相册,看了下相册列表是在相册列表页的 HTML 里直接返回的,不是 json,所以要解析一下页面,引入 requests-html
这个库
后面发现还有留言板,结构更简单,打算先下手这个
翻页的时候发现留言板其实也有 ajax 请求,可以不用解析页面,这样就更简单了,直接拿。有几个需要注意的地方
- 留言板里每条留言的头像都是用户在当时状态的头像,所以不好塞 User 表里,而是直接记录在留言里方便点
- 时间给的字符串不是时间戳,自己转一下
- 留言内容在好几个地方有,filterOriginBody 是最原始的,但是表情没转义,想拿转义后的在 filterdBody 或者 body 里,但是又加了很多没必要的东西,需要滤一下
处理展示的时候发现跨域的图片拿的有问题,回头还是得把有跨域限制的图片爬到本地来(包括留言板的头像和附图等)
数据爬下来了怎么存也是个问题,打算用 sqlite,因为数据量也不大,但是裸操作 sqlite 还是太麻烦,搜了下 ORM,找到 peewee 这个库,研究了下怎么用
考虑爬取后的展示问题,打算用 flask 做服务,Vue.js 做前端,semantic-ui 做样式
把状态的获取都搞定了,包括
- 所有状态信息。包括 点赞、分享、评论 数
- 所有状态的前 8 个点赞人(人人只返回了前 8 个)
- 所有状态的所有评论
并用 flask 做了展示,先没上 Vue.js,只是裸写了一顿,熟悉了下 semantic-ui 的逻辑
牙龈发炎折腾了快一周,拖稿了
研究了半天现在登录的加密,混淆过的 js 读的脑仁疼,先跑通再说,直接从 Chrome 的请求里把加密后的 password
和 rkey
拿出来,就可以登录得到可用的 session 了
第一步先拿状态列表
状态直接是通过 AJAX 请求拿回来一段 json,这个就方便很多了,看了下返回的数据结构,有用的大概如下(部分数据已模糊化)
{
"count": 999, // 总共有多少条状态
"doingArray": [ // 当前页的状态列表
{
"comment_count": 20, // 评论数
"content": "要的留邮箱", // 内容
"createTime": "1416289808000", // 时间戳,后面还有个 dtime 用来显示,我们爬就不用了
"id": 12345, // 编号,后续拿评论什么的都用的上
"repeatCountTotal": 2, // 转发数,还有个 repeatCount 不知道干嘛的(直接转发?)
"rootContent": "谁有?", // 转发原文(如果是转发,否则没这个和下面几个字段)
"rootDoingUserId": 23456, // 转发原用户 id
"rootDoingUserName": "张三", // 转发原用户名
},
]
"likeInfoMap": { // 点赞信息,还有个 likeMap 不知道干嘛的,跟页面对比是这个有用
"status_12345": 1, // 格式是 status_id,用上面的来找就行了
}
}
每页 20 条,页码从 0 开始,所以最大页码就是 Math.ceil(count/20) - 1
,一路翻过去就好
重新爬的时候发现调登录可能会遇上要输入验证码,不如直接从 Chrome 里把 cookie 拿出来更方便
试图直接从 Chrome 里拷 Cookie 出来用,没用成功,还是走一遍 login 好了。之前找的那几个都是用明文登录的方式,似乎现在都不行了,抓了下请求包,还没完全搞明白官方是怎么加密的,但是在浏览器里把 ajaxLogin 那个请求的 form data 扒出来,直接提交后就可以获取到正确的 requests.session
想起来自己的人人账号也好久没用了,说不定这个站哪天都不在了,等官方有导出工具还是别想了,自己来吧
找了一圈找到了如下几个备份工具,不过看说明从 2018 年某刻后都不能用了,自己学着改改看