网页数据抓取怎么写(Gitscraping技术回顾抓取数据到Git存储库的一大优势)
优采云 发布时间: 2021-12-26 10:02网页数据抓取怎么写(Gitscraping技术回顾抓取数据到Git存储库的一大优势)
大多数人都知道 Git 抓取,这是一种用于网页抓取工具的编程技术。您可以定期将数据源的快照抓取到 Git 存储库,以跟踪数据源随时间的变化。
如何分析采集
到的数据是一个公认的问题。git-history 是我设计用来解决这个问题的工具。
Git 抓取技术回顾
将数据抓取到 Git 存储库的一大优势在于抓取工具本身非常简单。
下面是一个具体的例子:加州林业和消防局 (Cal Fire) 在 /incidents 网站上维护了一张火灾地图,该地图显示了最近*敏*感*词*火灾的状态。
我找到了网站的底层数据:
卷曲
然后我搭建了一个简单的爬虫工具,每20分钟爬一次一个网站的数据,提交给Git。到目前为止,该工具已经运行了14个月,已经采集
了1559个提交版本。
Git 抓取最让我兴奋的是它可以创建真正独特的数据集。许多组织没有详细归档数据更改的内容和位置,因此通过抓取他们的网站数据并将其保存到 Git 存储库,您会发现您比他们更了解他们的数据更改历史。
然而,一个巨大的挑战是如何最有效地分析采集
到的数据?面对上千个版本和海量的JSON和CSV文档,如果仅靠肉眼观察差异,很难挖掘出数据背后的价值。
git-history
git-history 是我的新解决方案,它是一个命令行工具。它可以读取文件的所有历史版本并生成 SQLite 数据库来记录文件随时间的变化。然后你就可以使用Datasette来分析和挖掘这些数据。
下面是通过使用 ca-fires-history 存储库运行 git-history 生成的数据库示例。我通过在存储库目录中运行以下命令创建了一个 SQLite 数据库:
git-history file ca-fires.db incidents.json \
--namespace incident \
--id UniqueId \
--convert 'json.loads(content)["Incidents"]'
在这个例子中,我们获取了文件 events.json 的历史版本。
我们使用 UniqueId 列来标识随时间变化的记录和新记录。
新创建的数据库表的默认名称是item和item_version,我们通过--namespace event将表名指定为incident和incident_version。
工具中还嵌入了一段Python代码,可以将提交历史中存储的每个版本转换为与工具兼容的对象列表。
让数据库帮助我们回答一些关于过去 14 个月加州火灾的问题。
事件表收录
每次火灾的最新记录。从这张表中,我们可以得到一张所有火灾的地图:
这里使用了 datasette-cluster-map 插件,它在地图上标记了表中具有有效经纬度值的所有行。
真正有趣的是 event_version 表。该表记录了每次火灾的先前捕获版本之间的数据更新。
250 场火灾有 2,060 个记录版本。如果根据_item进行分面,我们可以看到哪些火灾记录的版本最多。前十名依次为:
版本越多,火持续的时间越长。维基百科上甚至还有 Dixie Fire 的条目!
点击Dixie Fire,在弹出的页面中可以看到所有抓到的“版本”按版本号排列。
git-history 只在此表中写入与之前版本相比发生变化的值。因此,一目了然,您可以看到哪些信息随时间发生了变化:
经常变化的是 ConditionStatement 列。此栏是文字说明。另外两个有趣的列是 AcresBurned 和 PercentContained。
_commit 是提交表的外键。该表记录了该工具已提交的版本,当您再次运行该工具时,该工具可以定位到上次提交的是哪个版本。
连接到提交表以查看每个版本的创建日期。您也可以使用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的设计过程中,最难的就是设计一个合适的表结构来存储之前的版本变更信息。
我的最终设计如下(为清晰起见进行了适当编辑):
CREATE TABLE [commits] (
[id] INTEGER PRIMARY KEY,
[hash] TEXT,
[commit_at] TEXT
);
CREATE TABLE [item] (
[_id] INTEGER PRIMARY KEY,
[_item_id] TEXT,
[IncidentID] TEXT,
[Location] TEXT,
[Type] TEXT,
[_commit] INTEGER
);
CREATE TABLE [item_version] (
[_id] INTEGER PRIMARY KEY,
[_item] INTEGER REFERENCES [item]([_id]),
[_version] INTEGER,
[_commit] INTEGER REFERENCES [commits]([id]),
[IncidentID] TEXT,
[Location] TEXT,
[Type] TEXT
);
CREATE TABLE [columns] (
[id] INTEGER PRIMARY KEY,
[namespace] INTEGER REFERENCES [namespaces]([id]),
[name] TEXT
);
CREATE TABLE [item_changed] (
[item_version] INTEGER REFERENCES [item_version]([_id]),
[column] INTEGER REFERENCES [columns]([id]),
PRIMARY KEY ([item_version], [column])
);
前面提到,item_version表记录了不同时间点的网站快照,但为了节省数据库空间,提供简洁的版本浏览界面,这里只记录了与之前版本相比发生变化的列。所有未更改的列都写为空。
但是这种设计有一个隐患,那就是如果某列的值在某次火灾中被更新为null,我们该怎么办?我们如何判断它是否已更新或未更改?
为了解决这个问题,我添加了一个多对多表item_changed,它使用整数对来记录item_version表中哪些列更新了内容。使用整数对的目的是尽可能少占用空间。
item_version_detail 视图将多对多表中的列显示为 JSON。我过滤了一些数据,放在下图中,看看哪些列更新了哪些版本:
通过下面的SQL查询,我们可以知道加州火灾中哪些数据更新最频繁:
select columns.name, count(*)
from incident_changed
join incident_version on incident_changed.item_version = incident_version._id
join columns on incident_changed.column = columns.id
where incident_version._version > 1
group by columns.name
order by count(*) desc
查询结果如下:
直升机听起来令人兴奋!让我们过滤掉第一个版本后直升机数量至少更新一次的火灾。您可以使用以下嵌套 SQL 查询:
select * from incident
where _id in (
select _item from incident_version
where _id in (
select item_version from incident_changed where column = 15
)
and _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 文件如下所示:
select * from incident
where _id in (
select _item from incident_version
where _id in (
select item_version from incident_changed where column = 15
)
and _version > 1
)
但是,捕获的数据通常不是这种理想格式。
我找到了网站的 JSON 提要。有非常复杂的嵌套对象,数据也很复杂,有些对整体分析没有帮助,比如更新的时间戳即使没有数据更新也会随着版本的变化而变化,深度嵌套的对象“扩展”收录
大量重复数据。.
我写了一段 Python 代码将每个网站快照转换成更简单的结构,然后将这段代码传递给脚本的 --convert 选项:
#!/bin/bash
git-history file sf-bay-511.db 511-events-history/events.json \
--repo 511-events-history \
--id id \
--convert '
data = json.loads(content)
if data.get("error"):
# {"code": 500, "error": "Error accessing remote data..."}
return
for event in data["Events"]:
event["id"] = event["extension"]["event-reference"]["event-identifier"]
# Remove noisy updated timestamp
del event["updated"]
# Drop extension block entirely
del event["extension"]
# "schedule" block is noisy but not interesting
del event["schedule"]
# Flatten nested subtypes
event["event_subtypes"] = event["event_subtypes"]["event_subtype"]
if not isinstance(event["event_subtypes"], list):
event["event_subtypes"] = [event["event_subtypes"]]
yield event
'
传递给 --convert 的单引号字符串被编译成 Python 函数,并在每个 Git 版本上依次运行。代码在 Events 嵌套列表中循环运行,修改每条记录,然后使用 yield 以可迭代序列输出。
一些历史记录显示服务器 500 错误,代码还可以识别和跳过这些记录。
在使用 git-history 时,我发现我大部分时间都花在了迭代转换脚本上。将 Python 代码字符串传递给 git-history 等工具是一个非常有趣的模型。今年早些时候,我还尝试在 sqlite-utils 工具中覆盖转换。
试一试
如果你想尝试 git-history 工具,扩展文档 README 中有更多选项。示例中使用的脚本都保存在 demos 文件夹中。
在 GitHub 上的 git-scraping 话题下,已经有很多人创建了仓库,目前有 200 多个仓库。海量爬取数据等你探索!