数据收集-抓包与反爬
抓包与反爬
- 常见的反爬手段
- 反爬原因
- 反爬常见概念
- 反爬的三个方向
- 基于身份识别进行反爬
- 通过headers字段来反爬
- 通过请求参数来反爬
- 常见基于爬虫行为进行反爬
- 基于请求频率或总请求数量
- 根据爬取行为进行反爬
- 基于数据加密进行反爬
- 对响应中含有的数据进行特殊化处理
- 验证码处理
- 图片验证码
- 图片识别引擎
- tesseract
- 图片识别引擎环境的安装
- 图片识别引擎的使用
- 图片识别引擎的使用扩展
- 打码平台
- 常见的打码平台
- 云打码的使用
- 常见的验证码的种类
- chrome浏览器使用方法
- 新建隐身窗口
- network的更多功能
- Perserve log
- filter过滤
- 观察特定种类的请求
- 寻找登录接口
- JS解析
- 确定js的位置
- 观察按钮的绑定js事件
- 通过search all file 来搜索
- 观察js的执行过程
- js2py的使用
- 具体代码
常见的反爬手段
反爬原因
爬虫占总PV(PV是指页面的访问次数,每打开或刷新一次页面,就算做一个pv)比例较高,这样浪费钱(尤其是三月份爬虫)。
公司可免费查询的资源被批量抓走,丧失竞争力,这样少赚钱。
状告爬虫成功的几率小
反爬常见概念
- 爬虫:使用任何技术手段,批量获取网站信息的一种方式。关键在于批量。
- 反爬虫:使用任何技术手段,阻止别人批量获取自己网站信息的一种方式。关键也在于批量。
- 误伤:在反爬虫的过程中,错误的将普通用户识别为爬虫。误伤率高的反爬虫策略,效果再好也不能用。
- 拦截:成功地阻止爬虫访问。这里会有拦截率的概念。通常来说,拦截率越高的反爬虫策略,误伤的可能性就越高。因此需要做个权衡。
- 资源:机器成本与人力成本的总和。
反爬的三个方向
- 基于身份识别进行反爬
- 基于爬虫行为进行反爬
- 基于数据加密进行反爬
基于身份识别进行反爬
通过headers字段来反爬
1.1 通过headers中的User-Agent字段来反爬
- 反爬原理:爬虫默认情况下没有User-Agent,而是使用模块默认设置
- 解决方法:请求之前添加User-Agent即可;更好的方式是使用User-Agent池来解决(收集一堆User-Agent的方式,或者是随机生成User-Agent)
1.2 通过referer字段或者是其他字段来反爬
- 反爬原理:爬虫默认情况下不会带上referer字段,服务器端通过判断请求发起的源头,以此判断请求是否合法
- 解决方法:添加referer字段
1.3 通过cookie来反爬
- 反爬原因:通过检查cookies来查看发起请求的用户是否具备相应权限,以此来进行反爬
- 解决方案:进行模拟登陆,成功获取cookies之后在进行数据爬取
通过请求参数来反爬
2.1 通过从html静态文件中获取请求数据(github登录数据)
- 反爬原因:通过增加获取请求参数的难度进行反爬
- 解决方案:仔细分析抓包得到的每一个包,搞清楚请求之间的联系
2.2 通过发送请求获取请求数据
- 反爬原因:通过增加获取请求参数的难度进行反爬
- 解决方案:仔细分析抓包得到的每一个包,搞清楚请求之间的联系,搞清楚请求参数的来源
2.3 通过js生成请求参数
- 反爬原理:js生成了请求参数
- 解决方法:分析js,观察加密的实现过程,通过js2py获取js的执行结果,或者使用selenium来实现
2.4 通过验证码来反爬
- 反爬原理:对方服务器通过弹出验证码强制验证用户浏览行为
- 解决方法:打码平台或者是机器学习的方法识别验证码,其中打码平台廉价易用,更值得推荐
常见基于爬虫行为进行反爬
基于请求频率或总请求数量
1.1 通过请求ip/账号单位时间内总请求数量进行反爬
- 反爬原理:正常浏览器请求网站,速度不会太快,同一个ip/账号大量请求了对方服务器,有更大的可能性会被识别为爬虫
- 解决方法:对应的通过购买高质量的ip的方式能够解决问题/购买个多账号
1.2 通过同一ip/账号请求之间的间隔进行反爬
- 反爬原理:正常人操作浏览器浏览网站,请求之间的时间间隔是随机的,而爬虫前后两个请求之间时间间隔通常比较固定同时时间间隔较短,因此可以用来做反爬
- 解决方法:请求之间进行随机等待,模拟真实用户操作,在添加时间间隔后,为了能够高速获取数据,尽量使用代理池,如果是账号,则将账号请求之间设置随机休眠
1.3 通过对请求ip/账号每天请求次数设置阈值进行反爬
- 反爬原理:正常的浏览行为,其一天的请求次数是有限的,通常超过某一个值,服务器就会拒绝响应
- 解决方法:对应的通过购买高质量的ip的方法/多账号,同时设置请求间随机休眠
根据爬取行为进行反爬
通常在爬取步骤上做分析
2.1 通过js实现跳转来反爬
- 反爬原理:js实现页面跳转,无法在源码中获取下一页url
- 解决方法: 多次抓包获取条状url,分析规律
2.2 通过蜜罐(陷阱)获取爬虫ip(或者代理ip),进行反爬
- 反爬原理:在爬虫获取链接进行请求的过程中,爬虫会根据正则,xpath,css等方式进行后续链接的提取,此时服务器端可以设置一个陷阱url,会被提取规则获取,但是正常用户无法获取,这样就能有效的区分爬虫和正常用户
- 解决方法: 完成爬虫的编写之后,使用代理批量爬取测试/仔细分析响应内容结构,找出页面中存在的陷阱
2.3 通过假数据反爬
- 反爬原理:向返回的响应中添加假数据污染数据库,通常家属剧不会被正常用户看到
- 解决方法: 长期运行,核对数据库中数据同实际页面中数据对应情况,如果存在问题/仔细分析响应内容
2.4 阻塞任务队列
- 反爬原理:通过生成大量垃圾url,从而阻塞任务队列,降低爬虫的实际工作效率
- 解决方法: 观察运行过程中请求响应状态/仔细分析源码获取垃圾url生成规则,对URL进行过滤
2.5 阻塞网络IO
- 反爬原理:发送请求获取响应的过程实际上就是下载的过程,在任务队列中混入一个大文件的url,当爬虫在进行该请求时将会占用网络io,如果是有多线程则会占用线程
- 解决方法: 观察爬虫运行状态/多线程对请求线程计时/发送请求钱
2.6 运维平台综合审计
- 反爬原理:通过运维平台进行综合管理,通常采用复合型反爬虫策略,多种手段同时使用
- 解决方法: 仔细观察分析,长期运行测试目标网站,检查数据采集速度,多方面处理
基于数据加密进行反爬
对响应中含有的数据进行特殊化处理
通常的特殊化处理主要指的就是css数据偏移/自定义字体/数据加密/数据图片/特殊编码格式等
1.1 通过自定义字体来反爬
- 反爬思路: 使用自有字体文件
- 解决思路:切换到手机版/解析字体文件进行翻译
1.2 通过css来反爬
反爬思路:源码数据不为真正数据,需要通过css位移才能产生真正数据
解决思路:计算css的偏移
1.3 通过js动态生成数据进行反爬反爬原理:通过js动态生成
解决思路:解析关键js,获得数据生成流程,模拟生成数据
1.4 通过数据图片化反爬
- 58同城短租](https://baise.58.com/duanzu/38018718834984x.shtml)
- 解决思路:通过使用图片解析引擎从图片中解析数据
1.5 通过编码格式进行反爬
- 反爬原理: 不适用默认编码格式,在获取响应之后通常爬虫使用utf-8格式进行解码,此时解码结果将会是乱码或者报错
- 解决思路:根据源码进行多格式解码,或者真正的解码格式
验证码处理
图片验证码
- 验证码(CAPTCHA)是一种区分用户是计算机还是人的公共全自动程序。
- 验证码的作用:防止恶意破解密码、刷票、论坛灌水、刷页。
图片验证码的处理方案 - 手动输入(input)
- 图像识别引擎解析:使用光学识别引擎处理图片中的数据,常用于图片数据提取
- 打码平台: 爬虫常用的验证码解决方案
图片识别引擎
OCR(Optical Character Recognition)是指对文本资料进行扫描成图像文件,然后对图像文件进行分析处理,自动识别获取文字信息及版面信息的软件。
tesseract
- Tesseract,一款由HP实验室开发由Google维护的开源OCR引擎,特点是开源,免费,支持多语言,多平台。
- 项目地址:https://github.com/tesseract-ocr/tesseract
图片识别引擎环境的安装
- 引擎的安装
- mac环境下直接执行命令
brew install --with-training-tools tesseract - windows环境下的安装
可以通过exe安装包安装,下载地址GitHub项目中的wiki。安装完成后,将Tesseract 执行文件的目录加入到PATH中,方便后续调用。 - linux环境下的安装
sudo apt-get install tesseract-ocr
- Python库的安装
- PIL用于打开图片文件
pip/pip3 install pillow - pytesseract模块用于从图片中解析数据
pip/pip3 install pytesseract
图片识别引擎的使用
- 通过pytesseract模块的 image_to_string 方法就能将打开的图片文件中的数据提取成字符串数据
from PIL import Image
import pytesseract
im = Image.open()
result = pytesseract.image_to_string(im)
print(result)
图片识别引擎的使用扩展
- tesseract简单使用与训练
- 其他ocr平台
微软Azure 图像识别:https://azure.microsoft.com/zh-cn/services/cognitive-services/computer-vision/
有道智云文字识别:http://aidemo.youdao.com/ocrdemo
阿里云图文识别:https://www.aliyun.com/product/cdi/
腾讯OCR文字识别:https://cloud.tencent.com/product/ocr
打码平台
常见的打码平台
- 云打码:http://www.yundama.com/
能够解决通用的验证码识别 - 极验验证码智能识别辅助:http://jiyandoc.c2567.com/
能够解决复杂验证码的识别
云打码的使用
- 云打码官方接口
云打码平台提供,实现了两个方法: - indetify:传入图片的响应二进制数即可
- indetify_by_filepath:传入图片的路径即可识别
自己配置
username = 'whoarewe' # 用户名
password = '***' # 密码
appid = 4283 # appid
appkey = '02074c64f0d0bb9efb2df455537b01c3' # appkey
codetype = 1004 # 验证码类型
云打码官方提供的api
#yundama.py
import requests
import json
import timeclass YDMHttp:apiurl = 'http://api.yundama.com/api.php'username = ''password = ''appid = ''appkey = ''def __init__(self, username, password, appid, appkey):self.username = usernameself.password = passwordself.appid = str(appid)self.appkey = appkeydef request(self, fields, files=[]):response = self.post_url(self.apiurl, fields, files)response = json.loads(response)return responsedef balance(self):data = {'method': 'balance', 'username': self.username, 'password': self.password, 'appid': self.appid,'appkey': self.appkey}response = self.request(data)if (response):if (response['ret'] and response['ret'] < 0):return response['ret']else:return response['balance']else:return -9001def login(self):data = {'method': 'login', 'username': self.username, 'password': self.password, 'appid': self.appid,'appkey': self.appkey}response = self.request(data)if (response):if (response['ret'] and response['ret'] < 0):return response['ret']else:return response['uid']else:return -9001def upload(self, filename, codetype, timeout):data = {'method': 'upload', 'username': self.username, 'password': self.password, 'appid': self.appid,'appkey': self.appkey, 'codetype': str(codetype), 'timeout': str(timeout)}file = {'file': filename}response = self.request(data, file)if (response):if (response['ret'] and response['ret'] < 0):return response['ret']else:return response['cid']else:return -9001def result(self, cid):data = {'method': 'result', 'username': self.username, 'password': self.password, 'appid': self.appid,'appkey': self.appkey, 'cid': str(cid)}response = self.request(data)return response and response['text'] or ''def decode(self, filename, codetype, timeout):cid = self.upload(filename, codetype, timeout)if (cid > 0):for i in range(0, timeout):result = self.result(cid)if (result != ''):return cid, resultelse:time.sleep(1)return -3003, ''else:return cid, ''def post_url(self, url, fields, files=[]):# for key in files:# files[key] = open(files[key], 'rb');res = requests.post(url, files=files, data=fields)return res.text username = 'whoarewe' # 用户名password = '***' # 密码appid = 4283 # appidappkey = '02074c64f0d0bb9efb2df455537b01c3' # appkeyfilename = 'getimage.jpg' # 文件位置codetype = 1004 # 验证码类型# 超时
timeout = 60def indetify(response_content):if (username == 'username'):print('请设置好相关参数再测试')else:# 初始化yundama = YDMHttp(username, password, appid, appkey)# 登陆云打码uid = yundama.login();print('uid: %s' % uid)# 查询余额balance = yundama.balance();print('balance: %s' % balance)# 开始识别,图片路径,验证码类型ID,超时时间(秒),识别结果cid, result = yundama.decode(response_content, codetype, timeout)print('cid: %s, result: %s' % (cid, result))return resultdef indetify_by_filepath(file_path):if (username == 'username'):print('请设置好相关参数再测试')else:# 初始化yundama = YDMHttp(username, password, appid, appkey)# 登陆云打码uid = yundama.login();print('uid: %s' % uid)# 查询余额balance = yundama.balance();print('balance: %s' % balance)# 开始识别,图片路径,验证码类型ID,超时时间(秒),识别结果cid, result = yundama.decode(file_path, codetype, timeout)print('cid: %s, result: %s' % (cid, result))return resultif __name__ == '__main__':pass
常见的验证码的种类
url地址不变,验证码不变
这是验证码里面非常简单的一种类型,对应的只需要获取验证码的地址,然后请求,通过打码平台识别即可url地址不变,验证码变化
这种验证码的类型是更加常见的一种类型
通过cookie来验证之前获取的验证码和最后提交的验证码是同一个验证码。在请求页面,请求验证码,提交验证码的到时候需要保证cookie的一致性,对此可以使用requests.session来解决。
chrome浏览器使用方法
新建隐身窗口
使用隐身窗口,首次打开网站,不会带上cookie,能够观察页面的获取情况,包括对方服务器如何设置cookie在本地
network的更多功能
Perserve log
默认情况下,页面发生跳转之后,之前的请求url地址等信息都会消失,勾选perserve log后之前的请求都会被保留。
filter过滤
在url地址很多的时候,可以在filter中输入部分url地址,对所有的url地址起到一定的过滤效果。
观察特定种类的请求
默认是选择的all,即会观察到所有种类的请求
比如常见的选项:
- XHR:大部分情况表示ajax请求
- JS:js请求
- CSS:css请求
不清楚一个请求是否为ajax请求的时候,直接选择all,从前往后观察即可,其中js,css,图片等不去观察即可。
寻找登录接口
- 寻找action对的url地址
这个地址就是在登录的form表单中action对应的url地址,进行表单提交的地址,对应的,提交的数据,仅仅需要:用户名的input标签中,name的值作为键,用户名作为值,密码的input标签中,name的值作为键,密码作为值即可 - 通过抓包寻找登录的url地址
手机版中,依然有参数,但是参数的个数少一些
总结
- 使用隐身窗口的主要目的是为了避免首次打开网站携带cookie的问题
- chrome的network中,perserve log选项能够在页面发生跳转之后任然能够观察之前的请求
- 确定登录的地址有两种方法:
- 寻找from表单action的url地址
- 通过抓包获取
JS解析
确定js的位置
观察按钮的绑定js事件
通过search all file 来搜索
观察js的执行过程
找到js的位置之后,通过观察js的位置,找到js具体在如何执行,可以通过python程序来模拟js的执行,或者是使用类似js2py直接把js代码转化为python程序去执行。
- 1:继续执行到下一个断点
- 2:进入调用的函数中
- 3:从调用的函数中跳出来
js2py的使用
- js2py是一个js的翻译工具,也是一个通过纯python实现的js的解释器,github上源码与示例
- js的执行思路:使用python程序实现js的执行时候,需要观察的js的每一个步骤,非常麻烦。所以更多的时候我们会选择使用类似js2py的模块去执行js。
- 具体实现
- 定位进行登录js代码
formSubmit: function() {var e, t = {};$(".login").addEventListener("click", function() {t.phoneNum = $(".phonenum").value,t.password = $(".password").value,e = loginValidate(t),t.c1 = c1 || 0,e.flag ? ajaxFunc("get", "http://activity.renren.com/livecell/rKey", "", function(e) {var n = JSON.parse(e).data;if (0 == n.code) {t.password = t.password.split("").reverse().join(""),setMaxDigits(130);var o = new RSAKeyPair(n.e,"",n.n), r = encryptedString(o, t.password);t.password = r,t.rKey = n.rkey} elsetoast("公钥获取失败"),t.rKey = "";ajaxFunc("post", "http://activity.renren.com/livecell/ajax/clog", t, function(e) {var e = JSON.parse(e).logInfo;0 == e.code ? location.href = localStorage.getItem("url") || "" : toast(e.msg || "登录出错")})}) : toast(e.msg)})}
js代码解析:
- 我们要登录需要对密码进行加密和获取rkey字段的值
- rkey字段的值,直接发送请求rkey请求就可以获得
- 密码是先反转然后使用RSA进行加密, js代码很复杂, 通过在python中执行js来实现
实现思路:
- 使用session发送rKey获取登录需要信息
- url: http://activity.renren.com/livecell/rKey
- 方法: get
- 根据获取信息对密码进行加密
2.1 准备用户名和密码
2.2 使用js2py生成js的执行环境:context
2.3 拷贝使用到js文件的内容到本项目中
2.4 读取js文件的内容,使用context来执行它们
2.5 向context环境中添加需要数据
2.6 使用context执行加密密码的js字符串
2.7 通过context获取加密后密码信息 - 使用session发送登录请求
- URL: http://activity.renren.com/livecell/ajax/clog
- 请求方法: POST
- 数据:
phoneNum: xxxxxxx
password: (加密后生产的)
c1: 0
rKey: rkey请求获取的
具体代码
提前下载js文件到本地:
BigInt.js
RSA.js
Barrett.js
import requests
import json
import js2py# - 实现思路:
# - 使用session发送rKey获取登录需要信息
# - url: http://activity.renren.com/livecell/rKey
# - 方法: get
# 获取session对象
session = requests.session()
headers = {"User-Agent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Mobile Safari/537.36","X-Requested-With": "XMLHttpRequest","Content-Type":"application/x-www-form-urlencoded"
}
# 设置session的请求头信息
session.headers = headersresponse = session.get("http://activity.renren.com/livecell/rKey")
# print(response.content.decode())
n = json.loads(response.content)['data']# - 根据获取信息对密码进行加密
# - 准备用户名和密码
phoneNum = "131..."
password = "****"
# - 使用js2py生成js的执行环境:context
context = js2py.EvalJs()
# - 拷贝使用到js文件的内容到本项目中
# - 读取js文件的内容,使用context来执行它们
with open("BigInt.js", 'r', encoding='utf8') as f:context.execute(f.read())with open("RSA.js", 'r', encoding='utf8') as f:context.execute(f.read())
with open("Barrett.js", 'r', encoding='utf8') as f:context.execute(f.read())# - 向context环境中添加需要数据
context.t = {'password': password}
context.n = n
# - 执行加密密码的js字符
js = '''t.password = t.password.split("").reverse().join(""),setMaxDigits(130);var o = new RSAKeyPair(n.e,"",n.n), r = encryptedString(o, t.password);'''
context.execute(js)
# - 通过context获取加密后密码信息
# print(context.r)
password = context.r
# - 使用session发送登录请求
# - URL: http://activity.renren.com/livecell/ajax/clog
# - 请求方法: POST
# - 数据:
# - phoneNum: 15565280933
# - password: (加密后生产的)
# - c1: 0
# - rKey: rkey请求获取的
data = {'phoneNum': '131....','password': password,'c1':0,'rKey':n['rkey']
}# print(session.headers)
response = session.post("http://activity.renren.com/livecell/ajax/clog", data=data)
print(response.content.decode())# 访问登录的资源
response = session.get("http://activity.renren.com/home#profile")
print(response.content.decode())
小结
- 通过在chrome中观察元素的绑定事件可以确定js
- 通过在chrome中search all file 搜索关键字可以确定js的位置
- 观察js的数据生成过程可以使用添加断点的方式观察
- js2py的使用
- 需要准备js的内容
- 生成js的执行环境
- 在执行环境中执行js的字符串,传入数据,获取结果
数据收集-抓包与反爬相关推荐
- 五 Pathon爬虫之抓包与反爬以及反爬解决方案
一 介绍 1.1 服务器反爬的原因 爬虫占总PV(PV是指页面的访问次数,每打开或刷新一次页面,就算做一个pv)比例较高,这样浪费钱(尤其是三月份爬虫). 三月份爬虫是个什么概念呢?每年的三月份我们会 ...
- 实习周记1(sdk了解、fiddler抓包、反编译工具)
文章目录 本周知识清单 SDK为游戏提供哪些功能 什么是SDK SDK在游戏中提供的功能 SDK技术特点 简洁易用 稳定 高效 模块化开发 组件化开发 插件化开发 SDK各功能模块如何实现的 制作SD ...
- fiddler不能抓取浏览器数据_抓包软件 Fiddler 了解一下?
学会如何抓包,是爬虫的必备技能,甚至可以说,不会抓包就等同于不会爬虫. 那我们怎样抓包呢?如果直接抓取浏览器上的内容,可以直接使用开发者工具进行抓包,但有个局限,只能抓浏览器的,功能也没有多少.还可以 ...
- android 分享微信小程序失败,从一次失败的微信小程序抓包、反编译经历中学习反思...
某天看到群里某个朋友说某小程序抓不到包,我突然就来了兴趣,我也试着分析了下这个小程序,名字我就不说了,本着我个人兴趣分析学习的目的. 我用安卓和IOS,以及charles和fiddler都试了,还真的 ...
- python 抓包解析数据_Python抓包并解析json爬虫的完整实例代码
Python抓包并解析json爬虫 在使用Python爬虫的时候,通过抓包url,打开url可能会遇见以下类似网址,打开后会出现类似这样的界面,无法继续进行爬虫: 例如: 需要爬取网页中第二页的数据时 ...
- 使用Fiddler对IPhone手机的应用数据进行抓包分析
原文出自: http://www.cr173.com/html/20064_1.html Fiddler能捕获ISO设备发出的请求,比如IPhone, IPad, MacBook. 等等苹果的设备. ...
- 抓包,反抓包,反反抓包
前言 当年还在学校的时候,就接触到了抓包,当时还在贴吧写了一篇小白文来误导小白(不是.但当时的自己还没接触到逆向,竟然对抓包没有提起兴趣,说到底又是太年轻,不懂事.时至今日,才发现它是安全人员必须要掌 ...
- Android安卓进阶之——一文带你了解抓包和反抓包
今天主要跟大家介绍一下Android的抓包和防止抓包 介绍两款抓包工具,Profiter和Charles. 工具环境: Android Studio 4.2.2 手机Google Pixel 3XL ...
- api 数据 App 抓包工具 fiddler
from : http://www.heyuan110.com/2015/06/17/App抓包工具fiddler/ App抓包工具fiddler Charles fiddler 抓包 确保安装 ...
最新文章
- ROW_NUMBER() OVER 函数的用法
- git push时提示:更新被拒绝,因为您当前分支的最新提交落后于其对应的远程分支
- memcached全面剖析 –3.memcached的删除机制和发展方向
- 执行sql语句_SQL查询语句的执行顺序解析
- python使用curve_fit拟合任意分布
- 【Java从0到架构师】SpringMVC - 特殊的请求参数
- 广义表取表头表尾_数据结构广义表的递归算法
- pythonchallenge之C++学习篇-01
- 工具栏的打印图标不见了_XP操作系统工具栏右边的打印机图标不见了
- 获取openwrt mac地址
- c语言calloc和malloc,使用malloc()、calloc()、free()和realloc()在C中进行动态内存分配
- XML Shema 笔记整理(1)
- matlab最基础教程(四):常用的系统自带函数,符号变量与字符串篇
- 从UAP-Studio中导出项目并且部署到服务器上
- 微信小程序API 文件·文件管理器
- html手机端下拉菜单代码,jQuery手机移动端下拉列表选择代码
- 进程与线程基础day02--------守护进程、系统日志、文件锁
- 量化交易之vnpy篇 - 几种同步发单模式(中金所股指锁仓模式、最小单边轧差操作模式、双边同步模式,净头寸模式)
- 新学问教育php,神墨教师的教育梦——让梦想与爱同行
- 数字 IC 设计、FPGA 设计秋招笔试题目、答案、解析(5)2021 华为海思(下)