大规模数据上传的问题和解决
过完年第一天上班,就接到一个项目连夜出差去了。客户手头有大约 20 万条住房信息需要上报到一个指定的系统。这 20 万条记录由 200 多个员工花了超过一个月的时间到该县辖区内的各自然村挨家挨户上门采集,然后以乡镇-行政村-自然村的结构存放在 1000 多个 excel 文件中。excel 中的每条记录对应一套住房,同目录下,还有房屋的照片。
这些信息和图片都要上传到一个指定的网站,当然,没有接口。
所以,问题还是很清晰的,就是实现一个爬虫模拟请求,先登录网站,然后从 excel 中依次读取记录,将相应的信息和图片进行上传,重复这个过程。
常规问题
技术选择
其实没什么好选,我最熟悉的语言是 PHP 和 JavaScript,最终选了 Node.js 。平常处理网络请求已经用的很多,处理数据的话有 underscore ,模拟请求有 superagent ,处理 dom 内容有 cheerio ,想要控制队列和并发有 async ,处理 excel 有 xlsx 。性能和开发速度也有个很好的平衡。
验证码和保持会话
只要网站是需要登录的,做爬虫就会遇到这两个问题。验证码简单可以用算法识别,验证码复杂就人工介入,遇到验证码后下载到本地,然后弹出对话框等待人工识别后回填。会话保持的话 superagent 本身就有 cookie 机制,不是问题。
表单分析
每个请求需要发送什么内容,有哪些参数,如何和原始数据对应,这个工作琐碎而费神,好在有同事帮忙处理。
大局观
之前提到的问题都是纯粹的技术问题,以往的经验基本都已经覆盖到了,下面要讲的则是我在这个项目中最深刻的感悟。这些概念其实很早就接触过,或隐约有体会,但这次是深深的体验了一把。
每次只做一件事
顺利的情况下,上传一条记录包括下列步骤:读取一条数据、预处理一条数据、上传一张图片、拼接表单、发送请求、记录日志。
理想情况下,我可以先实现一个处理一条记录的方法,然后在外面套一层循环,问题就解决了。但事实上,每个步骤本身都很复杂,想要一步不错的走完这个流程还是有难度的,预处理时会发现数据不合要求、上传图片可能由于网络原因失败,代码质量的问题也可能导致程序意外终止。
更合理的做法是把整个流程用几个相对独立的小程序来实现,一个程序用来压缩图片,一个程序用来上传图片,一个程序用来检查数据质量,一个程序用来上报数据。就像 Unix 的管道一样,上一个工具的输出,可以作为下一个工具的输入。
比较值得一提的一点是,这几个独立程序的输出,必须以文件的形式保存下来。
这样做最大的好处就是让问题显而易见,每经过一个步骤,你都可以对产物进行检查,有没有出错一目了然。压缩失败,目录里的文件会缺,上传失败,上传结果汇总表的数据会比文件数量少,这比看程序运行时内存里的数据容易多了。
这些产物本身也是日志,比如上传时部分成功,部分失败,下一次上传的时候,可以根据上一次上传的结果,跳过已经成功的部分,只补传上一次失败的,简单的续传就实现了。这一点在数据量大的时候尤其重要,节约的时间非常可观。
不要相信用户
做网站的时候,出于安全的考虑,有一个原则叫做不要相信用户,因为总有人想要搞破坏,绕过你的防线。
我发现处理大量数据的时候也存在这个问题,你不能相信用户给的数据是符合要求的。这里,用户也想把数据质量做好,但确实无法做到。首先数据来源不一,要求 200 个人采集来的数据格式完全相同几乎是不可能的,即使经过培训,有统一模版,还有专门的宏来解决一些重复操作,也不可能。
一开始用户说我们负责程序,他们负责保证数据正确,我信了,结果我在加班。增加了一个功能,专门检查数据的正确性。
垃圾进,垃圾出 Garbage In Garbage Out GIGO
之前说到我们负责程序,他们负责数据正确性,看上去责任很明确。为什么我还是要做检查数据正确性的工作呢。一方面是为了互相配合更好的完成工作,另一方面是因为数据量大了以后,错误很难定位。
具体到这个项目中,在上传过程中,有一个地方需要将 excel 中的多条记录合并上传,比如根据身份证判断为同一个人,他名下的房屋要合并成一条记录后再上传,但实际上数据里存在两个人填了同一个身份证或者里面填写的房屋数量和实际条数不符的情况。
出现上述问题的情况下,按照正确数据格式设计的合并功能将无法正确工作,最终导致上报到系统中的条数和原始数据条数不一致。
那么问题来了,我如何证明程序是对的,错是在数据呢。从几万条数据里找到那条出错的数据,然后告诉客户“你给的这条数据有问题”吗?
世界上有很多东西,要证明正确是很难的,要证明错误却容易的多,程序就是其中之一。
最后一点,考虑的全面一点
这个太难了,经验会让你考虑到更多问题,却总是不能全面。这也是我写这篇文章的目的之一,对遇到的问题进行总结,下次多少能思考的更全面。
关于这个项目,最有意思的是,我没有预先考虑到要做删除数据的功能。程序唰唰唰一跑,几千几万条数据就上去了,如果出错了,根本定位不到哪些条目是错的,尤其是测试的时候,刷了一片数据上去。手动删可真要删到翻白眼了。
做一个批量删除吧。