1. 先弄懂PT站流量体系和反作弊的整体思路
前几年我接手维护一个小型PT站的时候,三天两头收到用户举报说有人刷上传量。站子不大,用户几百人,可分享率排行榜前面的几个ID,怎么看怎么不对劲。一开始我也以为直接写几条SQL查查异常ratio就能搞定,结果发现远远没那么简单。真正把问题压下去,是在我把流量链路从头到尾捋了一遍、又从协议层到算法层做了一套检测机制之后。这篇文章里很多判断,就是那段时间从抓包、日志和数据库里一点点趟出来的。
很多做站的朋友一提到“流量作弊检测”就觉得是个报表问题——谁家的上传量突然暴涨,查一下,封了完事。真不是这样。PT站的流量统计是一个分布式协作体系:客户端上报什么,Tracker就记什么,数据库就存什么,最后才折算成分享率、做种时长这些考核指标。你只看结果报表,根本看不到作弊者是怎么把数字改出来的。所以想搞懂反作弊,得先搞清楚流量从客户端到统计报表之间走了多少层、每一层有哪些环节可能被篡改、哪些环节又可以通过交叉验证发现异常。
先说一个根本问题:PT站和公开BT到底差在哪。公开BT网络里大家互不认识,Tracker只负责告诉每个peer“你该连谁”,具体上传下载数字是peer之间自己记的,没人在意真假。PT站完全不同,它有准入机制、有分享率考核、有用户等级体系,每个账号靠passkey绑定身份,Tracker记录的上传下载量直接决定你能不能继续用这个站。正因为数字有实际价值,才有人铤而走险去改这些数字。理解了这一点,你就明白为什么所有检测手段最后都要回到一条主线上:验证“客户端上报的数据”和“客观上发生的真实传输”是否一致。
再往后是整个统计链路。一次完整的流量回报大概是这样的:客户端每过一段时间,常见是30分钟左右,或者在做种/下载状态发生变化时,向Tracker发一个announce请求。请求里带着用户身份标识passkey、种子标识info_hash、客户端自报的累计上传量、下载量、剩余量等参数。Tracker收到后更新数据库里的用户统计,前端再把统计展示成分享率、魔力值之类。也就是说,Tracker其实是“全凭一张嘴”——客户端报多少,它就信多少。如果客户端那边改了上报值,后端拿到的就是假数据。所以反作弊的核心工作,就是给这张“嘴”加上各种验证机制,让它不能乱说话。
顺着这个思路,我在实际部署检测方案时把工作分成了三层。第一层是协议层,检查announce报文本身是否合规、passkey与peer_id是否匹配、上报参数数值是否自洽。第二层是行为层,建立用户和peer的行为画像,从请求频率、速率曲线、会话时长这些维度判断“像个真人还是像个脚本”。第三层是数据层,把数据库里不同来源的数据做交叉比对。比如A给B传了多大的量,B那边有没有对应下载记录,两边对不上就说明至少有一方在说谎。三层缺一不可:只查协议层会被精心构造的假报文绕过,只看行为层容易误伤正常用户,单靠数据层交叉验证又没法定位到底是谁先动的手。三层配合起来,才能从报文到行为再到数据全链路形成闭环。
这一节是全文的定调,后面所有章节都会围绕这三层展开。下一节先讲协议层,也就是Tracker协议本身到底有哪些可以被动手脚的地方。
2. Tracker协议核心机制与作弊面分析
2.1 announce请求里那些数字到底怎么用
要做检测,先得看懂Tracker协议里最核心的一个请求——announce。这个请求正常情况下是HTTP GET,也有少量客户端用UDP或HTTPS发送。URL路径后面挂一串参数,服务端拿到这些参数后返回一组peer列表,告诉客户端下一步该去连谁。
我来还原一个典型的announce请求长什么样,参数顺序和编码方式每个站的实现略有不同,但核心字段是一致的:
GET /announce.php?passkey=8f3a9c2e6d5b44a1... &info_hash=%9E%8A%... &peer_id=%2DTR3000%2D... &port=51413 &uploaded=21474836480 &downloaded=0 &left=0 &event=completed &numwant=200 &compact=1 HTTP/1.1逐字段拆开看。passkey是PT站在URL里附加的用户身份标识,服务端拿到passkey就知道这是哪个账号在汇报流量。info_hash是种子信息的SHA1哈希,用于锁定是哪个资源。peer_id是客户端生成的唯一标识,格式通常是“-客户端前缀-随机数”。比如qBittorrent常见的是“-qBxxxx-”,Deluge是“-DExxxx-”,Transmission是“-TRxxxx-”。port是客户端监听的端口。uploaded和downloaded是客户端自报的累计值,单位是字节。left是剩余未下载字节数,为0表示已经下载完整。event表示当前状态变化,常见的有started、completed、stopped,当然也可以是空值。
这里最关键的一点在于:Tracker协议本身对uploaded、downloaded、left这些数值并没有内建的校验机制。协议设计者假定客户端都是“诚实的”,上报多少就是多少。在公开BT里这个假定基本成立,因为没人有动机去虚报数据。但在PT站里,分享率是要考核的,这就产生了一个巨大的动机漏洞:只要改一改上报参数,就能让Tracker以为你做种传了很多数据出去。
用后端逻辑验证一下,你马上能明白问题出在哪儿。Tracker收到announce后,通常会把数据库里该用户对应种子的上传记录更新为请求中uploaded的数值;下载量则用“当前累计值减历史累计值”来计算差值,或直接取downloaded字段。如果客户端每次都把uploaded写得比上一次大很多,数据库里的上传总量就会一路虚高,分享率也跟着上去了。作弊者真正需要处理的,只是怎么让这些增长看起来不那么突兀。
2.2 常见作弊手法的技术路线拆解
结合我看到的实际案例,PT站常见的流量作弊大致有这么几类,每一类的技术路线都不同,检测点也不同。
第一类是纯改上报值,也是最原始的一种。作弊者直接修改客户端的联网回报逻辑,或者用脚本模拟HTTP请求,向Tracker提交伪造的uploaded数值。这种做法的特点是实现成本最低,但痕迹非常明显。因为脚本不会像真实客户端那样做完整的BT协议交互,它往往只向Tracker发announce,却建立不了多少真实peer连接,也不会有对应的下载记录。只要数据库里上报上传量和实际peer连接记录差异过大,基本可以判定造假。
第二类是搞一堆假peer互相刷。作弊者租用或自己控制一批机器,或者在一台机器上开很多虚拟机、容器,伪造出大量peer_id,彼此之间建立连接、互传数据。从Tracker的视角看,确实存在这些peer,也确实有上传和下载发生,但这些流量完全在作弊者自己的网络环境里循环,对站内其他用户没有任何贡献。这种手法比第一种复杂,但检测起来可以通过IP段分析和peer_id规律分析发现。真实用户散布在各地,IP段是分散的;刷量集群的IP往往集中在某几个C段,peer_id的命名规律也高度雷同。
第三类是“秒下”型作弊。很多有做种时长考核的站里特别常见。作弊者下载一个大文件,正常情况怎么也要几小时,但他通过某种手段让Tracker认为这个种子瞬间下载完成,left直接变成0,于是做种时长和额外积分开始累计。这种手段往往是把download完成时间和种子的发布时间做对比,如果间隔小到不合理,或者同一个账号在一批种子上都出现了秒下完,基本上就是脚本在刷。
第四类是身份混用型,也就是一个passkey在同一时间出现在多个不同IP、多台设备上。正常用户确实可能在不同设备上登录同一个账号,但并发量级很小,而且各设备的流量曲线应当有自然的错峰。如果同一个passkey同时挂了几十个peer,每个peer的上传速率都在匀速增长,那大概率是有人在用同一个账号的凭据做多机并发刷量。
2.3 面对这些手段,检测点该布置在哪一层
针对上面的四类手段,我在设计检测点时的思路是:凡是协议报文层面可以识别的,优先在协议层识别;协议层识别不了的,交给行为层;行为层再分不清的,最后落到数据层交叉验证。
协议层可以做的检查包括几个方面。一是参数自洽性检查:left为0却还不断有大量下载上报,正常吗?event为completed后紧接着又上报started并伴随上传量陡增,合理吗?二是passkey与peer_id的关联检查:同一个passkey在一小时内在多少个不同peer_id上出现过?正常客户端peer_id只在客户端配置变化时才变,频繁变化就是脚本特征。三是流量数值单调性和增量检查:uploaded必须是单调不减的。如果发现某次上报的uploaded比上一次还小,那就是数据被篡改,或是多客户端竞争同一统计导致的数值撕裂,只要加了日志比对就能发现。
行为层则更多关注“像不像真人”。真人用户在一天内的活跃时段是有规律的,上传速率曲线会有拥塞、波动、快慢交替,而脚本刷量往往呈现匀速直线或非常规律的周期波动。把这些特征量化后,可以作为异常评分的一部分。
数据层的核心是交叉验证。两个peer之间发生过真实传输,那么双方各自的客户端都会记一笔账。A报给Tracker说我向B上传了X字节,B这一侧也应当有对应的我从A下载了约X字节的记录。哪怕因为协议实现差异会有少量误差,量级也应该对得上。如果大量记录对不上,就说明其中一方在虚报。这一层配合数据库的定时对账任务来做,可以非常有效地发现前两类作弊。
就我自己的经验来说,协议层检查实现成本最低、误伤最少,优先部署;行为层适合做长期风控告警;数据层交叉验证最可靠,但需要维护对账任务,对数据库压力也稍大,适合在确认疑点后当作实锤手段。下一节我会重点讲行为层背后的算法细节,因为这才是从“看到异常”到“自动识别异常”的分水岭。
3. 反欺诈算法如何把异常“揪”出来:从带宽校验到行为画像
3.1 物理约束校验:先看数据是否符合带宽现实
这套思路说白了就是“用物理世界的上限去卡虚拟世界的牛皮”。无论客户端怎么上报,数据终究要通过真实的网络链路传输,而你所能掌握的最可靠的物理约束,就是带宽和存储。
举一个很简单的例子:一台服务器托管在10Mbps带宽的机房里,无论它怎么优化,理论上每秒最多能传出的流量也就10Mbps左右,也就是约1.25MB/s。如果这台机器上报的上传速率长期高于这个上限,那就只有两种可能——要么机房悄悄给你加了带宽但你没被告知,要么上报数据是假的。显然第二种可能性大得多。
在工程实现上,我会给每个peer或者每个passkey维护一个“带宽上限估计”,来源包括注册资料里的宽带套餐、Tracker侧观察到的历史峰值速率、以及对端IP所在网段的常规能力。每次收到announce时,把上涨的速率与上限做对比,超过合理阈值,比如高出30%且持续多个周期,就触发告警。
这里有个容易被忽略的细节:带宽是共享的,不是每台设备独享全部。家用宽带的上传速率往往被运营商限制得很低,比如下行500Mbps、上行只有30Mbps。所以如果你看到某个家用宽带用户在短短几分钟内上报了几十GB的上传量,哪怕他的下行带宽再大,上行物理上就不支持这个量级。这种“下行大、上行小”的常见套餐特征,本身就是一个很好的物理约束参考。
3.2 时间序列与行为画像:识别机器刷量的痕迹
物理约束能拦掉最粗暴的作弊,但对那些“把速率编得刚刚好在带宽范围内”的手法就不够用了。这个层级需要引入时间序列分析。
正常用户的上传速率曲线,仔细看是有生命力的。它会随着peer加入、离开而上下浮动,会受网络拥塞影响出现毛刺,会因为你中途打开网页占用了带宽而掉下去。这些变化是随机的、不可预期的。而脚本模拟的速率曲线,哪怕加入随机抖动,也往往存在某些统计特征:比如相邻时间点的差值非常平滑,抖动幅度始终在一个很窄的区间,或者波动的周期刚好等于脚本里设置的随机种子周期。
我在工程上用过两个比较简单但够用的统计量。第一个是相邻announce间隔的变异系数。正常客户端的announce间隔受Tracker响应时间、客户端自身的重试策略影响,会有一定波动,而脚本往往使用固定间隔,变异系数会异常低。第二个是速率序列的自相关性。如果同一条速率曲线里每隔N个点就出现一次相似的形态,大概率是程序循环生成的。
除了速率曲线,行为画像还包括不少维度。会话时长分布、一天内的活跃窗口、新种子偏好、下载后做种的比例、同时对多少个种子发起请求……把每个用户的这些指标收集起来,建立行为基线,偏离基线越远,异常分越高。比如一个平时只下电影的用户,突然一晚上同时刷了200个种子,每个种子都是秒下完,这个行为模式本身就很有说服力。
3.3 交叉验证与信誉系统:让作弊者持续承压
如果说前面两层是你对单个用户“自说自话”的验证,那交叉验证就是把多个数据源拼在一起对表,看谁在说谎。
最典型的就是双端对账。A客户端向B客户端传了数据,A上报Tracker说“我给B上传了10GB”,B在后续的announce里也会汇报“我从某个peer下载了大约10GB”。两个数字应该量级吻合。我做过一个对账任务,每隔一小时把数据库里“用户A给用户B种子上传量”的聚合和“用户B从用户A种子上传量”的聚合拉出来比对,误差超过一定比例就记一笔。一对账,很多单看报表完全看不出来的作弊就暴露了:一个用户可能把上传量报得很高,但没有任何一个下游peer的真实下载记录能对得上,那不就是虚报么。
交叉验证还可以用在更宏观的层面。比如同一IP段下多个账号的流量特征是否异常相似,同一时间段全站总上传量是否与实际网络容量明显脱钩。这些不算什么高深算法,但胜在能持续运作,不停给后台提供风险累积数据。
信誉系统则是把所有这些信号汇总成一个可执行的管理依据。每个用户维护一个信誉分,协议层异常、行为画像偏离、交叉验证对不上账都会扣分。分数累积到阈值后,系统自动提高对账频率、限制该账号的某些操作权限,或者直接把可疑流量回滚。有了这套机制,不会因为一次误报就把正常用户打死,也能给恶意用户形成持续的追查压力。我自己的实践里,信誉分阈值需要根据站内用户规模动态调整,站小人少的时期宁可宽松一点,避免误伤带来口碑损失。
4. Wireshark抓包分析实操:亲眼看看流量长什么样
前面讲了不少检测理论和算法,但如果你没有亲手看过真实流量,很多概念始终是悬着的。这一节我来说说怎么用Wireshark抓包,把Tracker交互的每一个字段真实地看一遍。这也是我最推荐新手管理员做的第一件实操功课——比读十篇协议文档都有用。
4.1 抓包环境与过滤器的准备
抓包最理想的位置,当然是Tracker服务器本身。如果在Tracker服务器上抓,能看到完整的HTTP请求行、查询参数、响应体,信息量最大。如果服务器不方便装图形界面,就用tcpdump先落盘,再把pcap文件拉回本地用Wireshark分析。我的常用命令是:
tcpdump -i eth0 -s 0 -w tracker.pcap -c 100000-c限制抓包数量,避免长时间挂着把磁盘写爆。另外我只在维护窗口期抓个10到20分钟,抓到几百MB就足够分析了。
抓回来后,在Wireshark里需要做的第一件事就是过滤出Tracker交互的流量。一般Tracker的HTTP流量可以用这个显示过滤器:
http.request && http.host contains "your-tracker-domain"其中your-tracker-domain替换成你自己Tracker服务器的域名或IP地址段。如果还没有域名,直接用ip.addr过滤也行。想知道站内用户都向哪些地址发了announce,可以先不过滤,直接在Wireshark的“Statistics -> HTTP -> Requests”里看,排前几名的Host基本就是你的Tracker和其他依赖的外部接口。
另外有个小经验:HTTP响应的状态码也很值得看一眼。正常的announce响应是200 OK。如果作弊脚本伪造水平不高,可能出现大量4xx、5xx响应,因为它的请求格式跟服务端期待的不一致。抓包里看到这类请求数量异常多,基本上可以顺着路径把作弊者的真实来源IP找出来。
4.2 完整解读一次announce请求的各个字段
打开一个抓到的HTTP GET请求,你会看到类似这样的完整URL,我把中间的十六进制值做了简化处理,实际内容会更长:
GET /announce.php?passkey=8f3a9c2e6d5b44a1... &info_hash=%9E%8A%... &peer_id=%2DTR3000%2Dabc123... &port=51413 &uploaded=21474836480 &downloaded=0 &left=0 &event=completed &numwant=200 &compact=1 HTTP/1.1这个请求在Wireshark的“Follow HTTP Stream”里看会非常清晰。逐字段对照前面讲的功能,你就能在真实世界里验证一次客户端和Tracker的“对话”。我建议新手拿到抓包后,先做一个最基础的练习:把同一次会话里同一个passkey的连续几个announce请求的uploaded字段值逐一记录下来。正常情况下,这些值会随着真实传输逐步增大,但你还能看到它偶尔完全不涨——比如用户在暂停做种,或者种子在等待peer接入时。
一个值得注意的字段是event。正常流程下,用户开始下载一个种子会看到event=started,下载完成后会看到event=completed,退出客户端时会看到event=stopped。如果同一个passkey在几秒内反复出现started和stopped,那就是客户端在快速启停,往往是脚本在测试连接或者试图重置计时器。
peer_id也很有看点。拿qBittorrent来说,它的peer_id通常以“-qBxxxx-”开头,其中前四位是版本号。如果你看到一个peer_id前缀对应的客户端版本和该用户实际用的客户端完全对不上,或者同一IP在短时间里出现了多种完全不同的peer_id前缀,那就基本可以确定是多客户端并发刷量。这些信息在抓包里都是一目了然的,比直接查数据库客户端版本字段更直观。
4.3 从抓包结果里找作弊痕迹的三种观察角度
看够了单个请求,接下来要用分析的眼光去观察整体流量模式。我在抓包里找异常时,基本沿着三个角度走。
第一个角度是请求频率。正常客户端向Tracker发announce,即使做种状态一直不变,也会按客户端设定的间隔,常见是30分钟或更长,周期性汇报。如果某个passkey的announce频率远高于正常值,达到每分钟几次甚至几十次,那就很可疑。作弊脚本为了让Tracker及时记录伪造的上传增量,往往会把汇报间隔设置得很短。这个高频特征在抓包的“Statistics -> HTTP -> Requests”或者“Conversations”里特别明显。
第二个角度是速率的真实变化。把同一个passkey按时间排序的announce请求列出来,计算相邻两次之间uploaded字段的增量,再除以时间间隔,就得到这段时间内的上报上传速率。把这个速率画成曲线,对比前文说的物理带宽约束和波动特征,基本能判断是真实传输还是脚本直线增长。有个细节:真实速率很少长时间满速稳定,因为BT上传需要下游peer能配合接收,而真实网络总会有波动;脚本则倾向于把速率做成一个比较稳定但略低于物理上限的数值,目的是不触线。
第三个角度是IP与端口分布。Tracker会给你返回peer列表,里面包含每个peer的IP和端口。作弊者的IP往往集中在几个C段,或者干脆全是内网地址。端口的分布也常出现连续端口号或者重复端口号,因为脚本批量起连接时喜欢使用固定范围内的端口。Wireshark的“Statistics -> Endpoints”是看这个分布最快的入口,按IPv4地址排序,再切到TCP端口维度,规律经常会自己跳出来。
这里有一个很关键的实操提示:抓包分析主要用于排查确认,而不是当作实时检测工具。你不可能24小时开着Wireshark去盯每一个用户。抓包的价值在于积累“眼见为实”的样本,帮你在写检测规则或调整算法阈值时,知道什么样的流量是真实流量、什么样的流量是伪造流量。我第一次完成一个完整的抓包分析后,再回去看数据库里的异常记录,感觉完全是两种精度。
5. 实操中踩过的坑与问题排查实录
反作弊系统上线简单,但想长期运行不误伤、不漏报,中间有不少坑。我把实际操作中踩过的问题整理成下面几类,供做站的朋友参考。
5.1 误伤正常用户:NAT和公网IP的锅
我踩过最大的一个坑,是把NAT网关后面的多个用户当成同一个IP上的批量作弊。有些小区宽带、校园网、公司网络,几十上百个用户对外只显示同一个公网IP。这种情况下,IP维度的统计和检测规则几乎全部失效——同一个IP上可能出现几百个完全不同、完全正常的使用者,他们的peer_id、passkey、下载内容全都不同。
我是怎么发现的?有一个用户被系统连续警告了三次,理由是“同IP并发peer过多”。查下去才发现,那个公网IP是某单位的大出口网关,底下正常用户分布在好几个城市。那次误伤之后,我把所有涉及IP的判断都加了条件:只有“同一个passkey在多个IP上并发”才判为高度可疑,“同一个IP下多个不同passkey”只能作为参考因子,不能单独作为处罚依据。
另外还有一个隐蔽的坑:手机热点。用户在用4G/5G热点做种时,IP段频繁变化,甚至每次会话的IP都不一样。这种场景下,如果你用“同一passkey短时间内出现在多个IP”直接判作弊,会误伤大量正常做种的移动用户。我的处理方式是增加时间窗口,比如要求“同一passkey在10分钟内出现在5个以上不同公网IP”,并且配合客户端指纹和流量数据一起判断,而不是仅凭IP就下结论。
5.2 HTTPS加密流量下的抓包难题
现在很多Tracker已经切换到HTTPS,抓包会遇到一个现实问题:HTTP层的内容是加密的,你在Wireshark里只能看到TLS握手的交互,看不到announce URL里的具体参数。想解密这些流量,一个可行的方法是让客户端在调试阶段把TLS会话密钥导出,Wireshark里设置好密钥文件后,就能看到明文内容。具体做法是在客户端的运行环境里设置SSLKEYLOGFILE环境变量,把密钥文件路径指给你自己定义的某个文件,然后在Wireshark的“Preferences -> Protocols -> TLS -> (Pre)-Master-Secret log filename”里填上这个文件。
不过这个方案只适用于你能控制客户端的测试环境,没法用于生产线上的全量抓包。生产环境里我更推荐两条路:一是让Tracker服务器在接入层打访问日志,把完整的请求URL和参数记下来,日志字段比抓包还全;二是在内网出口镜像端口抓包时,只做请求频率和连接数层面的分析,不做内容层分析。很多作弊特征,比如大量并发连接、固定间隔请求、单一IP大量连接,在TLS加密下依然能通过连接数统计看出来。
这里也想顺带提一个很多人会忽略的问题:有些老牌作弊脚本直接用HTTP明文发announce,完全不识别Tracker是否已经切换到HTTPS。如果你把Tracker从HTTP切到HTTPS,作弊量往往会瞬间下降一大截。原因不是用户变诚实了,而是不少脚本根本不会处理TLS握手,直接断连了。这个经验对我后来选择直接上HTTPS有很大影响。
5.3 从确认异常到落地处置的完整处理路径
前面讲了这么多检测信号,但真正要落地处置用户的时候,人还是要介入的,不能只看机器评分就一锤定音。我自己的处理路径大致分四步。
第一步是复核。系统发出可疑告警后,先把该用户最近几天的announce日志、peer连接记录、对账差异记录全部拉出来,结合抓包样本做人工复核。这里重点关注的是:异常趋势是持续性的还是偶发的,是单个种子的问题还是全站范围内的问题。
第二步是对表。把复核过程中发现的可疑数据与数据库里该用户的历史数据对表,确认有没有交叉验证的实锤。比如前文说的双端对账,如果A用户所有报告的上传量在B、C、D等下游用户的下行记录里完全找不到对应,那就是一个非常硬的实锤。
第三步是处置。根据站内的管理规则,一般会先限制该账号的高频操作权限,再把确认的虚假流量回滚,最后按情节轻重做降权或封禁处理。要注意的是,处置过程中保留证据很重要,把抓包文件、日志片段、对账报表打成一个包存档,方便日后用户申诉时有据可查。
第四步是复盘。每次确认一个作弊案例后,我会把作弊手法的特征回填到检测规则里,看规则能不能提前识别。不能的话就补一条新规则,或者调一个参数。这个闭环做完,系统才会越来越准。
做流量作弊检测这一年多下来,我最大的体会是:这个领域没有一劳永逸的办法。今天你上一个正则匹配peer_id,明天就有人写随机peer_id的脚本;你今天查固定间隔announce,他明天就把间隔改成随机抖动。真正能长期起作用的,是靠协议层校验、行为画像、数据交叉验证这套组合拳,把检测成本抬到作弊者不愿意承担的高度。
对刚接触这个方向的朋友,我建议不要一上来就写复杂的机器学习模型。先用Wireshark把真实流量看熟,再在数据库里把对账任务建起来,最后才考虑上异常评分和行为画像。地基打牢了,后面加什么算法都稳;地基不牢,模型再花哨也是白搭。
如果你也在维护类似平台,或者在做P2P相关的风控系统,希望这些实操经验能给你一些参考。我也很乐意在评论区跟大家一起拆解具体的抓包样本和异常特征,毕竟这类对抗性强的领域,一个人的思路永远有限,多聊聊总能有新收获。