豆瓣阅读文字解码

豆瓣阅读上有许多精彩小说,想要把一些小说下载下来,但是一个个页面复制粘贴未免显得麻烦,所以自然想到利用爬虫爬取文章咯。但是不爬取不知道,一分析才发现豆瓣阅读的反爬也是十分厉害了。

请求分析

栗木村拐卖事件这个页面为例,分析获取文章的全过程。
打开谷歌的调试工具F12,刷新页面,可以看到刷新过程中,页面会出现如下载入界面,随后再出现真正的文章内容。

载入界面

文章界面

这意味着网页内容是很可能是ajax动态加载的,也意味着有请求接口可以直接获得数据。
但是还是以常规的办法开局,查看文本内容是否直接在页面html中,但是Elements界面定位的文字内容竟然是一个文字在一个<span>标签内,如果文本真的在html内,其实就算这样,也不是很难提取,但是Elements界面看到的都是渲染过后的,这非常重要,对静态网页无所谓,但是尤其要注意动态网页,不要以为直接右键定位就可以爬取数据。对动态加载的网页,保险的方式还是查看网页源代码Ctrl+U,看到的页面基本没有任何文章内容。这意味着数据一定是另外请求得到的,对我而言,我还是很喜欢数据在单独的请求中的,这样就不用解析网页了,但是豆瓣会轻易的给出数据吗?

由于基本确定是动态加载,那么是XHR请求的概率也是很大了。Network界面中的XHR请求如下,除了get_reader_data外,其他大小size都非常小。

XHR请求

进一步preview看其他请求,只有一些评论,文章信息的json数据,但是get_reader_data不一样,其data部分有许多编码后的数据,而且量是非常的大,非常有可能是编码后的文章内容。
请求get_reader_data

至此,请求分析基本完成,可以确定文本数据在get_reader_data中,但是我们需要的数据经过编码,看看编码后的文本,也应该不是常见的编码方式,很有可能是豆瓣自定义的一种编码,下面要做的就是找到解码方式,就要深入到JS文件中了,这也是最难最繁琐的地方。

JS分析

给数据请求打上断点,刷新页面。

XHR断点

调用栈

Call Stack给出了发起请求的过程函数,可能由于豆瓣JS代码混淆没有做好,一眼就被getArticleData吸引了,这个名字极可能意味着这里就是获取文本数据的地方。
在函数中部打上断点,至于过程多试几次,我也是打了好几次才找到解码函数位置,刷新后观察如下界面
解码函数

其中变量lget_reader_data请求中返回的data数据,变量i是文章id,变量r是解码后的JSON对象。找到解码函数之后,剩下的就很简单,移动到函数上方进入函数定义部分,一点点分析,这需要一些JavaScript语法知识,我也将解码部分抽离出来了。

var o = ["A", "b", "H", "P", "Q", "X", "V", "p", "r", "I", "$", "7", "F", "z", "o", "K", "_", "S", "6", "a", "T", "C", "t", "j", "5", "n", "D", "e", "x", "U", "R", "y", "4", "N", "Y", "9", "v", "0", "3", "W", "l", "u", "1", "i", "q", "s", "O", "J", "G", "E", "w", "f", "B", "m", "L", "2", "d", "h", "k", "8", "c", "g", "Z", "M"];

function n(t) {
    return function(t) {
        if (Array.isArray(t)) {
            for (var e = 0, i = new Array(t.length); e < t.length; e++)
                i[e] = t[e];
            return i
        }
    }(t) || function(t) {
        if (Symbol.iterator in Object(t) || "[object Arguments]" === Object.prototype.toString.call(t))
            return Array.from(t)
    }(t) || function() {
        throw new TypeError("Invalid attempt to spread non-iterable instance")
    }()
}

var k = function(t, e) {
    return function(t, e, i) {
        var o = {},
            s = String.fromCharCode("}".charCodeAt(0) + 1),
            r = e.length;
            e = function(t, e, i) {
                    return e ? (t = t.slice(), e.split("").forEach(function(e) {
                        var o = e.charCodeAt(0) % i;
                        t = [].concat(n(t.slice(o)), n(t.slice(0, o)))}), t) : t
            }(e, i, r);
        for (var a = 0; a < r; ++a)
            o[e[a]] = a;
        for (var h, l, c, u, d = [], f = 0, p = 0; f < t.length;)
            h = o[t[f++]],
            l = o[t[f++]],
            c = o[t[f++]],
            u = o[t[f++]],
            d[p++] = h << 2 | l >> 4,
            d[p++] = (15 & l) << 4 | c >> 2,
            d[p++] = (3 & c) << 6 | u;
        var g = t.slice(-2);
        return g[0] === s ? d.length = d.length - 2 : g[1] === s && (d.length = d.length - 1),
            function(t) {
                for (var e = "", i = 0; i < t.length; ++i) {
                    var n = t[i];
                    e += String.fromCharCode(256 * n + t[++i])
                }
                return e
            }(d)
    }(t, o, e)
}

var N = function(t) {
    return parseInt(("" + t).slice(0, 10), 36).toString().slice(0, 5)
}

function result(l, i){
    var r = k(l, N(i));
    return r
}

使用时只要调用result函数即可,参数分别为编码后的data数据和文章id字符串。返回的是JSON字符串,可在python中转换为dict对象。

代码实现

分析完成后,就需要代码实现,由于涉及到js代码,当然可以将其用python语言描述,但是耗时费力,同时需要对js语法比较了解,不推荐。可以使用python中一些执行js代码的库,比如execjsjs2py等,由于结果涉及中文,推荐使用js2py。将上述抽离出来的JS代码保存为douban.js文件。

import js2py
import json
import requests

def run_js(l, i):
    with open('douban.js') as fp:
        js = fp.read()
    context = js2py.EvalJs()
    context.execute(js)
    r = context.result(l, i)
    return json.loads(r)

def post_url(url, aid):
    headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.96 Safari/537.36"}
    data = {"aid": aid, "reader_data_version": "v15"}
    r = requests.post(url, data=data, headers=headers, timeout=3)
    return r.json()["data"]

if __name__ == '__main__':
    data =post_url("https://read.douban.com/j/article_v2/get_reader_data", "108019536")
    result = run_js(data, "108019536")["posts"][0]["contents"]
    content = ""
    for i in result:
        d = i["data"]["text"][0]["content"]
        content+=d.strip()
    print(content)

代码内的一些函数使用不深入讲解,可自行深入了解。

参考文章:
1、豆瓣阅读的文字解码 - Blog

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,019评论 4 62
  •   2005 年,Jesse James Garrett 发表了一篇在线文章,题为“Ajax: A new App...
    霜天晓阅读 883评论 0 1
  • Ajax和XMLHttpRequest 我们通常将Ajax等同于XMLHttpRequest,但细究起来它们两个是...
    changxiaonan阅读 2,217评论 0 2
  • 今日统计 1.数学,2小时49 2.英语阅读,2小时23 3.政治选择题,2小时36 洗漱+早餐 0901--11...
    天热开风扇阅读 20评论 0 1
  • 你知道吗, 哈佛是世界上最有声望的大学之一, 最受欢迎、最成功的课程就是教你如何学会快乐? 本沙哈尔教授的积极心理...
    韩焕玲阅读 329评论 0 1