ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

ASP检测手机客户端并跳转:User-Agent移动端分流实践

ASP检测手机客户端并跳转:User-Agent移动端分流实践

接手一个跑了快十年的 ASP 老站,最怕的不是代码有多老,而是需求方一句轻描淡写的话:手机打开要自动进手机版。听起来就是几行 if 的事,真做起来,iPad 到底算不算手机、微信里点开算不算、搜索引擎的爬虫抓到的是哪个版本、用户手动点了“访问电脑版”下次还跳不跳,一个个问题会接连冒出来。这个项目标题——ASP 检测手机客户端并跳转到相应页面——说的就是这么一件事:用 ASP 在服务端读取User-Agent,判断来访设备是不是移动端,再决定把这次请求送去 PC 页面还是移动页面。

我在 IIS 上折腾过好几个这种老站的分流改造,从最粗暴的字符串包含判断,到后来封装成公共函数、加 Cookie 记忆、处理缓存和爬虫白名单,踩过的坑基本能凑成一本小册子。这篇内容适合三类人看:还在维护 ASP/IIS 老站的运维和全栈、被外包甩过来接手“祖传代码”的开发者,以及虽然不用 ASP 但想搞懂移动端分流底层逻辑的人。不管你用的是 VBScript 还是后来转到别的语言,判断思路是通用的,只是语法换了个壳。我会把方案怎么选、代码怎么写、哪里会出错、出错怎么查,一层层拆开讲清楚,代码可以直接抄去改。

1. 先想清楚:为什么要在服务端做设备分流

1.1 老站点的现实处境:一套代码要伺候两种屏幕

很多跑了多年的企业站、行业站,前端还是十几年前那套表格布局,宽度写死 960px 或者 1000px,图片是缩略图直接撑满,导航是 hover 触发的下拉菜单。这种页面在手机上打开,要么整体缩小到看不清字,要么横向滚动条拉半天。想改成响应式?CSS 全量重写、图片重新切、交互重做,工作量不说,样式表里那种“哪块浮左哪块浮右”的老写法,一改就全乱。所以绝大多数老项目的现实选择是:另起一套移动站,主站不动。

这时候就必须有一个东西来判断“来的人用的是不是手机”,然后把他送到对应的站点去。这件事可以放在三个位置做:前端 JS 判断、服务端 ASP 判断、Web 服务器层面(IIS 的 URL 重写模块)判断。前端 JS 判断的问题是首屏会闪一下——PC 页面先渲染出来,脚本跑完才跳走,用户体验很差,而且搜索引擎爬虫大多不执行 JS,收录会出问题。服务器层面判断性能最好,但需要装额外的重写模块,很多老机器上没装或者没权限装。所以最稳妥、最不需要额外依赖的做法,就是在 ASP 页面里做判断,请求还没输出 HTML 就已经决定去哪了,干净利落。

1.2 三条路线的取舍:响应式、独立移动站、按需跳转

在动手写代码之前,我一般会先跟需求方把这三种方案摆到桌面上对比一遍,因为选错了后面全是返工。这个对比表我几乎是原样复用的,你可以直接拿去跟人讨论:

方案改造成本维护成本SEO 影响适用场景
原站改响应式高,CSS 和模板基本重写低,一套代码最好,URL 不变新项目或前端结构较干净的项目
独立移动站 + 服务端分流中,移动站可另起模板中,两套模板要同步内容需要额外标注,处理不好会被判重复内容老站改造、PC 端交互复杂
同域名 /m/ 路径子站中,同上中,同上较好,主域权重共享不想再买域名、Session 要共享

第三条其实是第二条的变体,区别在于移动站是m.example.com这样的子域,还是www.example.com/m/这样的路径。子域的好处是移动站可以独立部署、独立缓存、独立做 CDN;坏处是 Cookie 和 Session 默认不共享,用户在主站登录了,到移动站还是未登录状态,得额外处理 Cookie 的 Domain。路径方式的好处是 Cookie、Session、登录态天然共享,一套后台一套登录逻辑,坏处是同一台服务器上要维护两套模板,路由容易写混。我个人的偏好是:如果站点有会员登录体系,优先走路径方案;如果是纯展示型站点,子域方案更清爽。

还有一个容易被忽略的点:跳转是单向的还是双向的。很多项目只做了“手机访问 PC 页自动跳移动站”,结果用户在手机上手动点了“访问电脑版”,刷新一下又被弹回移动站,气得直接关页面。所以完整的方案里,必须留一个“用户主动选择优先于自动检测”的开关,这个后面会讲怎么用 Cookie 实现。

2. 核心原理:ASP 到底是怎么“看见”手机的

2.1 User-Agent 这串字符里藏着什么

浏览器每次发请求,都会在请求头里带一个User-Agent字段,就是一串自报家门的文本。服务端拿到的就是这个字符串,ASP 里通过Request.ServerVariables("HTTP_USER_AGENT")取出来。这串东西长这样(简化过的):

Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1 Mozilla/5.0 (Linux; Android 13; SM-G991B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36

能看出来,移动端的 UA 里通常会出现Mobile、Android、iPhone、iPod这类关键词,PC 端一般没有。这就是全部判断逻辑的基础——关键词匹配。听起来很土,但这么多年过去了,业界主流做法依然是这样,因为 UA 是唯一一个在请求到达服务端时就能拿到的设备线索(CSS 媒体查询要等页面渲染,JS 要等脚本执行)。

不过要注意,HTTP_USER_AGENT是一个“客户端说了算”的字段,任何人都可以伪造。所以这里的检测只能用来做体验优化,不能用来做安全判断或者权限控制。我见过有人拿 UA 判断来做“移动端专属优惠”,结果被脚本刷得很难看,这个思路一定要纠正过来。

2.2 为什么不能用“全量机型清单”去匹配

新手最容易犯的错,是去网上找一份几百行的手机 UA 清单,然后一条条InStr比对。这个做法在 2010 年可能还行,现在完全跑不通:新机型每天都在出,UA 里带的具体型号千奇百怪,清单永远追不上。正确的思路是用少量高置信度关键词做“组合命中 + 反向排除”。

我的判断顺序是这样的:

  1. 先看 UA 是否为空,空的话按 PC 处理(有些脚本请求不带 UA);
  2. 先做爬虫排除,命中爬虫关键词的直接放行,不跳转;
  3. 再做平板识别,iPad、Android 平板这类先拎出来单独处理;
  4. 最后做手机识别,命中任意移动关键词就判定为手机。

这个顺序很重要,因为iPad的 UA 里既有iPad又有Mobile(老系统),如果先判手机就会把 iPad 误判成手机;而爬虫比如移动版搜索引擎蜘蛛,UA 里也可能带Mobile,不先排除就会把爬虫也跳走,直接影响收录。顺序错了,逻辑再对也是白搭。

<% '============================================================ ' 设备类型判断公共函数 ' 返回值:0=PC 1=手机 2=平板 3=爬虫/脚本 '============================================================ Function GetDeviceType(ua) Dim s, i GetDeviceType = 0 If Len(ua & "") = 0 Then Exit Function s = LCase(ua) ' ---- 第一优先级:爬虫与命令行工具,一律放行 ---- Dim bots bots = Array("spider", "crawler", "slurp", "archiver", _ "baiduspider", "googlebot", "bingbot", "yandexbot", _ "sogou web spider", "curl/", "wget/", "python-requests", _ "okhttp/", "go-http-client", "headlesschrome") For i = 0 To UBound(bots) If InStr(s, bots(i)) > 0 Then GetDeviceType = 3 Exit Function End If Next ' ---- 第二优先级:平板 ---- If InStr(s, "ipad") > 0 Then GetDeviceType = 2 Exit Function End If ' Android 平板 UA 通常不含 Mobile 关键字 If InStr(s, "android") > 0 And InStr(s, "mobile") = 0 Then GetDeviceType = 2 Exit Function End If ' ---- 第三优先级:手机 ---- Dim phones phones = Array("mobile", "iphone", "ipod", "windows phone", _ "blackberry", "symbian", "opera mini", "kindle", _ "miuibrowser", "micromessenger", "ucweb", "alipayclient") For i = 0 To UBound(phones) If InStr(s, phones(i)) > 0 Then GetDeviceType = 1 Exit Function End If Next End Function %>

这段代码里有个细节值得说一下:bots数组里我特意没有放单独的"bot",只放了"bingbot"、"googlebot"、"yandexbot"这种带前缀的完整词。原因是我真被坑过一次——某个国产手机品牌的英文名缩写里正好含bot三个字母,用户用这款手机访问,被判定成爬虫,永远看不到移动版页面。这个 bug 在日志里几乎看不出来,是用户投诉“我的手机打开是电脑版”才反查到的。关键词宁可写得长一点、具体一点,也不要用两三个字母的短词去扫。

3. 落地实现:把判断逻辑封装成公共文件

3.1 文件怎么放:include 的位置很讲究

ASP 没有命名空间,也没有模块系统,公共逻辑靠<!--#include file="..."-->引入。我一般会在站点根目录建一个inc目录,里面放三个文件:

  • inc/device.asp:设备判断函数,纯逻辑,不做任何输出;
  • inc/siteconfig.asp:配置常量,PC 站地址、移动站地址、是否开启跳转、白名单路径;
  • inc/boot.asp:入口文件,把前两个 include 进来,然后执行“判断 + 跳转”的动作。

这样拆分的好处是,将来要改跳转规则只动boot.asp,要加新的 UA 关键词只动device.asp,互不干扰。然后每个页面顶部只写一行:

<!--#include file="inc/boot.asp"-->

注意路径是相对于当前文件的,不是相对于站点根目录。如果页面放在子目录里,就得改写成<!--#include file="../inc/boot.asp"-->,或者干脆用virtual形式写绝对路径:<!--#include virtual="/inc/boot.asp"-->。我强烈建议用virtual,因为老站的目录层级往往很深,改一个页面就得数一遍../,太容易出错。

3.2 boot.asp 的完整写法与几个关键判断

<% '============================================================ ' inc/boot.asp —— 移动端分流入口 ' 必须在页面任何输出之前 include '============================================================ Dim PC_HOST, MOBILE_HOST, ENABLE_JUMP PC_HOST = "www.example.com" MOBILE_HOST = "m.example.com" ENABLE_JUMP = True ' 总开关,线上出问题一键关掉 Dim ua, deviceType, viewMode, curHost, curPath, curQuery, targetUrl If ENABLE_JUMP Then ua = Request.ServerVariables("HTTP_USER_AGENT") deviceType = GetDeviceType(ua) viewMode = LCase(Request.Cookies("viewmode") & "") ' pc / mobile / 空 curHost = LCase(Request.ServerVariables("SERVER_NAME")) curPath = Request.ServerVariables("URL") curQuery = Request.ServerVariables("QUERY_STRING") ' 已经手动选择过,尊重用户选择 If viewMode = "pc" Then ' 什么都不做 ElseIf viewMode = "mobile" Then If curHost = PC_HOST Then Call JumpTo(MOBILE_HOST, curPath, curQuery) ElseIf deviceType = 1 Then ' 手机访问 PC 站,跳移动站 If curHost = PC_HOST Then Call JumpTo(MOBILE_HOST, curPath, curQuery) ElseIf deviceType = 0 Or deviceType = 2 Then ' PC 或平板访问移动站,跳回 PC 站 If curHost = MOBILE_HOST Then Call JumpTo(PC_HOST, curPath, curQuery) End If ' deviceType = 3(爬虫)不跳转,让它看到当前页 End If %>

这里最核心的两个点是:只在“跨站”时才跳,以及爬虫完全不跳。第一条防的是死循环——如果移动站上的页面也 include 了这段代码,而判断逻辑不检查当前域名,那么在移动站上检测到手机又会跳一次移动站,浏览器就一直转圈。我见过最离谱的一次,页面在m站和www站之间来回弹,最后是浏览器的“重定向次数过多”救了场。第二条防的是收录问题,让爬虫按它请求的 URL 拿到对应内容,PC 版和移动版各自被收录,再用页面里的rel="canonical"/rel="alternate"互相标注关系,这才是对搜索引擎友好的做法。

3.3 跳转函数:把 URL 拼接这件事做干净

跳转本身是Response.Redirect一行的事,但拼接目标 URL才是真正容易出问题的地方。原始 URL 可能带查询串、可能带中文参数、可能是 HTTPS、可能带非标准端口,直接字符串拼接很快就乱了。

Sub JumpTo(hostName, path, queryStr) Dim scheme, port, url ' 协议跟随当前请求,避免 HTTPS 页面跳到 HTTP If LCase(Request.ServerVariables("HTTPS")) = "on" Then scheme = "https://" Else scheme = "http://" End If ' 非 80/443 端口要带上,内网测试环境必须 port = Request.ServerVariables("SERVER_PORT") If port <> "80" And port <> "443" Then port = ":" & port Else port = "" End If If Len(path & "") = 0 Then path = "/" url = scheme & hostName & port & path If Len(queryStr & "") > 0 Then url = url & "?" & queryStr Response.Status = "302 Found" Response.AddHeader "Location", url Response.End End Sub

Request.ServerVariables("URL")拿到的是当前请求的虚拟路径(比如/news/detail.asp),QUERY_STRING拿到的是问号后面那一截。两个拼起来就是完整的相对地址,原样搬过去,这样用户在手机上看某篇文章,跳到移动站还是那篇文章,而不是回到首页——这个细节做不好,用户会非常恼火。中文参数的编码问题不用担心,QUERY_STRING拿到的本来就是 URL 编码后的原始串,原样透传即可;如果是要自己构造新的参数值,才需要用Server.URLEncode()处理。

4. 跳转状态码与用户体验的那些细节

4.1 302 还是 301:不是随便选的

Response.Redirect在 IIS 下默认吐的是 302 临时重定向。有人觉得“我这个跳转是永久规则,应该用 301”,于是改成:

Response.Status = "301 Moved Permanently" Response.AddHeader "Location", url Response.End

这个写法本身是有效的,但要想清楚后果。301 会被浏览器和搜索引擎长期缓存,一旦规则改错了,用户手里的浏览器还记着旧地址,你改了服务器也没用,得等缓存过期。而移动端分流的规则是跟着设备走的:同一台设备可能今天用手机访问,明天用电脑访问,跳转目标本来就不是永久固定的。所以我个人的做法是:PC 站与移动站之间的设备分流用 302,只有站点整体换域名(老域名彻底废弃)才用 301。

如果你一定要用 301,至少要在代码里留一个“紧急关闭”的总开关(就是前面那个ENABLE_JUMP),出问题的时候能立刻止血。这个习惯我保持了七八年,救过至少两次。

注意:Response.Status和Response.AddHeader必须在任何 HTML 输出之前执行,否则会抛ASP 0156 : 80004005的 Header Error。这个错误在后面的排查章节会详细讲。

4.2 用户手动切换:用 Cookie 记住选择

移动站页面底部一般会放一个“访问电脑版”的链接,PC 站上也常有“访问移动版”。这两个链接的实现方式很简单,就是带上一个参数让入口逻辑把选择写进 Cookie:

' 假设链接是 /setmode.asp?to=pc&back=/news/detail.asp <% Dim toMode, backUrl toMode = LCase(Request.QueryString("to") & "") backUrl = Request.QueryString("back") & "" If backUrl = "" Then backUrl = "/" If toMode = "pc" Or toMode = "mobile" Then Response.Cookies("viewmode") = toMode Response.Cookies("viewmode").Path = "/" Response.Cookies("viewmode").Domain = ".example.com" ' 主域与子域共享 Response.Cookies("viewmode").Expires = DateAdd("d", 30, Date()) End If If toMode = "pc" Then Response.Redirect "http://" & PC_HOST & backUrl Else Response.Redirect "http://" & MOBILE_HOST & backUrl End If %>

这里有两个关键参数。Domain要写成.example.com这种带前导点的形式,主域和子域才能共享同一个 Cookie;如果只写www.example.com,那么在m.example.com上读不到这个值,用户在移动站点的选择到了 PC 站就失效了。Expires一定要设置,不设置的话它是会话 Cookie,用户关掉浏览器就忘了,下次打开又被强制跳一次,体验很差。30 天是个比较合适的值,太长了用户自己都忘了什么时候点过。

另外提醒一句,backUrl这种从 URL 里直接读出来就拼进跳转地址的写法,是典型的开放重定向漏洞。一定要做白名单校验,只允许以/开头的站内路径:

If Left(backUrl, 1) <> "/" Or InStr(backUrl, "//") > 0 Or InStr(backUrl, "\") > 0 Then backUrl = "/" End If

判断//是为了挡住//evil.com这种协议相对地址,判断\是为了挡住某些浏览器会把它当斜杠处理的情况。这几行加上去,心里踏实很多。

4.3 POST 请求和 Ajax 请求怎么办

设备分流最容易翻车的地方是表单提交。用户在手机上填了一屏表单,点提交,POST到/order/save.asp,入口逻辑检测到是手机,一个 302 把人跳到移动站地址——结果POST的数据全丢了,用户看到的是一张空白表单。这个 bug 在测试阶段很难发现,因为测试同学一般用电脑点,上线后手机用户一提交就炸。

处理办法很直接:入口逻辑里判断请求方法,POST一律不跳。

If LCase(Request.ServerVariables("REQUEST_METHOD")) = "post" Then ' POST 请求不做设备分流,交给页面自己处理 Else ' 正常的分流逻辑 End If

不跳 POST 之后,页面得能正常往下跑。所以移动站的模板最好能兼容 PC 端提交过来的数据,或者干脆在移动端页面里用相对路径提交,保证提交地址本身就在移动站域名下。Ajax 请求同理,XMLHttpRequest的 UA 和主请求一样,但它期待的是 JSON 或者片段数据,一旦被 302 到 HTML 页面,前端解析直接报错。判断方法可以看X-Requested-With头:

If LCase(Request.ServerVariables("HTTP_X_REQUESTED_WITH")) = "xmlhttprequest" Then ' Ajax 请求,跳过分流 End If

这两个判断加进去,入口逻辑才算真正能上生产。

5. 那些年骗过我的 User-Agent

5.1 iPad、微信内置浏览器和“伪装”的移动端

UA 判断写得再细,也总有一些设备出乎意料。我整理了一张实际遇到过的对照表,你可以对照着完善自己的关键词库:

UA 特征实际设备我的处理方式
含iPadiPad(老系统)判平板,走 PC 版或平板专用版
含Macintosh且含MobileiPad(新系统,默认请求桌面版网站)视为平板,不强制跳移动站
含Android但不含MobileAndroid 平板判平板
含MicroMessenger微信内置浏览器按手机处理,注意微信内下载和支付限制
含AlipayClient支付宝内置浏览器按手机处理
含UCBrowser/UCWEBUC 浏览器按手机处理,注意它的 UA 会随版本变
含Windows Phone已停更的 WP 机型归入手机,别当成 PC
含HeadlessChrome无头浏览器/截图服务归入爬虫,放行不跳

最坑的是 iPad 的新系统。从 iPadOS 13 开始,iPad 默认请求桌面版网站,UA 长得和 Mac 一模一样,只在里面多一个Mobile字样。如果你的判断逻辑是“含Mobile就是手机”,iPad 就会被当成手机跳去移动站,用户在大屏上看到一列窄窄的手机版页面,非常难受。所以我在判断顺序里把平板识别放在了手机识别前面,并且明确写了iPad和“Android 且不含 Mobile”两条规则。

至于微信内置浏览器,它的 UA 里带MicroMessenger,同时也带Mobile,一般能被正常识别。真正要注意的是微信里做了跳转之后的行为差异:微信内置浏览器对跨域跳转、下载文件、唤起 App 都有额外限制,如果跳转目标是m子域,还要确认公众号后台配置的业务域名包含该子域,否则页面里的 JS-SDK 功能会失效。这个是配置层面的问题,跟 ASP 代码无关,但排查的时候特别容易被忽略。

5.2 缓存与 CDN:跳转逻辑“没生效”的隐形凶手

有一种情况我遇到不止一次:代码明明改对了,本地测试也正常,但线上一部分用户就是跳不过去。查了半天发现是页面被 CDN 缓存了。设备分流是在服务端根据User-Agent动态决定的,但 CDN 默认是按 URL 缓存的,它不区分访问者的 UA——第一个访问者的请求被缓存下来,后面所有人拿到的都是同一份。

解决办法有两个方向。一是给响应加上Vary头,告诉缓存服务器“这份内容会随 UA 变化”:

Response.AddHeader "Vary", "User-Agent"

二是干脆让入口页面不被缓存,对于跳转这种轻量逻辑,成本几乎为零:

Response.AddHeader "Cache-Control", "private, no-store, max-age=0" Response.AddHeader "Pragma", "no-cache" Response.Expires = 0

我一般两个都加。Vary头虽然语义正确,但它会让缓存服务器为每一种 UA 存一份副本,缓存命中率暴跌,CDN 的费用和回源压力都会上去。所以更彻底的做法是:只让真正的内容页参与缓存,跳转判断放在不缓存的入口上,或者在 CDN 后台把.asp动态请求排除在缓存规则之外。

提示:如果你改完代码发现线上没变化,先别怀疑代码,用命令行带一个特定的 UA 去请求一次,看看响应头里有没有X-Cache: HIT之类的标记。这一步能省掉一半的排查时间。

6. 排查实录:报错、死循环和“只对部分人失效”

6.1 ASP 0156 报错:九成是文件编码惹的祸

Response object error 'ASP 0156 : 80004005' Headers already sent这个错误,几乎是每个 ASP 开发者都会撞上的。字面意思是“响应头已经发出去了,你现在才想改它”,但真正的原因往往很隐蔽。我遇到过的几种情况:

第一种,include 文件的保存编码是UTF-8 带 BOM。BOM 是文件开头那三个不可见的字节,编辑器不显示,但 IIS 会把它当内容输出,于是响应头提前发送,后面的Response.Redirect就报错了。解决办法是把所有 include 文件和页面文件统一存成UTF-8 无 BOM或者 ANSI,编辑器里一般都有这个选项。这个坑我见过至少五次,每次都要花半天才能定位。

第二种,boot.asp里在跳转代码前面多了一个空格或者空行。ASP 的<%@ %>指令块外面哪怕有一个空格,都会被当成输出。所以入口文件的第一行必须干干净净,从<%开始。

第三种,页面顶部有 HTML 注释或者<!DOCTYPE>,而 include 放在了它下面。这个属于引用位置错误,改到最顶上就行。

对应的排查动作很简单:用浏览器开发者工具看响应头,如果Content-Type前面已经出现了内容,或者Response Headers里根本没有跳转相关的头,就说明输出提前发生了。再用十六进制编辑器看一眼文件开头有没有EF BB BF三个字节,基本一次定位。

6.2 常见问题速查表

现象最可能的原因排查动作解决方式
页面报 ASP 0156输出提前发生查文件 BOM、查跳转前有无空格/HTML改存无 BOM,include 放第一行
浏览器提示重定向次数过多两个站互相跳查入口逻辑有没有判断当前域名只在跨站时才跳
手机用户看到的还是 PC 页CDN 缓存 / UA 未命中关键词带 UA 请求看响应头、看X-Cache加Vary、关缓存、补关键词
部分国产机型被当爬虫关键词太短,误伤品牌名用用户提供的 UA 做单测关键词写全,不用 3 字母短词
手机提交表单数据丢失POST 被 302 跳走看请求方法POST 和 Ajax 不跳转
点了“电脑版”又被弹回Cookie 未设置 Domain 或 Expires查 Cookie 作用域加.example.com和过期时间
跳转后文章变成首页未透传QUERY_STRING看 Location 头拼接原始查询串
HTTPS 站点跳成 HTTP协议写死看 Location 头的 scheme用HTTPS变量判断

这张表我基本是贴在显示器旁边的,新同事接手的时候直接看,能省掉大量重复解释。其中“用用户提供的 UA 做单测”这一条特别值得强调:每一个“我的手机打开不对”的反馈,都要让对方把 UA 发过来,然后拿到本地用Request.ServerVariables打印出来,丢进函数里跑一遍,问题基本就现形了。不要靠猜,猜测的成本远高于让用户复制一段文本。

6.3 上线前必做的四个验证动作

第一个,用真实设备各走一遍:iPhone Safari、Android Chrome、微信内置浏览器、iPad、一台 PC。这五条路径覆盖了 95% 以上的场景。第二个,用命令行伪造 UA 测试,把常见爬虫和几个奇葩 UA 都跑一遍:

curl -I -A "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1" http://www.example.com/news/detail.asp?id=123

看返回的HTTP/1.1 302和Location头,确认路径、查询串、协议都对。第三个,关掉跳转开关再跑一遍,确认开关真的有效——总开关失效这种事故,在真正需要它的时候才发现就晚了。第四个,检查移动站的页面里有没有指向 PC 站的绝对链接,有的话用户点一下又跳回去了,体验割裂。前三个是自动化能做的,第四个只能人工点,但必须点。

7. 我个人在几个老站项目上的实际体会

这套逻辑我在几个不同规模的 ASP 老站上都跑过,最大的感受是:代码本身只有两百行,真正花时间的全是边界情况。第一次上线的时候我只写了最简单的“含 Mobile 就跳”,测试环境一切正常,上线第二天就收到三起投诉,一个是 iPad 用户,一个是提交订单丢了数据的,还有一个是搜索引擎抓取异常导致收录掉了。三件事让我把判断顺序、POST 放行、爬虫白名单这三块彻底重写了一遍。

后来我形成了一个习惯:所有分流规则都写成可配置的数组,而不是硬编码在 if 里。新增一个要识别的浏览器,就在数组里加一行字符串,不用改逻辑;临时要放行某个渠道,就把域名加进白名单数组。这套东西放到今天也不算先进,但它在老环境里极其可靠——没有额外依赖,不需要装模块,改一行配置重启都不用。老技术栈的优势就在这儿,它笨,但它不会在你最忙的时候出意外。

还有一个我一直在坚持的小技巧:入口文件里永远留一行注释,写清楚最后修改日期和改了什么。老项目最大的敌人不是技术难,而是半年后你或者别人打开这个文件,完全想不起来当初为什么要在deviceType = 2的分支里加一个And InStr(s, "mobile") = 0。一行注释能省下半小时的考古时间,性价比极高。

返回列表