本文章中所有内容仅供学习交流,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
近来想深入学习滑块轨迹,没有找到好的学习资料,偶然看到一篇文章有讲到轨迹,这就开始了今天的这篇文章,出于小白的我看到文章就发现文章又过时了。开始就来了debugger,我的个天,学个爬虫为啥这么难~
还是开始我们这次的学习之旅吧!我就不信了,我还搞不定你~
于是找度娘研究了一天时间也没搞的太懂怎么搞,总是不成功,功夫不负有心人,慢慢的有点点明白什么意思了,接下来开始。直接实战,在实战中弥补自已的不足~

网址:aHR0cHM6Ly9iei56enptaC5jbi9pbmRleA==

1、绕过无限debugger:
ctrl+shfit+i 打开chrome浏览器开发工具

在这里插入图片描述
一来就出现了无限debugger。我的天,网上查了一天时间,尝试了说的各种方法,对js不是很熟悉的我心里面一万只马在奔腾。突然好像悟出来了什么,上菜。不懂的小白们也只有慢慢根据我这个来干,可能领悟的更快。

var _constructor = constructor;  //先接收变量
//重写函数
Function.prototype.constructor = function(s) {
                if (s == "debugger"){
                        console.log(s);
                        return null;
       return _constructor(s);
var  xxx=setInterval
setInterval=function (){}

打开console
在这里插入图片描述在这里插入图片描述

在这里插入图片描述
久违的正常开发者工具可以运行了。

2、开始分析
清空network,选择推荐,出现一个getDate,向下滑动出现很多带auth_key的请求如图
在这里插入图片描述每一个请求就是一张加载的图片。看来得把auth_key搞定。

开始搜索(ctrl+shift+f)这个关键定auth_key:
在这里插入图片描述打上断点,不能刷新,
在这里插入图片描述遂步分析每个字段是怎么来的,两种方式逆向,一种分析生成的逻辑用python模拟,另一种直接扣js代码,扣代码扣了半天,还是自己功力不行,从此网站看来我的还去学习如下知识点:js的函数调用形式,怎么补环境,像怎么document取得cookie。此次就python模拟:
开始分析:
_0x5e9924 = _0x2e82e1 + ‘-’ + _0x79d357 + ‘-0-’ + _0x14eb0a(_0x1c69e7 + ‘-’ + _0x2e82e1 + ‘-’ + _0x79d357 + ‘-0-TPV4hi7wIeM7DPv35457O8poVyUJRX0o’);

第一个:_0x2e82e1在这里插入图片描述在这里插入图片描述在这里插入图片描述

这个document怎么得到,搞不定,所以扣js也无从下手,先放在一边,我们看看这个函数做了什么,如下图,取消所有断点,在for行打下断点,点运行,出现如下图,每个箭头对应的是做了什么操作,查js相应的功能,
indexof检索cookie的字符串值有没有出现nmumber=,有则返回0,没有该方法返回 -1。substring截取cookie中字段’number=1673712000’的1673712000
在这里插入图片描述明白了此意思,我们取消断点,删除此值
在这里插入图片描述
回到
在这里插入图片描述

进入_0x2b0593()这个后面的(||逻辑或)

在这里插入图片描述在这里插入图片描述上图标注错误,后面发现加1是下一个月。看了半天猜测从gettime来看是一个从1970-1-1-0-0-0到当年-当月-15-0-0-0的一个秒级时间戳
在这里插入图片描述

第二个:_0x79d357
取消所有断点,回到在这里插入图片描述
开启断点,开始在这里插入图片描述
调试,在这里插入图片描述
在这里插入图片描述
猜测是md5加密:
在这里插入图片描述
第三个:

在这里插入图片描述从substring知道是截取字符串
在这里插入图片描述在这里插入图片描述
通过以上,开始写python代码:

def get_number():  # _0x2e82e1
    year = datetime.now().year
    month = datetime.now().date().month
    day = 15
    if month < 12:
        month = month + 1
    else:
        month = 1
        year = year + 1
    date = f"{year}-{month}-{day}"
    b = str(date) + " 00:00:00"
    return int(time.mktime(time.strptime(b, "%Y-%m-%d %H:%M:%S")))
def get_number_md5(number): # _0x79d357
    return hashlib.md5(str(number).encode()).hexdigest()
def get_url_path(url): # _0x1c69e7
    return url[21:len(url)]
def get_auth_key(number, number_md5, url_path): # auth_key
    temp = hashlib.md5(str(f"{url_path}-{number}-{number_md5}-0-TPV4hi7wIeM7DPv35457O8poVyUJRX0o").encode()).hexdigest()
    return f"{number}-{number_md5}-0-{temp}"

如此通过如下就可以得到 url后面的auth_key:

if __name__ == '__main__':
    a_1 = get_number()
    b_1 = get_number_md5(a_1)
    c_1 = get_url_path("https://cdn2.zzzmh.cn/wallpaper/origin/265410565e874f71a09db1d35689f8cd.jpg/thumbs")
    d_1 = get_auth_key(a_1, b_1, c_1)
    print(a_1)

开始准备爬取每张图片,通过查看没有看到图片的地址是怎么来的,发现如下,在这里插入图片描述猜测result:里应该是每个图片的地址列表,经过加密码,先搜索关键字:ctrl+shift+f搜索result
在这里插入图片描述打上断点,再来调试,
在这里插入图片描述deciper 这个词估计就是加密
我们来扣这个js,说好的我只学python的,呜呜

var CryptoJS = require('crypto-js')
window = global;
function _0x41fcab(_0x4596fd) {
    for (var _0x24a3f0 = window['atob'](_0x4596fd), _0x1f38bf = new Int8Array(_0x24a3f0['length']), _0x514efb = 0x0; _0x514efb < _0x24a3f0['length']; _0x514efb++)
        _0x1f38bf[_0x514efb] = _0x24a3f0['charCodeAt'](_0x514efb);
    return _0x1f38bf;
function _0x4a4703(_0x29c724) {
    for (var _0x2560f3 = [-0x6f, 0x34, 0x5b, 0x41, -0x41, 0x74, 0x77, 0x6a, -0x79, -0x52, -0x5, 0x50, 0x33, 0x61, 0x44, -0x53, -0x70, -0x33, 0x17, -0x2e, -0x22, -0x72, -0x37, -0xb, -0x7f, 0x5a, 0x21, 0x16, -0x1f, 0x32, -0x11, 0x14, -0x2c, 0xf, -0x5e, -0x7b, 0x76, -0x17, -0x3d, 0x72, 0x47, -0x68, -0x7e, -0x75, -0x51, -0x36, -0x12, -0x6e, -0x4, -0x5f, -0x5b, 0x5e, -0x50, -0xe, 0x78, 0x69, 0x55, 0x68, -0x56, -0x6c, 0x43, 0x19, 0x65, 0x6c, 0x10, -0x69, 0x6f, -0xa, 0x75, -0x49, 0x4d, 0x59, -0x1d, -0x62, -0x44, 0x70, 0x6b, -0x1, 0x56, 0x79, 0x58, -0x65, -0x7c, 0x45, -0x1e, -0x8, -0x71, -0x4a, -0x76, 0x39, -0x19, 0xc, -0x73, -0x6a, 0x5f, 0x7f, 0x54, 0x7c, -0x66, -0x1c, 0x49, 0x2b, -0x3c, 0x1c, 0x2e, 0x73, 0x1e, 0x7a, -0x4b, 0x7d, -0x43, -0x4d, 0x3, -0x7, -0x35, -0xd, 0x35, 0x4e, -0x48, 0x1, 0xb, -0x47, -0x27, -0x4f, -0x3, 0x13, 0x29, 0x7e, -0x2b, -0x7d, -0x1b, 0x22, 0x3f, 0x8, 0x48, -0x23, -0x29, -0x3f, 0x3c, -0x18, 0x66, 0x2f, -0x77, -0x67, -0x16, 0x2d, 0x3b, 0x40, -0x60, 0x31, 0x53, -0x6b, -0x78, -0x39, -0x46, 0x0, -0x26, -0x54, -0x28, 0x18, 0xe, 0x30, 0x1d, 0x2c, -0x24, -0x2f, 0x38, -0x5c, 0x26, 0x25, 0x4, -0x32, 0x67, 0xa, -0x59, 0x37, 0x71, -0x1a, 0x6e, 0x36, 0x24, -0x14, -0x4e, -0xc, -0x74, 0x46, -0x25, 0x5, -0x3e, -0x4c, -0x30, -0x40, 0x4f, 0x64, 0x28, 0x6, -0x3a, -0x5a, -0x13, -0x9, 0x27, 0x5d, -0x63, 0x15, 0x7, 0x1a, -0x2, 0x1b, -0x2d, 0x51, 0x3a, -0x7a, 0x4c, -0x42, 0x2, 0x5c, -0x2a, 0x62, -0x10, 0x9, 0x3d, 0x3e, -0xf, 0x63, -0x15, 0x1f, -0x38, 0x57, 0x11, -0x34, -0x45, -0x21, -0x3b, -0x55, 0x42, 0x4a, 0x12, -0x5d, -0x80, -0x57, -0x20, 0x2a, 0x20, -0x58, 0x6d, 0x60, 0xd, -0x6, 0x4b, -0x64, -0x31, 0x23, -0x61, 0x52, -0x6d, 0x7b], _0xce0c6d = 0x0, _0x4d32e9 = 0x0, _0x1f9094 = 0x0, _0x1d690b = new Array(), _0x31b8dd = 0x0; _0x31b8dd < _0x29c724['length']; _0x31b8dd++) {
        _0xce0c6d = _0xce0c6d + 0x1 & 0xff,
            _0x4d32e9 = (0xff & _0x2560f3[_0xce0c6d]) + _0x4d32e9 & 0xff;
        var _0x26091a = _0x2560f3[_0xce0c6d];
        _0x2560f3[_0xce0c6d] = _0x2560f3[_0x4d32e9],
            _0x2560f3[_0x4d32e9] = _0x26091a,
            _0x1f9094 = (0xff & _0x2560f3[_0xce0c6d]) + (0xff & _0x2560f3[_0x4d32e9]) & 0xff,
            _0x1d690b['push'](_0x29c724[_0x31b8dd] ^ _0x2560f3[_0x1f9094]);
    return _0x1d690b;
function _0x505719(_0x19700b) {
    for (var _0x31746e, _0x49e2d1, _0x5bb90d = '', _0x227025 = 0x0; _0x227025 < _0x19700b['length']; )
        _0x31746e = _0x19700b[_0x227025],
            _0x49e2d1 = 0x0,
            _0x31746e >>> 0x7 === 0x0 ? (_0x5bb90d += String['fromCharCode'](_0x19700b[_0x227025]),
                _0x227025 += 0x1) : 0xfc === (0xfc & _0x31746e) ? (_0x49e2d1 = (0x3 & _0x19700b[_0x227025]) << 0x1e,
                _0x49e2d1 |= (0x3f & _0x19700b[_0x227025 + 0x1]) << 0x18,
                _0x49e2d1 |= (0x3f & _0x19700b[_0x227025 + 0x2]) << 0x12,
                _0x49e2d1 |= (0x3f & _0x19700b[_0x227025 + 0x3]) << 0xc,
                _0x49e2d1 |= (0x3f & _0x19700b[_0x227025 + 0x4]) << 0x6,
                _0x49e2d1 |= 0x3f & _0x19700b[_0x227025 + 0x5],
                _0x5bb90d += String['fromCharCode'](_0x49e2d1),
                _0x227025 += 0x6) : 0xf8 === (0xf8 & _0x31746e) ? (_0x49e2d1 = (0x7 & _0x19700b[_0x227025]) << 0x18,
                _0x49e2d1 |= (0x3f & _0x19700b[_0x227025 + 0x1]) << 0x12,
                _0x49e2d1 |= (0x3f & _0x19700b[_0x227025 + 0x2]) << 0xc,
                _0x49e2d1 |= (0x3f & _0x19700b[_0x227025 + 0x3]) << 0x6,
                _0x49e2d1 |= 0x3f & _0x19700b[_0x227025 + 0x4],
                _0x5bb90d += String['fromCharCode'](_0x49e2d1),
                _0x227025 += 0x5) : 0xf0 === (0xf0 & _0x31746e) ? (_0x49e2d1 = (0xf & _0x19700b[_0x227025]) << 0x12,
                _0x49e2d1 |= (0x3f & _0x19700b[_0x227025 + 0x1]) << 0xc,
                _0x49e2d1 |= (0x3f & _0x19700b[_0x227025 + 0x2]) << 0x6,
                _0x49e2d1 |= 0x3f & _0x19700b[_0x227025 + 0x3],
                _0x5bb90d += String['fromCharCode'](_0x49e2d1),
                _0x227025 += 0x4) : 0xe0 === (0xe0 & _0x31746e) ? (_0x49e2d1 = (0x1f & _0x19700b[_0x227025]) << 0xc,
                _0x49e2d1 |= (0x3f & _0x19700b[_0x227025 + 0x1]) << 0x6,
                _0x49e2d1 |= 0x3f & _0x19700b[_0x227025 + 0x2],
                _0x5bb90d += String['fromCharCode'](_0x49e2d1),
                _0x227025 += 0x3) : 0xc0 === (0xc0 & _0x31746e) ? (_0x49e2d1 = (0x3f & _0x19700b[_0x227025]) << 0x6,
                _0x49e2d1 |= 0x3f & _0x19700b[_0x227025 + 0x1],
                _0x5bb90d += String['fromCharCode'](_0x49e2d1),
                _0x227025 += 0x2) : (_0x5bb90d += String['fromCharCode'](_0x19700b[_0x227025]),
                _0x227025 += 0x1);
    return _0x5bb90d;
function _0x4e3a27(_0x372e84) {
    return _0x505719(_0x4a4703(_0x41fcab(_0x372e84)));
console.log(_0x4e3a27(_0x372e84))

在这里插入图片描述ok!我们开始写框架写代码:

def getimage(page):
    resp = request_page()
    # print(response.json())
    if resp.json()['msg'] == 'success':
        print("开始下载!")
        download_img(resp)
    else:
        print("error!")
def request_page():
    resp = requests.request("POST", url, headers=header, data=payload)
    if resp.json()['msg'] == '需要身份验证':
    else:
    print(headers_getimage)
    return resp
def download_img(response):
if __name__ == '__main__':
    for page_n in range(2):
        print('开始第' + str(page_n+1) + '页')
        getimage(page_n+1)
        print('第' + str(page_n+1) + '页下载完毕!')

写完代码一跑,我的乖乖,第二页爬不动,再来查看是什么问题!
在这里插入图片描述过了滑块后请求的header里有个加密字段,这个要正确带上后,在这里插入图片描述
才能请求第二页,并且后面第3页,等还得带上在这里插入图片描述才能正常爬取。
尝试搜索关键定,找了半天也没找到,到发现captchaVerification的值与captcha一致。
在这里插入图片描述重新打开网页,开始文章开始的步骤,打上断点,点开第二页,滑动滑块成功后断下,
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述熟悉的加密,我们验证一下:
在这里插入图片描述OK!
搞定!
文章写的来之不易,如果觉得对你有帮助,请点赞关注!
链接: https://pan.baidu.com/s/1y2CbGr4WFApzd1_lJfmi8A 提取码: 82c6 复制这段内容后打开百度网盘手机App,操作更方便哦

不足:还得加紧多学习js,怎么能快速扣代码。
代码没有做过多的注释,好的注释是个习惯!有不明白的可以留言~只有实战才是最快的学习方法。涵盖了很多知识点,本人懒,没有改成类,这样代码会更精减些。适合像我这样的小白学习。
接下来准备写怎么能加快像request头部header头,data字段生成的方法,欢迎大佬指正。

* Serving Flask app 'app' (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: on * Running on http://127.0.0.1:5000 (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 330-024-692 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载使用,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!
初级教程看:https://download.csdn.net/download/dwf1354046363/20818468 9 网络爬虫进阶之 Selenium 篇 9.1 Selenium 简介 . . . . . . . . 9.1.1 Selenium 是什么 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 9.1.2 Selenium 特点 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 9.1.3 基本安装与使用 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 9.1.4 各种浏览器驱动下载地址 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 9.1.5 Selenium 初试 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 9.2 定位元素 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 9.2.1 基本的定位方法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 9.2.2 使用 By 定位 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 9.2.3 定位一组元素 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 9.3 控制浏览器 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 9.3.1 控制浏览器窗口大小 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 9.3.2 控制浏览器后退、前进 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 9.3.3 模拟浏览器刷新 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 9.4 WebDriver 中的常用方法 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 9.5 设置元素等待 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 9.5.1 显式等待 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 9.5.2 隐式等待 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 9.6 多表单切换 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 9.7 多窗口切换 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 9.8 其他操作 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
本套视频教程适合想掌握爬虫技术的学习者,以企业主流版本Python 3.7来讲解,内容包括:Python基础、Urllib、解析(xpath、jsonpath、beautiful)、requests、selenium、Scrapy框架等。针对零基础的同学可以从头学起,具备Python基础的同学建议直接从第52集开始学习爬虫部分视频。教程中示例了多种网站的爬取,包括设计类网站、招聘类网站、图书类网站、图片素材类网站等,还讲解了验证码的破解和常见的打码平台使用。 视频大小:19.8 G
重要的事情放在最前面,请您一定看仔细: 本文以学习和交流爬虫技术,尤其是JS逆向技术为目的。大家重点学习其中的思路和过程,请不要模仿爬取文中举例的网站,如果要爬取,请爬取不要超过3页,否则后果自负。 Crawl It! 爬虫技术万年第一步——分析网页结构和数据加载方式: 结果一打开网页是这样的,看来网站维护人员也过节去了。那就假期回来再继续写吧。。 本文暂停于 2021.2.11 上午 10:25 春节假期一眨眼就过完了,我又回来“搞事情”了! 打开url,显示数据页面。 通过Chrome
大数据时代下,爬虫技术逐渐成为一套完整的系统性工程技术,涉及的知识面广,平台多,技术越来越多样化,对抗性也日益显著。 大家可以参考一下学习路线,看看自己需要对哪些知识进行补充。 爬虫逆向学习路线学习路线总结系统提高加密算法特征和实现传输协议和通讯协议各种验证码识别方法个人汇总专栏爬虫逆向社区 学习路线总结 基础学习路线总结: 主语言基础语法 常用网络请求库、解析库 常用抓包工具 自动化工具库 流行采集框架 多进程、多线程、协程、分布式爬虫 采集器管理 Js逆向学习路线总结: 浏览器构造、基础语法、作
3.对加密方法名解密,减少对鼠标伤害 4.删除无限debugger 备注:index.html为原始页面代码,index2.html为处理后页面代码,eval.js为格式化后的evaljs代码,eval2.js为处理后代码 快速阅读: index2.html 搜索call快速找到eval的入口 eval2.js 搜索"$$a[0]"这里是控制流平坦化代码的开始,搜索"$WH(24)"这里是设置无限debugger,很多个 商标网已经改版,部分js已经改变
本资源包括30小节,价值2400,爬虫进阶课程 01爬虫的核心知识;02爬虫请求库学习;03数据解析篇;04爬虫神器-Requests请求库;05Requests(二);06Ajax动态数据采集;07selenium自动化工具;08自动化神器pyppeteer学习;09charles抓包工具;10mitmproxy中间人代理;11cookie反爬虫原理;12cookie反爬虫原理(二);13常见数据加密算法;14javascript反爬原理与调试;15爬虫模拟登录原理实践;16网络爬虫案例综合实践;17JavaScript进阶案例实践;18JavaScript逆向OB混淆学习;19基金爬虫综合实践;20文本混淆反爬虫绕过学习;21图形验证码反爬虫;22滑块验证码反爬虫;23scrapy框架学习;24scrapy数据提取;25scrapy核心技巧;26scrapy电商项目实践;27feapder框架学习;28JavaScript逆向爬虫技巧;29分布式爬虫;30爬虫任务部署
无限debugger是一种常见的JavaScript逆向技巧,它可以绕过网站的 JavaScript 代码保护,使得黑客可以在网站上执行任意的 JavaScript 代码。具体步骤如下: 1. 打开浏览器的开发者工具(F12)。 2. 在 Sources 面板中找到要逆向JavaScript 文件,将代码复制到剪贴板中。 3. 点击 Sources 面板上方的 {} 按钮,将代码放入 console 中并执行。 4. 在执行代码的过程中,使用 `debugger;` 语句来暂停代码的执行,使得可以在执行过程中进行调试。 5. 在调试时,使用 `step over` 或 `step into` 等调试命令来单步执行代码,以便查看代码的执行过程和内部变量的值。 6. 如果需要继续执行代码,可以使用 `resume script execution` 命令恢复代码的执行。 需要注意的是,过无限debugger属于非法技术手段,不建议用于非法用途。
记录一个操蛋的报错(FileNotFoundError: Could not find module ‘***_dll.dll‘ (or one of its dependencies).) CSDN-Ada助手: 恭喜您写了第20篇博客!标题看起来很有趣,对于操蛋的报错问题,您对读者们的帮助一定很大。感谢您在博客中记录并解决了这个FileNotFoundError问题,这对那些遇到相同错误的读者来说一定非常有用。接下来,如果可能的话,您可以考虑分享一些关于如何避免类似错误的经验或者提供一些常见错误的解决方案。这样的创作建议可以帮助更多的开发者们更好地应对类似问题。希望您能够继续坚持写作,我们期待着您下一篇博客的发布! 猿人学第二届第一题解题小记 打转的猫咪: 是的 webpack打包 猿人学第二届第一题解题小记 Hann Yang: JS全盘复制到WebStorm,折叠代码观察,有点像webpack打包 某逆向天花板之路AST学习 Hann Yang: 转成case需要注意的是为什么每句后面有一个break需要理解 全套爬虫进阶实战之某壁纸(绕过无限debugger,逆向,滑块,爬虫) 打转的猫咪: 哦哦 哪可能反爬又升级了 记录一个操蛋的报错(FileNotFoundError: Could not find module ‘***_dll.dll‘ (or one of its dependencies).) 某逆向天花板之路AST学习 记录一个操蛋的报错(FileNotFoundError: Could not find module ‘***_dll.dll‘ (or one of its dependencies).) 某逆向天花板之路AST学习