网页抓取解密(Git-history正是网页抓取工具编程技术技术回顾(组图))

优采云 发布时间: 2022-01-04 03:12

  网页抓取解密(Git-history正是网页抓取工具编程技术技术回顾(组图))

  大多数人都知道 Git 抓取,这是一种用于网页抓取工具的编程技术。您可以定期将数据源的快照抓取到 Git 存储库中,以跟踪数据源随时间的变化。

  如何分析采集到的数据是一个公认的问题。 git-history 是我设计用来解决这个问题的工具。

  Git 抓取技术回顾

  将数据抓取到 Git 存储库的一大优势在于抓取工具本身非常简单。

  这是一个具体的例子:加州林业和消防局 (Cal Fire) 在 /incidents网站 上维护了一张火灾地图,该地图显示了该州最近发生的*敏*感*词*火灾。

  我找到了网站的底层数据:

  卷曲

  然后我设置了一个简单的爬虫工具,每 20 分钟抓取一个 网站 数据的副本并提交给 Git。截至目前,该工具已运行14个月,已采集1559个提交版本。

  Git 抓取最让我兴奋的是它可以创建真正独特的数据集。许多组织并没有详细归档数据更改的内容和位置,因此通过抓取他们的网站 数据并将其保存到 Git 存储库中,您会发现您比他们更了解他们的数据更改历史。

  然而,一个巨大的挑战是如何最有效地分析采集到的数据?面对上千个版本和海量的JSON和CSV文档,如果只靠肉眼观察差异,很难挖掘出数据背后的价值。

  git 历史

  git-history 是我的新解决方案,它是一个命令行工具。它可以读取文件的所有历史版本并生成 SQLite 数据库来记录文件随时间的变化。然后你就可以使用Datasette来分析和挖掘这些数据了。

  以下是通过使用 ca-fires-history 存储库运行 git-history 生成的数据库示例。我通过在存储库目录中运行以下命令创建了一个 SQLite 数据库:

  git-history 文件 ca-fires.db events.json \

  --命名空间事件 \

  --id UniqueId \

  --convert'json.loads(content)["事件"]'

  

  在这个例子中,我们获取了文件 events.json 的历史版本。

  我们使用 UniqueId 列来标识随时间变化的记录和新记录。

  新数据库表的默认名称是item和item_version,我们通过--namespace event指定表名是incident和incident_version。

  工具中还嵌入了一段 Python 代码,可以将提交历史中存储的每个版本转换为与工具兼容的对象列表。

  让数据库帮助我们回答一些关于过去 14 个月加州火灾的问题。

  事件表收录每次火灾的最新记录。通过这张表,我们可以得到一张所有火灾的地图:

  

  这里使用了datasette-cluster-map插件,它在地图上标记了表中所有给出有效经纬度值的行。

  真正有趣的是 event_version 表。此表记录了每次火灾的先前捕获版本之间的数据更新。

  250 场火灾有 2,060 个记录版本。如果根据_item进行分面,我们可以看到哪些火灾记录的版本最多。排名前十的分别是:

   迪克西火:268

  卡尔多之火:153

   纪念碑火 65

   August Complex(包括 Doe Fire):64

   溪火:56

   法式火焰:53

   西尔弗拉多火灾:52

   小鹿火:45

   蓝岭火:39

   麦克法兰火:34

  版本越多,火持续的时间就越长。维基百科上甚至还有 Dixie Fire 的条目!

  点击Dixie Fire,在弹出的页面可以看到所有抓到的“版本”按版本号排序。

  git-history 只在此表中写入与之前版本相比发生变化的值。因此,您可以一目了然地看到哪些信息随时间发生了变化:

  

  最频繁的变化是 ConditionStatement 列。此栏是文字说明。另外两个有趣的列是 AcresBurned 和 PercentContained。

  _commit 是提交表的外键。该表记录了该工具提交的版本,因此当您再次运行该工具时,该工具可以定位到上次提交的版本。

  连接commits表查看每个版本的创建日期。您也可以使用incident_version_detail 视图来执行连接操作。

  通过这个视图,我们可以过滤所有_item值为174、AcresBurned值不为空的行。借助datasette-vega插件,将_commit_at列(日期类型)和AcresBurned列(数字类型)进行对比,形成一个图表,直观地展示了Dixie Fire火灾随时间的蔓延情况。

  

  总结:让我们首先使用 GitHub Actions 创建一个定时工作流,并每 20 分钟获取一次 JSON API 端点的最新副本。现在,在 git-history、Datasette 和 datasette-vega 的帮助下,我们已经成功地用图表展示了过去 14 个月加州最长的森林火灾的蔓延情况。

  关于表结构设计

  在git-history的设计过程中,最难的就是设计一个合适的表结构来存储之前版本变化的信息。

  我的最终设计如下(为了清晰起见进行了适当编辑):

  创建表 [提交] (

  [id] 整数主键,

  [hash] 文本,

  [commit_at] 文本

  );

  创建表 [项目] (

  [_id] 整数主键,

  [_item_id] 文本,

  [事件 ID] 文本,

  [位置] 文本,

  [类型] 文本,

  [_commit] 整数

  );

  创建表 [item_version] (

  [_id] 整数主键,

  [_item] 整数引用 [item]([_id]),

  [_version] 整数,

  [_commit] 整数引用 [commits]([id]),

  [事件 ID] 文本,

  [位置] 文本,

  [类型] 文本

  );

  创建表 [列] (

  [id] 整数主键,

  [namespace] 整数引用 [namespaces]([id]),

  [名称] 文本

  );

  创建表 [item_changed] (

  [item_version] 整数引用 [item_version]([_id]),

  [column] 整数引用 [columns]([id]),

  主键([item_version],[column])

  );

  前面提到,item_version表记录了网站在不同时间点的快照,但为了节省数据库空间,提供简洁的版本浏览界面,仅将与之前版本相比发生变化的列记录在这里。所有没有改变的列都写为空。

  但是,这种设计有一个隐患,就是如果某列的值在某次火灾中被更新为null,我们该怎么办?我们如何判断它是否已更新或未更改?

  为了解决这个问题,我添加了一个多对多表item_changed,它使用整数对来记录item_version表中哪些列更新了内容。使用整数对的目的是尽可能少占用空间。

  item_version_detail 视图将多对多表中的列显示为 JSON。我过滤了一些数据,放在下图中,看看哪些列更新了哪些版本:

  

  通过下面的SQL查询,我们可以知道加州火灾中哪些数据更新最频繁:

  select columns.name, count(*)

  来自 event_changed

  在 event_changed.item_version = event_version._id 上加入 event_version

  在 event_changed.column = columns.id 上加入列

  where event_version._version> 1

  按列名分组

  按计数(*)降序

  查询结果如下:

   更新:1785

   火灾被扑灭的百分比:740

   状态描述:734

   火灾区域:616

   开始时间:327

   受灾人数:286

   消防泵:274

   消防员:256

   消防车:225

   无人机:211

   消防飞机:181

   建筑损坏:125

   直升机:122

  直升机听起来很刺激!让我们过滤掉第一个版本后直升机数量至少更新一次的火灾。您可以使用以下嵌套 SQL 查询:

  从事件中选择*

  where _id 在 (

  从事件版本中选择 _item

  where _id 在 (

  select item_version from event_changed where column = 15

  )

  和_version> 1

  )

  查询结果显示有19次直升机出动火灾,我们在下图标注:

  

  --convert 选项的高级用法

  在过去的 8 个月中,Drew Breunig 使用 Git 爬虫不断从 网站 中获取数据并将其保存到 dbreunig/511-events-history 存储库中。这个网站记录旧金山湾区的交通事故。我将他的数据加载到 sf-bay-511 数据库中。

  以sf-bay-511数据库为例,有助于我们理解git-history和--convert选项overlay的用法。

  Git-history 要求捕获的数据采用以下特定格式:由 JSON 对象组成的 JSON 列表,每个对象都有一列可用作唯一标识列,以跟踪数据随时间的变化。

  理想的 JSON 文件如下所示:

  [

  {

  "事件ID": "abc123",

  “位置”:“第四和佛蒙特州的拐角处”,

  “类型”:“火”

  },

  {

  "事件ID": "cde448",

  "Location": "555 West Example Drive",

  “类型”:“医疗”

  }

  ]

  但捕获的数据通常不是这种理想格式。

  我找到了 网站 的 JSON 提要。有非常复杂的嵌套对象,数据也很复杂,有些对整体分析没有帮助,比如更新的时间戳即使没有数据更新也会随着版本的变化而变化,深度嵌套的对象“扩展”收录大量重复数据。 .

  我写了一段Python代码将每个网站快照转换成更简单的结构,然后将这段代码传递给脚本的--convert选项:

  #!/bin/bash

  git-history 文件 sf-bay-511.db 511-events-history/events.json \

  --repo 511-events-history \

  --id id \

  --转换'

  data = json.loads(content)

  if data.get("error"):

  # {"code": 500, "error": "访问远程数据时出错..."}

  返回

  对于数据中的事件["Events"]:

  event["id"] = event["extension"]["event-reference"]["event-identifier"]

  #去除嘈杂的更新时间戳

  删除事件[“更新”]

  # 完全删除扩展块

  删除事件["扩展名"]

  # "schedule" 块很吵但不有趣

  del event["schedule"]

  # 展平嵌套的子类型

  event["event_subtypes"] = event["event_subtypes"]["event_subtype"]

  如果不是 isinstance(event["event_subtypes"], list):

  event["event_subtypes"] = [event["event_subtypes"]]

  产量事件

  '

  传递给 --convert 的单引号字符串被编译成 Python 函数,并在每个 Git 版本上依次运行。代码在 Events 嵌套列表中循环运行,修改每条记录,然后使用 yield 以可迭代序列输出。

  一些历史记录显示服务器 500 错误,代码也可以识别和跳过这些记录。

  在使用 git-history 时,我发现我大部分时间都花在了迭代转换脚本上。将 Python 代码字符串传递给 git-history 等工具是一个非常有趣的模型。今年早些时候,我也尝试在 sqlite-utils 工具中叠加转换。

  自己试试

  如果您想尝试 git-history 工具,扩展文档 README 中提供了更多选项。示例中使用的脚本都保存在 demos 文件夹中。

  GitHub 上的 git-scraping 话题下,已经有很多人创建了仓库,目前有 200 多个,还有丰富的爬取数据等你探索!

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线