美文网首页
豆瓣阅读文字解码

豆瓣阅读文字解码

作者: crj1998 | 来源:发表于2019-02-25 20:46 被阅读0次

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

请求分析

栗木村拐卖事件这个页面为例,分析获取文章的全过程。
打开谷歌的调试工具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

相关文章

  • 豆瓣阅读文字解码

    豆瓣阅读上有许多精彩小说,想要把一些小说下载下来,但是一个个页面复制粘贴未免显得麻烦,所以自然想到利用爬虫爬取文章...

  • 《简书谈写作月刊002·文字情缘》上线

    小鹅通二维码,仅可通过【微信】扫描阅读~《简书谈写作月刊002》豆瓣阅读地址 卷首语·文字情缘 文字有温度,写作是...

  • 视频编解码六:FFmpeg音频解码

    FFmpeg音频解码流程 AAC视频格式解码为PCM编码格式iOS/Android编解码Demo代码有详细注释自行阅读

  • 阅读活动的理解

    一、阅读的核心就在于理解。它是从外显的文字到心理的活动,是从如同解码一般,再到人主观地对文字进行解释的过程。 二、...

  • 豆瓣阅读作者和简书推荐作者之微博认证:个人经验之谈

    到底哪个申请比较容易?哪个更快?个人经验之谈。 1.豆瓣阅读和简书介绍 豆瓣阅读 豆瓣阅读界面 ​ 首先笔者对豆瓣...

  • 文字能量解码师

    以前的我,创作的时候,需要等到夜很深很深才动笔,因为那会儿最静,灵感最多。 虽然最后文章都被说写得好,可是我人很累...

  • Toastmasters|CC4 Do it naturally

    CC4 HOW TO SAY IT OCT.20.2012 本文字数:825字 阅读时间:5分钟 原文链接:豆瓣...

  • 奋豆成长必读书单

    一、阅读方面 《如何阅读一本书》(豆瓣简介) 《越读者》(豆瓣简介) 《快速阅读》(豆瓣简介) 《这样读书就够了》...

  • 《简书谈写作月刊005·文字的砥砺》上线

    豆瓣阅读下载链接 卷首语·文字的砥砺 《淮南子》记载:“昔者仓颉作书,而天雨粟,鬼夜哭。”文字的力量可见一斑。从小...

  • 豆瓣阅读APP用户体验

    昨天花了一整天的时间梳理豆瓣阅读的流程,今天来写写豆瓣阅读给我的体验。 整体风格-文艺范儿 豆瓣阅读继承了豆瓣的文...

网友评论

      本文标题:豆瓣阅读文字解码

      本文链接:https://www.haomeiwen.com/subject/ppcnyqtx.html