互联网遍地是数据,品类丰富、格式繁多,包罗万象。数据采集,或说抓取,就是把分散各处的内容,通过各种方式汇聚一堂,是个有讲究要思考的体力活。君子爱数,取之有道,得注意遵守相关的法律法规和网站的使用政策😎
在项目实践环节,我将从实际的需求出发,分享个人的思路与代码,希望能够起到达到抛砖引玉的作用。局限于个人的知识技能,有不足之处,望不吝指出🤝。
本期我们分享一个采集某网站发布的工程项目中标公示数据的需求及思路,B站视频演示: [video(video-gVB8RRK2-1709877457749)(type-bilibili)(url-player.bilibili.com/player.html…)]
特别注明:
- 本文仅作为技术交流用,请勿他用
- 爬取大量带有知识产权的数据,并用于商业目的,属于违法行为
- WEB采集技术本身并不违法,关键在于使用的方式和目的
需求
某公共资源交易平台定期会发布工程项目中标公示,想要把这些信息,批量采集并提取关键字段成表格形式。
分析
人工操作流程
首先我们先看看,人工操作是如何获取数据。
1、打开官网首页,按照下图路径检索
2、从详情页中采集下图的信息(CTRL+C、CTRL+V到指定的列)
3、编辑 Excel 表格,完成一条数据录入,接着进行下一则。
整个流程不算复杂,但是操作一则也需耗费 30~60 秒。我估算了下,存量数量大概在 2 万条,总耗时约300个小时。再加上每日更新,那么做一个机器人还是能够节约很多时间成本。
请求分析(抓包)
上述步骤简单明了,但是耗时还是偏长,用机器模拟分页、点击等动作,取一条数,莫约耗时 10 秒,单机运行效率还是太低了😂。有没有更好的办法呢?
别急,我们先看看网络请求。
我们分别进行一次分页查询和打开详情页,在开发者工具,切换到网络面板,可以看到如下图的公示清单响应:
从中看出,后端会返回指定类型的数据总量,以及分页数据清单。我们就可以直接拼凑参数,让浏览器发起该请求,拿到数据,如此一来,就不用操控 DOM 了。接着,我发现请求中有名为
pageSize 的参数,WEB 开发人员对这玩意一定不会陌生,它是用于告诉后端每页获取的数据量,此处默认是 15。我尝试调大该值,不出意料,能够一次性将类型下的存量数据拿下。甚至都用不上插件,直接从代码中发起请求就完事😎。
再看公示详情页,从 HTML 源码或者 DOM 结构可以看出,真正的内容是嵌套其中的。
思路
数据获取
- 确定类型(行业),每次处理一种
- 一次性获取存量公示,推荐直接用
fetch函数 - 新建数组
rows用于存放结果 - 遍历上一步的结果(过滤掉废标),对每则公示:
- 拼凑详情页 URL,获取公示内容
- 提取字段(详见下节)
- 追加到
rows
- 把
rows转换为文本,并写入磁盘
字段提取
公示内容为段落和表格(table标签,通常为两列、三列、四列)组合的纯文本,可以直接抽取,按行分割成字段名:内容形式,然后利用正则表达式匹配,进而提取字段。
例如,工期:180日历天,正则匹配:(交货期|期限|工期)[^:]{0,15}:。
//`字段名:内容`格式的数据行
const lines = []
// 待采集字段的属性名以及正则
const columns = {
xmmc:"项目名称",
xmbh:"(项目|招标|标段)编号",
fbrq:"发布日期",
zbr:"中标(人|单位)(?!公示)",
zbj:"中标(价|费率|金额)|报价",
gq:"交货期|期限|工期",
xmjl:"项目(经理|总?负责)|联系人",
jgbm:"(监督|受理)部门"
}
// 抽取信息
let d = {}
Object.keys(columns).map(key=>{
// 关键字匹配不超过 15 字
let regex = RegExp(`(${columns[key]})[^:]{0,15}:`)
for (let index = 0; index < lines.length; index++) {
const line = lines[index].replaceAll(' ', "").trim()
const m = line.match(regex)
if(m){
d[key] = line.split(m[0]).pop().replace(/\t/g, "").trim()
lines.splice(index, 1)
break
}
}
})
总结
效果展示
不足之处
- 正则表达式匹配,使用的是专家规则,难免有遗漏的情况
- 目前仅支持
CSV格式 - 受限于个人能力,思路存在局限性😂