localStorage里那15个键,服务器一个也删不掉
本文目录
- 打开一个首页,它往你浏览器里放了多少东西?
- 为什么不是sessionStorage
- 这些键,有几个是这家店自己写的?
- 点名到户
- 认不出的那一半里,名字本身就是哈希
- 到期这件事,到底谁在执行?
- 把四块地放在一起看
- 带着这份存储再进一次,键会变少吗?
- 服务器这边有没有一个删除键?
- 就算发了,它管的范围也和你想的不一样
- 真正会替你收拾的,只有一家浏览器
- 同一个键的第三代来了,第一代还躺在那儿
- 浏览器自己报的用量,为什么是0?
- 第一处:用量接口不数这块地
- 第二处:一个看起来完美的因果,拆开就散了
- 存进去的到底是什么?
- 一个跟着你走的编号
- 带凭据味道的那些
- 你是谁、你在哪、你说什么语言
- 爬虫永远是第一次访问,它看到的是哪一版?
- 这会带来两个方向的偏差
- 差异该落在哪儿
- 12个站一个键都不写,它们少了什么?
- 差别在平台,还是在装了多少应用
- 自己站上怎么盘这块地
- 常见问题解答
- localStorage里的数据到底能存多久?
- 服务器能不能删掉用户浏览器里的localStorage?
- 为什么开发者工具里的存储用量和我自己算的对不上?
- 把登录令牌放在localStorage里到底行不行?
- 第三方应用卸载之后,它写进用户浏览器的键会自己消失吗?
- 爬虫会读取localStorage里的内容吗?
- 存储里存了用户所在地区,会不会影响SEO?
- 权威参考资料
摘要:用全新的浏览器配置打开125个海外品牌站的首页,等9秒钟就走,走的时候浏览器里已经躺下中位15个键、最多89个,全样本合计327万个字符。带着这份存储再进一次门,67个站又多写了几个,58个站原样不动,没有一个站变少;121个站连一个键名都没消失过。这些键里94.3%连一个到期字段都没有,而服务器端唯一能把它们清掉的那行响应头,131个站一个都没发过。
做技术审计这些年,保哥养成了一个习惯:看完响应头,顺手把浏览器的存储面板也翻一遍。响应头是站点说给你听的话,存储面板是它趁你没注意塞进你口袋里的东西。前者你可以改,后者一旦塞进去,它就不在服务器这一侧了。
这次想把这件事量清楚。挑了131个海外品牌站的首页——都是有真实流量、真实交易的成熟站,不是样板工程。每个站开一个干干净净的浏览器配置,访问首页,等9秒钟让脚本跑完,然后把localStorage、sessionStorage、IndexedDB三处逐个读出来,连键名带值的头120个字符一起记下。之后带着这份存储再访问一次同一个首页,看它会不会自己收拾。
131个站里,125个正常返回并读到了存储。落在外面的6个各有各的原因:boohoo、swarovski、uniqlo三家直接403拒绝,anker和soundcore两次都超过50秒没加载完,nespresso回了一个HTTP/2协议错误。这6个从此不再计入任何分母。
打开一个首页,它往你浏览器里放了多少东西?
先说总量。125个站,首次访问结束时localStorage的键数中位数是15个,平均20.1个,最多的一个站89个。分布拉得很开:
| 首次访问后的localStorage键数 | 站数 | 占比 |
|---|---|---|
| 0个(一个都不写) | 12 | 9.6% |
| 1到19个 | 59 | 47.2% |
| 20到39个 | 34 | 27.2% |
| 40个及以上 | 20 | 16.0% |
把这些值的字符长度加起来,全样本一共3271021个字符,中位一个站2051个。听起来不多,直到你看见最上面那几家:reebok一家就写了748692个字符,换算成浏览器给单个源那5MB配额,一家占掉14.28%;functionofbeauty写了484580个,dollarshaveclub写了352972个。reebok那748692里,有721436个装在同一个键里,键名叫FS_POP_CATEGORIES——一份被序列化后整个塞进浏览器的品类清单。
为什么不是sessionStorage
浏览器给了两块地:sessionStorage随着标签页关闭一起消失,localStorage不关。HTML规范里的Web Storage那一节把两者的区别写得很直白,前者的生命周期绑在浏览上下文上,后者绑在源上,而源是不会关闭的。
实测的选择相当一边倒。125个站里,102个两块地都用,11个只用localStorage,只用sessionStorage的只有4个。sessionStorage的键数中位是7个,不到localStorage的一半;99个站往localStorage里写的键比sessionStorage多。
这个偏好本身就说明问题。sessionStorage是会自己消失的那一块,localStorage是不会的那一块,而绝大多数脚本挑了后者。
这些键,有几个是这家店自己写的?
接下来是本轮最出人意料的一段。2507个键实例里,我按已知产品的键名特征做了一轮归因,能指名道姓说出是谁写的有1245个,49.7%。剩下那1262个,乍一看像是站点自己的业务代码。
但把它们按键名去重之后,事情就露馅了。125个站一共只用了964种键名。其中出现在两个以上站点的键名只有250种,占键名种类的25.9%,却占掉了71.5%的键实例。换句话说,你浏览器里那块地上,四分之三的东西写的是同一批名字。
站级的数字更直观:单个站点的键里,被别的站也用着的那部分占比中位是75%,四分位是46.7%和86.7%。这不是巧合,是同一批脚本开在不同的门店里。
点名到户
| 写键的一方 | 键实例 | 覆盖站数 | 典型键名 |
|---|---|---|---|
| Elevar(数据层工具) | 250 | 30 | ___ELEVAR_GTM_SUITE--userId |
| Shopify平台自带 | 213 | 75 | shopify:webmcp_adapter_loaded |
| Microsoft(UET与Clarity) | 117 | 30 | _uetvid、_uetvid_exp |
| Klaviyo(邮件营销) | 116 | 44 | __kl_key、klaviyoOnsite |
| Mixpanel | 75 | 38 | $referrer、$last_referrer |
| Google(GA与Ads) | 67 | 64 | _gcl_ls |
| Braze | 50 | 4 | ab.storage.attributes.<uuid> |
| Gorgias(在线客服) | 48 | 11 | gorgias.version |
覆盖最广的单个键是_gcl_ls,63个站都有;lastExternalReferrer和lastExternalReferrerTime各55个站;shopify:webmcp_adapter_loaded 48个;signInWithShop:cartSyncNextRecognitionAt 46个。这几个名字加起来,就已经铺满了半个样本。
这和之前那轮只读HTML看不见的第三方域名普查是同一件事的两个断面:那次数的是这些脚本从哪儿来,这次数的是它们在你这儿留下了什么。第三方不只是在你的页面上跑了一遍,它还在访客的机器上开了个抽屉。
认不出的那一半里,名字本身就是哈希
归不了类的1262个键里,有103种出现在3个以上的站点上,累计578次——按常识,这些几乎必然也是某个第三方,只是我认不出它。最扎眼的一组长这样:feh--c52、feh--1bda6、feh--9fd29358、feh--330df5……在8到9个站上同时出现,键名主体是一串十六进制。同一批站上还躺着一个forterToken。
把这两拨合起来算,能确认是第三方的比例大约在72.7%。剩下真正只属于某一个站的键名有714种,各出现一次。
键名叫feh--9fd29358这种东西,最麻烦的地方不是它存了什么,是站长根本无从查起。你在自己的存储面板里看见它,搜索引擎搜不出结果,也没有任何一份文档告诉你它归谁。第三方脚本会自己改版本、改行为,这件事在两天半就有四分之一的站悄悄变了脚本那次实测里量过一次;而这些哈希键名说明的是更靠前的一步:你连它是谁都不知道,就更谈不上盯着它变没变。
到期这件事,到底谁在执行?
这是本轮最硬的一个数字。2507个键里,值里带任何到期字段(expiry、expires、expiresAt、expireTimestamp、ttl、validUntil之类)的只有142个,5.7%,涉及63个站。剩下的2365个键,94.3%,连一句到期的话都没说。
而写了到期的那5.7%,情况也不比不写好多少。localStorage本身没有到期机制,规范里根本没有这一栏,MDN关于localStorage的说明写得很清楚:数据没有过期时间。所以那些expiry字段全部是脚本自己写进值里的一段文本,靠的是下一次这段脚本被加载、被执行、并且刚好想起来读一下这个字段。只要那个脚本被换掉、被删掉、或者仅仅是换了个键名,那句到期日就永远只是一段文本。
顺着这些值里的毫秒时间戳数了一遍,指向未来的有503个:485个落在2026年,9个落在2027年,9个落在2028年。跑得最远的一批叫snowplowOutQueue,到期日整整齐齐写在2028年9月3日——正好是我采集那天往后两年整。两年后那一天,不会有任何人来收。
把四块地放在一起看
| 存的地方 | 有没有到期日 | 谁负责执行 | 服务器能不能删 |
|---|---|---|---|
| Cookie | 有,Expires或Max-Age | 浏览器,到点自己删 | 能,重发一个过去时的到期日 |
| localStorage | 没有这一栏 | 没有人 | 不能,除非发Clear-Site-Data |
| sessionStorage | 没有,但关标签页就没 | 浏览器,关页时清 | 不能 |
| IndexedDB | 没有这一栏 | 没有人 | 不能,除非发Clear-Site-Data |
同一次访问里,这125个站还给了我中位24条cookie,最多的一个站101条——这个量级本身就够写一篇了,老用户打不开页面而SEO工具全返回200说的就是它攒过头之后的样子。这里面持久型3144条、会话型336条——持久型每一条都带着到期日,浏览器到点自己就删了。关于这些到期日是怎么写下的、谁在替它们续期,之前那轮569条Set-Cookie到期日的实测拆得更细。
两块地就此分道扬镳:cookie这边,规则是写下来的,浏览器照着执行;localStorage这边,规则要么没写,要么写了也没人执行。同一份数据换个抽屉放,它的寿命就从有期变成了无期。
带着这份存储再进一次,键会变少吗?
这是我最想知道的一问。第一次访问结束后不关浏览器,让存储原样躺着,再访问一次同一个首页。如果这些脚本真有自我收拾的能力,第二次就是它们表现的机会。
结果是:67个站的键变多了,58个站原样不动,变少的一个都没有。增量中位是1个,最多的一个站多了44个。
再按键名集合逐个比对,比键数更能说明问题:121个站在第二次访问后,第一次写下的键名一个都没消失。只有4个站少掉过键——burrow和charleskeith少的是Braze那条ab.storage.attributes.<uuid>,dji少的是sawebjssdkpageleave-541099205,dollarshaveclub少的是_fs_bundle。四个站,四个键,全样本就这么多。
第二次访问新增的键去重后有262种,累计376个。最常见的是_pin_unauth_ls(10个站)、_gcl_ls(8个)、lastExternalReferrer与lastExternalReferrerTime(各8个)。也就是说,就算你什么都不做,只是再打开一次首页,浏览器里那份清单也只会变得更长。
服务器这边有没有一个删除键?
有,只有一个。Clear-Site-Data这行响应头能让浏览器把指定类型的数据清掉,storage这个取值管的正是localStorage、sessionStorage和IndexedDB。W3C的Clear-Site-Data规范里定义了这几个取值各自的边界。
131个站的首页响应里,发这行头的有0个。
这个零并不意外。这行头的典型用法是退出登录那一次跳转,首页本来就不该带它。但它的存在方式恰恰印证了本篇的主题:服务器唯一一个能清空浏览器存储的手段,是一个只在特定跳转上出现一次的响应头——它不能事后补发,不能定向到某个用户,也不能对着一个已经三个月没来过的访客生效。那个人的浏览器里还躺着你三个月前写下的东西,而你没有任何办法让它消失。
就算发了,它管的范围也和你想的不一样
这行头里逗号分隔的几个取值,作用域不在同一个层级上:cookies那一项会一路扫到兄弟子域,storage那一项只待在发出它的那一个源里。保哥之前专门写过一张自家子域上的图片把主站登录态删空的那次事故,边界踩偏的代价比想象中大。至于什么算同一个站、什么算另一个站,那是公共后缀列表划的线,不是你的目录结构说了算。
真正会替你收拾的,只有一家浏览器
有一个例外值得单独说:Safari会主动清。WebKit在2020年3月那篇公告里宣布,所有由脚本写入的存储——localStorage、IndexedDB、Service Worker注册与Cache API——在7天没有用户交互之后会被一并删除。
于是同一批键在不同浏览器上的寿命完全不同:Safari上7天,Chrome上直到用户自己动手。这意味着任何依赖localStorage记住状态的功能,在Safari用户身上有一条你没写过的过期规则,而在Chrome用户身上一条都没有。如果你的A/B分组、地区选择、优惠券领取记录是靠这块地记着的,那这两拨用户走的根本不是同一套逻辑。
同一个键的第三代来了,第一代还躺在那儿
翻键名的时候撞见一组特别典型的东西。有4个站的浏览器里,同一个基础键名的多个版本同时存在:
- assos:sa-user-id-v2和sa-user-id-v3
- avocadogreenmattress:sa-user-id-v2和sa-user-id-v3
- peakdesign:sa-user-id-v2和sa-user-id-v3
- brooklinen:sa-user-id-v2、sa-user-id-v3、sa-user-id-v4,三代同堂
而且注意,这是第一次访问、全新配置就写下的三个键。不是历史遗留,是今天的脚本一次性写了三代格式。
全样本里键名带版本号的有77个,涉及47个站。写版本号这个动作本身说明作者很清楚格式会变;可版本一升,代码里换的是新键名,旧键名没人管。在浏览器存储里,升级从来不是替换,只是新增。
这个模式和第三方脚本的完整性校验是一对孪生问题:那边是你抄下来的那串哈希把新版脚本拦在门外,这边是新版脚本进来了,旧版留下的东西没人带走。
浏览器自己报的用量,为什么是0?
这一节讲我的尺子,而且这次尺子错了两回,第二回错得挺贵。
第一处:用量接口不数这块地
采集的时候顺手调了navigator.storage.estimate(),想拿一个官方口径的用量。125个站,返回的usage中位数是0字节,最大的canyon也只有161008。而我自己按字符数加出来的localStorage总量是3271021。
差了三个数量级,不是采集出错。MDN对estimate()的说明里讲得明白,这个接口返回的用量包含哪些存储由浏览器自行决定,实测下来Chrome数的是IndexedDB、Cache API这一类,localStorage并不在内。
所以如果你拿浏览器的用量接口去盘这块地,得到的结论会是这些站什么都没存。这个坑在做自动化审计时特别容易踩——接口返回了一个正常的数字,没有报错,只是它数的不是你要数的那样东西。
顺带说一句IndexedDB:125个站里,首次访问就建了库的有40个;再访问一次之后,又有9个站的库变多了。出现最多的库叫true_rand_gen_sequence.dat__db(10个站)和VISUALLY_IO(7个)。这一层同样没有到期机制。
第二处:一个看起来完美的因果,拆开就散了
主实验做完,我注意到有14个站第二次访问时页面体积相差10%以上,其中三个特别夸张:mackweldon从144万字符掉到9945,标题变成Just a moment;thefarmersdog从62万掉到28690,同样是Just a moment;warbyparker从115万掉到7068,标题只剩一个品牌名。
第一反应很兴奋:这不正好是本篇的论点吗——你第一次访问留在浏览器里的东西,成了第二次访问时把你判成机器人的依据。于是我做了个对照:同一时刻、同一条线路,换一个全新的空配置再访问一次。三个站全部正常返回。看上去因果链闭合得漂漂亮亮。
但这个对照有个漏洞:换成全新配置,变的不只是存储,cookie也一起没了。于是又跑了一轮,把两者拆开——同一个配置里依次做四趟:全新第一次、原样第二次、只清cookie留存储、cookie和存储都清。
| 站点 | 全新第一次 | 原样第二次 | 只清cookie | 两样都清 |
|---|---|---|---|---|
| mackweldon | 正常143万 | 正常147万 | 拦截9945 | 拦截9945 |
| thefarmersdog | 正常66万 | 拦截28690 | 正常66万 | 拦截28690 |
| warbyparker | 正常115万 | 拦截7068 | 正常119万 | 拦截7068 |
结果既不一致也不可复现:mackweldon这一轮的第二次访问根本没被拦,而清掉cookie之后反倒被拦了;另外两个站被拦的时机也对不上任何一种单一解释。更像是这些站的风控在按访问序号或者节奏出手,跟浏览器里躺着什么关系不大。
教训是新形态的:一次对照只能证明这两组不一样,证明不了是哪一项造成的不一样,除非你把那一项单独变动过。而且第一版的错误方向很值得警惕——它恰好指向更有故事的那一边。这一条已经是连着第三批栽在同一处了,尺子要是总往有利于结论的方向偏,先怀疑尺子。这条线索就此作废,本篇不据此下任何结论。
存进去的到底是什么?
把2507个键的值扫一遍,能看出浏览器里这份清单主要在装三类东西。
一个跟着你走的编号
462个键(18.4%)、85个站的键名或值里带着一个UUID或者32位十六进制串。allbirds的___ELEVAR_GTM_SUITE--userId、redo.anonymous_shopper_id、aloyoga的builderVisitorId、avocadogreenmattress的smartDashCookieValue,形态大同小异。
这些编号和cookie里的那些不一样:cookie有到期日、有SameSite属性、有域和路径的边界,浏览器围着它建了一整套规矩,那些规矩失效时的表现保哥在付款回来购物车就空了那篇里拆过。而localStorage里的编号,什么规矩都没有,它就是一串字符,躺在那儿,直到有人手动清掉浏览器数据。
带凭据味道的那些
145个键、65个站的键名或值里出现了token、session_id、auth、jwt这类字样。其中10个站有一个叫alia-jwt的键,值是标准的JWT形态。
把令牌放进localStorage是个老话题了。要点只有一条:浏览器存储里没有HttpOnly这种东西,页面上跑着的任何一段脚本都能把它读走。而按上一节的数字,这些页面上跑着的脚本,七成以上不是站点自己写的。
你是谁、你在哪、你说什么语言
63个键、39个站存的是身份与环境:ikea存LOCAL_ISO_STORAGE_KEY和LOCAL_LANGUAGE_STORAGE_KEY,flyingtiger存ls-locale和ls-currency,chubbiesshorts存了一个完整的locale对象还额外存了localeMismatch,framebridge存了geolocationCoords,里面是一对经纬度。
这一类值决定的是下一次访问看到什么版本的页面,也正是这一节要交代的第三处尺子问题:我抓到的那些地区值,写的是新加坡——arcteryx存的geolocation里city是singapore,aloyoga存的alo-users-prefered-country是SG,charleskeith存的是SG:en。这不是站点的默认值,是我这条出口线路的指纹。同一件事在缓存碎片那篇里也出现过:决定你看到哪一版的,往往是十分钟前路过的那个人,不是你的配置。凡是跨地域的抓取,先证明内容不随你的位置变,否则量到的是自己。
爬虫永远是第一次访问,它看到的是哪一版?
前面全是浏览器里的事,这一节说它怎么落到搜索和AI引用上。
关键点很简单:爬虫每一次来都是第一次。Googlebot的渲染是无状态的,上一次访问留下的localStorage不会带到下一次;AI爬虫更是连JS都不一定跑。所以凡是靠这块地记着的状态,机器那一侧永远是空的。
这会带来两个方向的偏差
一个方向是你精心做的个性化,索引里一份都没有。用存储记住地区、货币、语言、分组,回头客看到的是定制过的页面,爬虫看到的是那个谁都没登录过的默认版本。搜索结果本身也会被登录态、位置、历史这几个变量改写,只不过那是搜索引擎那一侧的事,跟你写进访客浏览器的这份状态是两条线。至于爬虫和真人到底看到了什么,那得实际比一比才知道,站内那篇揪出爬虫和用户页面差异的方法讲的就是这件事。
另一个方向是你以为已经修好的问题,在回头客身上还没修。改了模板、换了文案、上线了新版,爬虫下一次抓就看到了新的;而那个浏览器里还躺着旧版本状态的老用户,可能还在被旧逻辑接管。这一点和JS渲染的丢失不是同一回事——131个站的JS渲染实测说明担心的三件事里两件没发生,正文基本抓得到;真正抓不到的是这种存在于访客本地、服务端根本看不见的状态。
差异该落在哪儿
一句话:凡是需要被索引、被引用、被分享的差异,都不要只写进存储,要落到URL上。地区版本用路径或子域,语言用hreflang,筛选状态用查询参数并配好canonical,这些机器都读得懂。存储只适合放那些机器不需要知道的东西——收起来的公告条、看过的引导层、上次滚动到哪儿。
做GEO技术端优化的时候这条尤其要紧:AI引擎抓到什么就引用什么,它不会为了看你的个性化版本先在你站上逛两圈。同样地,如果页面本身在HTML里就是个空壳,那存储里再多状态也救不回来。用React或Next.js这类框架的站要特别留意,客户端状态和服务端渲染的边界很容易划错。
12个站一个键都不写,它们少了什么?
最后说说对照组,因为它比前面所有数字都更能说明这件事的可选性。
12个站在首次访问后localStorage一个键都没有:bigcommerce、bonprix、iittala、joolz、jysk、notino、oclean、rapha.cc、segway、suitsupply、zalando、zwilling。其中bonprix和zalando连cookie都是0条。这些不是玩具站,zalando是欧洲最大的时尚电商之一。
它们少了什么?从功能上看,什么也没少:商品照常展示,购物车照常工作,页面照常个性化——只是那些状态放在了服务端会话或者cookie里,而不是浏览器的这块地上。
差别在平台,还是在装了多少应用
按建站平台切一刀,答案很清楚:
| 建站平台 | 站数 | localStorage键数中位 | 最大值 |
|---|---|---|---|
| Shopify | 65 | 26 | 89 |
| Salesforce Commerce | 10 | 3 | 45 |
| Next.js自建 | 9 | 6 | 20 |
| Magento | 3 | 8 | 14 |
| 认不出 | 24 | 3.5 | 31 |
Shopify站的键数中位是其余几类平台的三到九倍。但这笔账不该记在平台头上——Shopify自带的那213个键分摊到75个站上,平均不到3个。真正把数字撑起来的是应用生态:Elevar、Klaviyo、Gorgias、Redo这些,每装一个就多一批键,而且卸载应用时几乎没人会去清访客浏览器里的残留。
自己站上怎么盘这块地
不用什么工具,浏览器开发者工具的应用面板就够,按这个顺序走一遍:
- 用一个全新的隐私窗口打开首页,什么都别点,等10秒,然后数存储面板里有多少个键。这个数就是你派发给每一位新访客的行李。
- 逐个认领:哪些是你自己的代码写的,哪些认不出来。认不出来的那些,去对照你的第三方脚本清单,对不上的说明有脚本是别人带进来的。
- 看有没有键名带版本号。有的话,搜一遍代码里还读不读旧版本;不读了就说明访客身上背着一份永远不会被清的死数据。
- 把靠存储记状态的功能列出来,逐个问一句:这个状态如果永远不过期,会不会出错。会出错的,要么改到cookie里让浏览器帮你计时,要么在值里写上时间戳并且每次读取都真的去校验。
- 把这些功能再问一遍:爬虫看不到这个状态,页面会不会缺内容。会缺的,说明这块差异应该落到URL上。
- 卸载任何第三方应用时,加一条收尾动作:在下一版代码里删掉它遗留的键。没人会替你做这件事。
顺带一句,同意管理这块也值得对齐。选同意管理平台的时候大家盯的都是cookie,但用户点拒绝之后,已经写进localStorage的那些编号在不在清理范围内,各家做法差别很大——这一点最好在隐私政策里写清楚,也最好实测一遍。
说到底,这块地的性质就是这样:浏览器的HTTP缓存你还能靠响应头和文件名去管,服务端那几层缓存你更是想清就清,唯独浏览器存储,写进去容易,写完之后它就归对方保管了。你能改的只有下一次写什么,改不了已经写下去的那些。
另一头还有一件反过来的事,同样跨过了那条线:一年前那份页面要的静态文件,今天三条里有一条已经是404——你在服务器上删掉的旧构建产物,别人手里那份旧HTML还在照着地址索要。一边是删不掉,一边是留不住,说的其实是同一条边界。
常见问题解答
localStorage里的数据到底能存多久?
规范上没有到期时间,理论上一直存到用户清除浏览器数据。实际有两个例外:Safari会在7天没有用户交互之后清掉所有脚本写入的存储;所有浏览器在磁盘空间紧张时都可能驱逐非持久化的存储。除此之外没有任何机制会让它消失,服务器也没有办法主动删除某一个访客的那一份。
服务器能不能删掉用户浏览器里的localStorage?
只有一个办法:在某个响应上发Clear-Site-Data这行头,并带上storage这个取值。但它只在那一次请求发生时生效,对不再来访的用户完全无效,而且作用范围只覆盖发出这行头的那个源。实测131个站的首页,一个发的都没有。
为什么开发者工具里的存储用量和我自己算的对不上?
因为navigator.storage.estimate()返回的用量通常不包含localStorage,它数的主要是IndexedDB和Cache API。这次实测125个站,这个接口报的用量中位是0字节,而按字符数算出来的localStorage实际装着327万个字符。做自动化审计时要用哪个口径,得先确认清楚。
把登录令牌放在localStorage里到底行不行?
技术上可行,风险在于浏览器存储没有HttpOnly保护,页面上任何一段脚本都能读到它。而本次实测里,一个站的存储键有七成以上是第三方脚本写的,也就是说页面上跑着大量你没写过的代码。如果一定要放,至少要把令牌的有效期做短,并且让服务端有单独作废它的能力。
第三方应用卸载之后,它写进用户浏览器的键会自己消失吗?
不会。卸载的是你站上的那段脚本,访客浏览器里已经写下的键不受影响,会一直留着。这次实测里有4个站同时存在同一个键的两三个版本,就是这个模式的实证:新版本写了新键名,旧键名没人回收。
爬虫会读取localStorage里的内容吗?
不会带着上次的存储来。Googlebot的渲染是无状态的,每次都相当于全新访问;多数AI爬虫连JavaScript都不执行。所以任何靠存储决定的页面差异,在索引里都不存在。需要被搜索到的差异,要落在URL、HTML或者响应头上。
存储里存了用户所在地区,会不会影响SEO?
会,而且方向是负面的。爬虫拿不到那个值,看到的永远是默认版本;如果默认版本的价格、库存、配送信息与你想让用户看到的不一致,索引里留下的就是那份不一致的内容。正确做法是把地区差异做成不同的URL,再用hreflang互相声明。
权威参考资料
本文标题:《localStorage里那15个键,服务器一个也删不掉》
本文链接:https://zhangwenbao.com/browser-storage-keys-server-cannot-delete-audit.html
版权声明:本文原创,转载与引用请注明作者与原文链接。许可协议: CC BY 4.0