文章目录
前言
这次起源于我常玩的一个游戏,头文字D:激斗,那么众所周知这是一款街机游戏,故而它的计时赛成绩什么的固然没有现成的表可以看,游戏内的玩家数据也是为街机设计的,你不能把它方便地截下来给朋友分享,于是我开始考虑做一个工具方便看表。
于是,DACreator 这个项目就诞生了。它的核心目标很简单:自动爬取玩家的全赛道成绩,直接生成可视化成绩表格图片。此文之用乃是记录这个项目从一个跑不通的烂尾脚本,到CLI版本最终完工的全过程,包括所有踩过的坑、技术选型的转折、以及一步步迭代优化的思路。
一、早期探索和严重路线错误
最开始的想法很朴素:既然网页上有数据,那写个爬虫把数据抓下来,再用Python画成表格,ok,大功告成矣!
我最开始对这个网站的技术架构完全不了解,打开浏览器看了一眼,发现成绩数据是滚动加载的,不会一次性渲染在页面上,所以一拍桌面就说:这是个前端动态渲染的页面,必须用浏览器模拟才能拿到数据。
于是,我第一版的爬虫,用了Selenium无头浏览器方案。思路也很直接:
- 打开成绩页面,模拟选择赛季、赛道、车型
- 触发页面的change事件,等待排名列表加载
- 逐行提取DOM里的用户名、成绩、排名数据
- 遍历所有赛道,重复之直到完成
但写完之后,我遇到了第一个致命问题 :程序能正常启动,能遍历赛道,但永远提示“找不到用户成绩”。换了多少个账号、加了多少等待时间、模拟了多少次页面点击,都拿不到任何有效数据。页面永远加载不出来真实的排名列表,控制台里全是inner_div 无成绩行、找不到赛道ID的报错。
那段时间我反复调试,改元素定位、改等待逻辑、甚至换了Playwright框架,结果全部失败。现在回头看,这是整个开发过程中走的最大的弯路:我从根上就搞错了数据的来源,Selenium这套方案,从一开始就是错的。
二、找到真实的数据接口,程序已可用
转机来自于我有天晚上失眠 ,闲来无事思考我的爬虫,我突然意识到一个问题:如果连POST和GET方法都没搞对,怎么能得到期待的数据呢?于是我跑去问AI,Deepseek给出的结论是:很有可能! 我立刻从床上弹射起来掀开电脑开始分析。
我把网页完整的Elements DOM结构扒了下来,一行行分析。
我终于发现,这个网站的前端页面根本就是个“空壳”。所有的成绩数据,根本不是前端渲染出来的,而是来自于一个后端POST API:https://arcadezone.cn/ranking/timetrial。网页上所有的排名数据,都是通过这个接口,携带CSRF-Token、赛季、赛道、页码参数请求之后,返回JSON格式的数据,再渲染到页面上的。
这个发现直接推翻了我之前所有的工作。既然有现成的API,那我根本不需要模拟浏览器,直接请求接口拿数据就完事了,不仅速度快,还不会被页面结构变化影响,岂不美哉?
当天晚上,我直接删掉了所有Selenium相关的代码,抛开之前所有的错误逻辑,基于这个API重写了一版极简的爬虫。核心逻辑非常清晰:
- 先请求网页,拿到CSRF-Token,解决接口鉴权问题
- 按赛季、赛道、页码构造POST请求,直接获取JSON格式的排名数据
- 解析JSON,匹配目标用户名,提取成绩、车型、时间、排名等核心字段
- 把数据整理成DataFrame,输出标准化的CSV文件
第一次运行这个脚本的时候,看着控制台一行行输出匹配到的成绩,我如释重负:首次100%成功爬取到了目标用户的有效成绩。这个最小可用原型,就是整个DACreator项目的真正起点。
三、优化与打磨
有了能跑通的核心爬虫之后,我并没有急着加功能,而是先解决最影响使用体验的问题,一步步把这个简陋的脚本,打磨成一个真正能用的工具。
1. 体验优化:解决控制台刷屏,实现进度可视化
最开始的原型,爬取过程用的是“一页一行”的日志打印,遍历48个赛道、每个赛道十几页数据的时候,控制台会被大量冗余信息直接刷屏,完全看不到整体进度,也不知道程序是不是卡死了。
于是我做了第一个优化:引入tqdm库实现双层进度条,外层是赛道总进度,内层是单赛道的页码进度,替代了原来的逐行打印。后来又进一步优化成了单行实时刷新进度,用\r回车符实现同一行内更新「总进度+当前赛道+当前页码+已匹配数据量」,彻底解决了控制台刷屏的问题。
现在运行爬虫,控制台只会有一行动态更新的进度提示,整个爬取过程清晰可控,用户终于能直观地知道程序跑到哪了,还要等多久。
2. 小修正
进度条搞定之后,我开始解决核心的数据解析问题。最开始的脚本,有两个致命的bug,直接导致生成的成绩表缺陷十分多:
- 「タイム評価(等级评价)」字段完全识别失效,控制台频繁报
hyoka 文件 = 未知文件,表格里对应的等级位置全是空白; - 「全国順位」字段经常解析缺失,要么显示“None”,要么计算出的排名和网页端对不上,数据完全不可信。
我先对着网页DOM结构啃排名问题,很快就找到了症结:全国顺位藏在ranking-list-item-rankBox类的元素里,需要提取纯数字才能排除干扰,再用(当前页码-1)*每页条数+当前索引的公式,精准计算出真实排名——这个问题很快就解决了。
但等级评价的问题,十分头疼。最开始我以为能从图片文件名里的hyoka_x.png提取等级,试了各种方法:从DOM里读图片src、用正则匹配文件名数字、甚至手动建了个映射表,但要么拿到的路径是相对地址无法解析,要么匹配到的数字和实际等级对不上,始终绕不开“未知等级”的报错。
我没招了,那就直接古法判定吧:直接在assets下建了个rank.csv本地映射表,把网页上所有可能出现的等级文本(ROOKIE、REGULAR、EXPERT、MASTER、LEGEND等)和对应的图片文件名(rookie.png、regular.png等)一一列出来,用pandas读取CSV构建字典映射。
虽然比较古法,但是这招十分管用啊:
- 彻底解决了网页图片路径解析的问题,不管网站后续怎么改图片地址,本地CSV只要对应好就能用;
- 新增等级时,只需要在CSV里加一行,不用改代码,维护起来反而更灵活;
解决完数据解析的核心问题,我可以推进整个项目的核心目标——生成可视化表格图片。我在core.py里用PIL库写了完整的绘制逻辑:
- 设计了清晰的表格样式:表头用深色背景突出,内容行做奇偶异色区分,避免看表看岔;
- 适配了多字体需求:日语用
YuGothB.ttc、数字用consolab.ttf、中文用msyhbd.ttc,解决了之前特殊字符显示乱码的问题; - 把等级文本通过本地CSV映射到对应的图片,直接嵌入表格,和网页端的显示效果完全一致;
- 预留了两种表格格式:带全国排名的“完整版”和只显示成绩的“简洁版”,后续加功能时不用大改代码。
到这里,整个工具终于实现了全流程功能闭环:从API请求拿到数据,到本地CSV解决等级映射,再到生成规范的表格图片,每一步都能稳定运行。
关于rank.csv,我是自己一个一个推出来的。我第一次把表做出来之后计时赛成绩比对的等级很混乱,LEGEND都跑出来了,后来排错发现是表格里面有几个全角引号。
3. 架构重构:模块化解耦,告别单文件脚本
功能闭环之后,我遇到了新的问题:所有代码都写在一个单文件脚本里,爬虫逻辑、图片渲染逻辑、CLI交互逻辑全部大锅炖在一起。后来网站的API参数有一点小调整,我改代码的时候,差点把图片渲染的逻辑改崩了。
我意识到,这个项目不能再是一个“一次性脚本”了,必须做架构重构,遵循单一职责原则,把不同的功能模块拆分开。
最终的重构方案非常清晰:
spider.py:纯爬虫核心模块,只负责API请求、数据解析、CSV生成,对外暴露统一的crawl_data()接口,保留独立运行能力core.py:纯图片渲染核心模块,只负责CSV读取、表格图片生成、结果保存,完全不耦合爬虫逻辑,只调用spider的标准化接口- CLI主入口:负责用户交互、模式选择、模块调用,串联全流程
这次重构的好处是立竿见影的:后来网站的API结构、赛季参数有变化,我只需要改spider.py里的代码,图片渲染的逻辑完全不用动;想要调整表格样式,也只需要改core.py里的配置,不会影响爬虫的稳定性。整个项目的可维护性、可扩展性有大大的提升。
4. 功能扩展:搜索模式,实现性能质的飞跃
重构完成之后,arcadezone.cn网站上线了一个新功能:按用户名搜索。原来的爬虫,用的是「遍历全赛道→每个赛道遍历全部分页→过滤目标用户」的暴力遍历模式,哪怕有进度条,也要几十次HTTP请求,耗时几十秒,效率极低。
而新的搜索功能,可以直接通过用户名,精准获取目标用户的全赛道成绩,完全不用遍历赛道和页码,这样一对比,我以前的古法爬虫和西西弗斯似的。
我立刻基于这个新功能,做了一次核心逻辑的升级,新增了spider_search.py搜索模块。这里我用了面向对象的继承特性,让搜索爬虫类直接继承原来的ArcadeZoneCrawler基础类,复用了已经写好的等级判断、时间格式化、CSRF获取、异常重试等所有基础逻辑,只需要重写搜索请求和结果解析的方法,完全没有重复代码。
这次升级,让整个工具的性能实现了质的飞跃:
- 原遍历模式:时间复杂度O(赛道数×单赛道页数),需要几十次HTTP请求,耗时几十秒
- 新搜索模式:时间复杂度O(1),仅需1次搜索API请求,耗时压缩到1~2秒,性能提升10倍以上
同时,我在core.py的CLI入口里,新增了第三种运行模式,最终形成了完整的三个功能:
- 全量遍历爬取(含排名)
- 本地CSV文件生成图片
- 用户名搜索快速生成(无排名)
到这里,DACreator CLI版本的所有功能,就全部完工了。
四、最终成品
这就是最终发布在GitHub上的CLI版本的完整项目目录,每个文件的职责清晰,完全符合最初的架构设计:
DACreator/
├── assets/
│ ├── font/ # 表格渲染用的字体文件
│ │ ├── YuGothB.ttc
│ │ ├── consolab.ttf
│ │ └── msyhbd.ttc
│ └── rank/ # 等级评价对应的图片
│ ├── rookie.png
│ ├── regular.png
│ └── ...
├── core.py # 图片渲染核心 + CLI主入口
├── spider.py # 全量遍历爬虫模块
├── spider_search.py # 用户名搜索爬虫模块
├── Player_ID.dat # 用户配置文件(ID、赛季)
├── requirements.txt # 项目依赖
├── README.md # 项目文档
└── LICENSE
五、总结
整个CLI版本的开发,前前后后花了一个多月的业余时间,踩了无数的坑,也学到了很多东西。这里也做一个简单的复盘,略总结其经验:
-
先搞懂数据来源,再动手写代码
最开始的Selenium弯路,完全是因为我想当然地判断了网站的技术架构,没有先去抓包分析接口。做爬虫之前,一定要先搞清楚数据到底是从哪来的,不然写再多代码都是无用功。 -
编码问题永远是玄学,一定要提前处理
搜索模式开发的时候,遇到了中文用户名搜索不到的问题,排查了很久才发现,是POST请求的时候,没有把JSON数据用utf-8编码。最终用json.dumps(payload, ensure_ascii=False).encode('utf-8')完美解决,这个坑踩过一次,就再也不会忘。 -
单一职责原则,永远不会过时
最开始的单文件脚本,改一个地方就可能崩全量功能。重构拆分模块之后,后续的迭代效率直接翻倍,哪怕是后来做GUI版本,也能直接复用spider.py和core.py的核心代码,几乎不用修改。
总的来说这次开发是很有价值的,属于是非常大胆也十分成功的经历。
项目地址:Github仓库:DACreator
免责声明
- 此项目为个人基于对《头文字D:激斗》的兴趣开发,仅作技术学习与个人娱乐使用,无任何商业用途,若功能存在问题或体验瑕疵,欢迎各位指正交流。
- 本文为项目开发过程的随手记录,行文风格偏向个人随笔,包含大量口语化表达与主观思考,并非严谨的技术教程,仅供参考。
- 项目中所涉及之字体、图片等资源仅作本地渲染表格之用,未用作任何盈利性用途,若涉及版权问题,可联系本人及时删除。

520

被折叠的 条评论
为什么被折叠?



