OP 从 2018 年开始做了一个爬虫项目,从 2018 年持续维护到现在。 爬虫项目里面使用 tensorflow 训练了一个验证码识别器,从 2018 年以来,验证码识别器一直保持很高的准确率(>99%),但是从 2024 年 10 月 17 日开始,被爬的系统开始频繁报错“验证码错误”异常,最开始我以为是被爬系统加强了反爬策略,于是重新采集了 2w 张验证码,打上标记之后重新训练了一个模型,验证集成功率>99%之后将新的模型部署上去了,但是被爬系统依旧频繁报错“验证码错误”。 于是我开始具体的分析整个请求链路,发现这个事情似乎没有这么简单。
在整个链路中,我将不同的服务器分别命名为 A 、B 、C 。
出现大量的“验证码错误”报错的当天,代理服务器 B 、业务服务器 C 均没有任何改动,网络环境也没有任何改动,大量报错是突然开始出现的。
从表现来看,似乎是业务系统 A 增加了反爬的相关验证;但是我花了几个小时的时间对业务系统 A 在浏览器环境中请求的接口进行分析,并没有找到明显的痕迹证明业务系统 A 有过变更,依旧是使用“获取页面”、“获取公钥”、“获取验证码”、“识别验证码”、“登录”这些接口就完成了用户账号的登录。
我在另外一台 Windows 服务器上使用 Atrust 登录了相同的 VPN 账号,因为将程序对接过来需要处理网络相关的问题,所以我在服务器的 Edge 浏览器上直接进行人肉测试,打开页面、输入账号密码、登录,整个过程和以前一样,没有出现任何问题,直接登录上了。此时,原来通过 Docker 部署的 Atrust 依旧在处理来自线上的请求,并且持续报错“验证码错误”。
然后我调整了网络,将 Windows 云服务器直接在公网开放端口,并运行了 CCProxy 来暴露代理,在云服务器安全组中将代理服务器 B 加入到白名单中,这样代理服务器能够使用 Windows 云服务器的公网 ip 加上 CCProxy 的代理端口(808)就能实现与 Docker-Atrust 相似的功能。
接着,我调整了灰度设置,将线上的流量从原来的 Docker-Atrust 中全部切换到 Windows 云服务器。观察业务服务 C 一段时间后,业务服务 C 依旧在报错“验证码错误”。此时,我再次打开 Windows 云服务器的 Edge 浏览器,重新运行之前测试过、正常的流程:打开页面、输入账号密码、登录,奇怪的现象出现了,网页报错“验证码错误”、“用户名或密码错误”
这个奇怪的现象令我十分困惑,具体原因如下:
带着上面的疑惑,我测试了多次 Edge 浏览器的登录,大多数情况下报错“验证码错误”、“用户名或密码错误”,如果我在提前输入好账号密码之后,快速多次点击验证码(触发验证码刷新),然后快速输入验证码并点击登录,就能够正确的登录进去。但是在登录后自动跳转的主页中报错“用户会话已注销”之类的提示。
此时夜已经很深了,我就将线上流量切回了 Docker-Atrust ,然后再次在 Windows 云服务器上 Edge 浏览器测试登录,此时它正常了。
😢
这几天的时间里面,我每天都在想这个奇怪的现象,Edge 浏览器与爬虫程序的交叉点是从 Atrust 开始的,如果爬虫程序有问题应该不会影响 Edge 浏览器的 Cookie 与会话,既然影响了说明问题不是出在代理服务器 B 以及业务服务器 C ,而是 Atrust 往上的东西。
我甚至怀疑是 Atrust 存在 bug 导致了不同 Cookie 的请求被混合然后返回了错误的数据,或者是 Atrust 存在一个类似“连接复用”的功能?将不同 Cookie 的请求强行使用了相同的连接然后导致串会话?
我已经尝试修改了爬虫程序,在业务服务器 C 中,每次调用代理服务器 B ,都加上一个时间戳参数,用来确保每次请求的 Uri 均不相同(同一时间请求同一个接口有很小概率相同),问题没有得到解决,也没有肉眼看出来有任何的改善。
也花了时间翻了 Atrust 的一些功能说明和论坛,没有明确找到 Atrust 有这种“连接复用”的功能。
感谢大家花了这么多的时间看到这里,希望大家能够帮我分析分析,看看是不是我的排查思路哪里有问题?或者是 Atrust 的什么相关功能防止了我这种请求方式?
我已经实在是没辙了😢
1
Mystery0 OP 帖子不要沉了呀,求大家帮帮我
|
2
defunct9 37 天前
好同志,图很清晰。
|
3
Mystery0 OP |
4
proxytoworld 36 天前
按道理零信任的项目是不会动第三方网站的 cookie
|
5
kkk9 36 天前
改时间戳有啥用?不应该先打个日志吗?把报错时候的所有参数都打印出来,对比正常时刻的参数
|
6
Mystery0 OP @proxytoworld 对,这也是我十分奇怪的点,按理说不管是不是深信服,VPN 都不应该会“篡改”请求的,但是除了 Atrust 之外,就没有地方可以怀疑了,除非是业务系统 A 改东西了。
因为我在服务器上单独测试过浏览器访问业务系统 A 了,问题依旧出现,说明问题应该不是出现在代理服务器 B 、业务服务器 C 甚至更加往下的服务器 这东西对我的冲击有点像“计算网络不存在了” |
7
zjsxwc 36 天前
把用户发出的 cookie ,与 A 收到的请求 cookie 对比一下,不就知道了有没有篡改
|
8
proxytoworld 36 天前
没玩过爬虫,但我的意见是出错的时候把 cookie 拿出来,放到浏览器里面手动测试可不可以访问
|
9
Mystery0 OP @kkk9 参数应该是一样的,代理服务器 B 和业务服务 C 都是代码来发的请求,没有道理不一样
Windows 云服务器上的 Edge 浏览器在多次测试过程中使用的都是同一个账号和密码,也没有换浏览器,请求成功和失败的时候参数也应该是一样的 这个东西给我的感觉就像是:我用 Edge 刚刚获取到的 Cookie ,还没开始走到登录接口呢,就突然被什么东西给拿去用了(例如刷新验证码,或者登录其他账号了),等 Edge 走到登录接口了,Cookie 就有问题了 |
11
defunct9 36 天前 2
A 端加了控制,同一个 IP 限制多次登录。
|
12
Mystery0 OP @proxytoworld #8 这需要改一下代码,让来自用户的请求先临时停下来,然后再把 cookie 从 B 里面抓出来拿到 Windows 云服务器去访问测试
改动比较大,后面如果还是没有进展也只能试试了 不过说起来,我观察了浏览器上访问业务系统 A 时的请求,它的 Cookie 有刷新的情况,正常先加载 Html ,此时服务端 set 一个 cookie ,然后浏览器开始加载 js 、img 等资源,在这些加载过程中依旧会随机的 set 一个新的 cookie ,从观察情况来看,没有什么规律,也不确定为什么业务系统 A 要 set 一个新的 cookie |
13
Mystery0 OP |
14
tomatocici2333 36 天前
@Mystery0 #13 有账号吗? 先正常渠道登录试试 看看有没有风控
|
15
Mystery0 OP @tomatocici2333 有账号,今晚我测试一下 A 的风控,看看是不是 A 的限制
|
18
for1shot 36 天前
个人觉得 cookie 出问题的概率不大。
我是做 DPI 的,略微了解过深信服的产品,讲一个有可能的原因。 可能是深信服识别出了你的爬虫特征,比如短时间内的大量访问,因此根据你的流量特征做了定向的封堵,不过这个封堵不是永久的,可能过一段时间就又恢复了,所以你手动测试有可能是正常的,但是一爬虫就失败了。 |
19
Mystery0 OP @for1shot 这种封堵一般会和业务系统绑定吗?
我在浏览器上测试的时候,看到的报错信息并不是直接返回的文本,而是整个 html ,html 渲染完成之后会在登录对话框中显示错误信息 如果是深信服的封堵,应该直接中断连接或者直接返回 text/plain 的数据(就是网页上几个大字),不会返回 html 数据,除非深信服识别之后将情况告知了业务系统 A 也就是正方,然后正方针对深信服告知的结果做了专门的错误信息处理。 写出来这些感觉可能性不强…… 正方确实是很多高校使用的教务系统,校园网对外访问也是使用深信服的产品( easyconnect 、atrust 等),不过也说不好,也许上面有要求正方和深信服一起解决掉所有高校的爬虫问题? |
20
tomatocici2333 36 天前
@Mystery0 #15 Atrust 不大可能去修改你的 ck,这玩意不就是个 clash
|
21
tomatocici2333 36 天前
@Mystery0 #19 返回整个 html 很正常。你都说了是老系统,对方有可能是 jsp
|
22
Mystery0 OP @tomatocici2333 #21 如果是深信服拦截并处理的,返回的就不是 html 了,因为深信服不知道正方的 html 有什么东西。因此,如果是深信服检测并拦截了,那也只能是将检测结果给业务系统也就是正方,让正方来报错返回正方的 html
|
23
for1shot 36 天前 1
@Mystery0 #19 可能是我 ”封堵“一词表述不当,造成了误解。
封堵并不一定会关闭整个 tcp 会话。 封堵有两种模式,一种是监测到异常的 tcp 流量之后,伪造一个 tcp close 报文,从而关闭整个 tcp 的对话,这种情况下会报 404 ,或者其他什么错误,表现为看不到 html 。这种是以流为单位的管理策略。 还有一种是深度包检测,是以数据包为单位进行管理,也就是监测到异常数据包之后,比如发现特定 cookie 字符串的数据包,仅仅擦除该数据包内的 cookie ,然后继续发给服务端。这就可能会导致:可以看到 html 返回,但是由于服务端收到的包被篡改了,就返回账号密码错误。 |
24
NoOneNoBody 36 天前
“大量”是个虚词
先说有没有成功的,还是全部都出错 如果并非全部出错,成功的数量也不少,不是零零星星几个的话,代码应该没问题,更多是链路中间的问题 现在的 web 服务器,没有反爬的话,都不像话 你这里写的都是些很基础的爬虫知识,也不晓得你有没有做高级抗反爬 现在很多都是 cdn 反爬,例如 cf 的五秒盾,很难破,基本需要降频,高频就需要不断变换 ip ,另外还有客户端生成 token 这些,都需要让客户端抗指纹 这里还有一个重点你没说,既然是需要登录,是有大量帐号么?还是帐号都是用户自己的? 如果都是贵司提供,不断复用,被风控那基本都是秒级的事,能爬几年真是不可思议 |
26
Mystery0 OP @for1shot #23 这个情况是 10.17 那一天突然开始出现的,倘若真是这个原因的话,我倾向于认为是 负责管理 Atrust 的管理人员,在那一天发现了 Atrust 有这个功能,然后给打开了
|
28
tomatocici2333 36 天前
@Mystery0 #22 大概率是 A 出了问题
|
29
Mystery0 OP @NoOneNoBody 有成功的,甚至现在都有成功的,因为正方提供的是教务数据,也就是课表之类的东西,学生上课时间是固定的,所以几年来只要是上课的日子都会在固定的时间点前后产生大量的请求。
从几天的请求响应监控来看,基本是上课时间前 20 分钟左右达到请求峰值,然后出现大量失败的请求,失败率在 50%甚至更高,(业务服务 C 对每一个返回给客户端的请求都有响应码和错误码的监控),上课之后请求量减少,成功率增加,大概是 75%-80%的成功率。等到一天的课程结束,特别是凌晨的时候,请求量特别少,这个时候成功率几乎大于 90%。 正方教务系统它是部署在学校机房的,往外没有 CDN (因为不提供公网访问,仅在校园网访问) 登录正方的账号都是用户自己的,登录 Atrust 的账号是我找学生借的(只有一个) |
30
NoOneNoBody 36 天前
@Mystery0 #29
这样说就比较明显了,要么就是对方反爬,要么就是对方机器也抗不住了 整体看下来,你们不是一个主动爬虫,就是不会游走爬取“未知”的内容,更像是个数据代理,把客户请求及爬取返回内容优化? 如果内容有大量重复,建议按规则做缓存,减少爬取次数 当然,缓存要准确,不然就相当于你们“制造”了错误数据,信用断崖下降了;而且缓存的内容涉及隐私的话,还要凭良心“加密”才缓存 |
31
Mystery0 OP @NoOneNoBody #30
更像是个数据代理,把客户请求及爬取返回内容优化? ———————— 对,是做的这个 如果内容有大量重复,建议按规则做缓存,减少爬取次数 ———————— 现在服务端只处理数据,不会主动存储,客户端做了数据落库的缓存,但是也提供了“刷新”缓存数据的功能,每天第一次打开 APP 的时候会执行登录逻辑然后获取数据,这样做的原因也是因为以前出现过显示错误数据的情况导致了一些问题 |
32
NoOneNoBody 36 天前
@Mystery0 #31
不是这个意思 例如三个用户都请求相同的 A 页面(同一个课程表什么的),但在服务器方看来,就是贵司一个 ip 对 A 请求了三次;所以这三个 A 的请求,最好能减少到一次,另两个以缓存返回给用户 |
33
Mystery0 OP @defunct9 #11 @tomatocici2333 #14 昨天下午我针对 A 做了多次账号登录的测试,使用 docker-atrust 提供 vpn 环境,然后在 Mac 上使用不同浏览器登录不同的账号,没有发现 A 系统有踢出的相关逻辑,应该和 A 关系不大
@for1shot #23 我今天( 26 号)下午做了很久的测试,用了 Reqable 抓包仔细对比了浏览器发送的请求头信息和程序设置的请求头,浏览器发送的头没有发现非常明显的针对风控的标识,但是程序的请求头有些过于死板了(从打开页面到最终请求登录接口,都十分的“标准”,时间戳参数要么都有,要么都没有,部分在浏览器端通过 js 发起的请求 [带有 X-Requested-With 头] 没有带上相对应的头),虽然这些“特征”不一定被 A 系统识别并检测了,我还是依次对比之后补上了一些请求头参数,现在从 Reqable 抓包来看,请求更“像”浏览器发起的了。 最后说一下关于 Atrust 部分,从我下午的测试情况来看,感觉出现问题的地方是和 Atrust 有关系,但是不知道是如何实现的:刚开始的时候我在通过浏览器和程序依次发送不同账号的登录请求,以此来对比两种请求方式的不同点并针对性的进行修改(具体差异见上一段)。持续了比较长的一段时间之后,就出现了一个明显异常的情况:这个时候在浏览器上我直接访问页面能够显示登录页面,但是输入任何账号(我本地测试用的是 3 个不同的账号)进行登录均无法登录,并且 [ [不再显示错误原因] ] !!!我保证测试账号的密码没有输错,验证码也没有输错,并且我试了很多次,都是这样的现象。 跟踪了一下这一种新的现象,具体表现为: 1.通过浏览器加载登录页面 GET /login.html (仅作说明,具体路径不是这个),无论是直接访问域名(未登录情况下会 302 到登录页面)还是直接访问登录页面 2.浏览器打开页面会正常显示,同时自动获取公钥( GET /public.html ),自动加载验证码( GET /kcaptcha ) 3.在浏览器上输入账号、密码、验证码之后,页面正常发起登录请求( POST /login.html ) 4.POST 请求返回 302 重定向状态码,但是 Location=/ (以前登录失败时返回的是 200 状态码,并且响应体内容位 html ,html 中带有错误原因) 5.因为 302 到了/,然后又走了一次第一步的流程,页面再自动 302 到/login.html ,因为是 302 过去的,所以是正常 GET /login.html ,也就没有错误信息了 今晚在家里的 windows 机器上,装了个 windows11 的虚拟机,登录上 Atrust 之后将代理服务器 B 部署进去,然后本地启动业务服务 C ,跑了三个账号的登录,没有发现什么问题(没有问题可能是特征暂时未识别出来,也可能是请求量太少没有达到并发的量),至少没有出现下午那种“不显示原因的直接失败”的情况了。 说说最终的结论吧,现在呢大概是能确定应该和 Atrust 有点关系,可能是 Atrust 检测到访问 A 系统的请求经过了代理( docker-atrust 里面用的 tinyproxy ,可能带了某些特征 [例如请求头] ),也可能是请求参数过于“标准”触发了 Atrust 的什么网络策略,然后 Atrust 对应该正常发送给 A 的数据包做了一些修改(不知道具体的修改内容)导致 A 接收数据不完整从而登录失败。 斗智斗勇了几天呢,今晚最后再上一次代码修改看看有没有什么改善,如果还是不行的话,就只能想办法绕过 Atrust 直接请求 A 了(想办法在大内网环境里面弄一台机器通过 tailscale 组网来直接提供接口代理),或者和 A 谈一下,被官方收编了算了…… |