前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >3700字!爬虫数据清洗已经不重要了,我这样的爬虫架构,如履薄冰

3700字!爬虫数据清洗已经不重要了,我这样的爬虫架构,如履薄冰

原创
作者头像
叫我阿柒啊
发布2023-12-19 10:43:56
4111
发布2023-12-19 10:43:56
举报

前言

在毕业设计中,用Java写下了第一个爬虫。2019年工作之后,从Python的requests原生爬虫库,学到分布式爬虫框架Scrapy,写了60个左右爬虫。然后写了十几篇有关于爬虫的文章。但大多都是围绕着程序设计、功能模块的角度写的,今天就从数据的角度出发,来看看爬虫程序是如何开发的。

数据

爬虫的目的就是获取数据,我总结了一下采集数据的步骤:

  1. 明确自己想要什么数据,这些数据有什么内容
  2. 去找有这些数据的网站
  3. 分析带有目标数据的网页,分析渲染数据的请求方式,是静态网页还是XHR异步
  4. 分析数据网页的层级。即从网站首页开始,如何层层递进到目标数据网页
  5. 程序开发(反爬验证、数据采集、数据清洗、数据入库)

所以说大多时候,爬虫程序的开发是以数据为驱动的。在开发程序前明确目标数据,在程序开发过程中清洗数据。

数据清洗其实是对每个数据中的字段进行处理。我在开发爬虫的过程中,常用的数据清洗方法有:字段缺失处理、数据转换、数据去重、异常值处理。 下面就使用Python的requests来开发实际操作一下,在数据去重等部分时,我也会使用Scrapy来实现,来展现一下Scrapy的优势所在。

关于Scrapy

大家可能对requets用的比较多,所以这里也简单得介绍一下Scrapy。

Scrapy是一个分布式爬虫框架,我把它比作成爬虫界的Spring。reqeusts就像是servlet一样,各种功能逻辑都需要自己去实现,而Spring做了集成,底层对用户透明。

就像我们知道,Spring是在application配置文件中初始化bean,在mapper中定义数据库操作一样,而使用者无需关心Spring是如何读取这些配置文件进行各种操作的。同样,Scrapy也提供了这样的功能配置。

所以说,Scrapy是一个爬虫框架,requests是一个爬虫模块,这就是两者区别的根本所在。下面是我画的Scrapy的架构图。

爬虫开发问题

无论使用Java的Jsoup也好,python的requests也罢,开发爬虫除了要解决网站反爬限制之外,还会面临下面几个问题:

1.分布式

爬虫程序一般只运行在一台主机上,如果是一模一样的爬虫程序部署在不同的主机上,那都是独立爬虫程序。如果想要弄一个分布式的爬虫,通常的思路是将爬虫程序分为url采集和数据采集两个部分。

现将url爬取下来放入到数据库中,然后通过where条件限制,或者直接使用redis的list结构,让不同主机上的爬虫程序读取到不同的url,然后进行数据爬取。

2.url去重

爬取数据的时候会经常遇到重复的url,如果重复爬取是不是浪费时间。通过url去重的思路就是:将爬取的url放入到集合中,每次爬取都去判断url是否存在于集合中。那么,如果程序中途停止了,这个内存中集合也将不复存在,再次启动程序,将无法判断哪些是已经爬取过的。

那么就用数据库,将已经爬取过的url插入到数据库中,这样就算重启程序,爬取过的url也不会丢失了。可是如果我就是想重新开始爬取,是不是还得手动清空数据库中的url表。每次查询数据库耗费的时间,这都是需要考虑的。

3.断点续爬

假如有1000个页面需要爬取,爬到第999个页面,进度条马上满格的时候,程序咯噔一下挂了,就差一个,但是还是没爬完啊,咋整?我选择重新启动程序,那么你说我怎么样才能直接从第999个开始爬取呢?

这里先讲讲我写的第一个爬虫:爬取10+个地市的poi信息。

实习,第一次开发爬虫,也不知道有高德poi接口啥的,于是就找了个网站来爬取poi信息。当时那个网站估计还在起步阶段,服务器带宽应该不高,访问速度是真的慢,而且动不动维护停站,所以我的程序也得跟着停止。如果每次启动都重新爬取,估计几年也爬不完,于是我想了个办法。

我先将所有地市下所有区县数据的条数(网站上有)先手动录入到数据库表中,每次重新启动爬虫程序的时候,先统计结果数据表中各个区县已经爬取的条数,与总条数进行对比。如果小于的话,说明还没有爬取完,然后通过某区县已爬取条数 / 网站每页展示条数计算出我已经爬取到此区县的页数,再通过余数定位到我爬到了此页面的第几个。通过这种方法,最后无丢失爬取了163w条数据。

换种思路,将爬取的url放到表中,重启程序开始爬取url的时候,先去判断url是否存在于数据表中,如果存在就不进行爬取,这样也能实现断点续爬。也是沿用了原始的url的去重的思路。

4.动态加载

爬虫教程第六篇基金篇写了一个jsonp的动态加载,算是比较简单的一种,只要找到请求接口获取数据进行处理即可。爬虫教程第七篇写了电视猫的eval()的js加密,这算是很复杂的一种动态加载。请求接口的参数是加密的,需要耗费大量时间来分析密密麻麻的js,来计算出这个186位的参数。

so,有没有一种方式让我既能脱离阅读分析js,还能绕过动态加载?

sure!!首先关于动态加载,可以理解为浏览器内核通过执行js在前端渲染数据。那么我们在程序中搞个浏览器内核,我们直接获取js渲染后的页面数据不就可以了么?

通常使用selenium + chrome、phantomjs、pyvirtualdisplay来处理动态加载,但是或多或少都会有性能问题。

如果手动实现这些功能,费时费力。所以,如果我说关于上述问题,Scrapy都提供了现成的解决方案(开箱即用的插件),那么你会心动吗?

插件的介绍我就不多说了,在我的Scrapy爬虫文章里都有,如果有兴趣可以自行学习。言归正传,继续探讨数据清洗的问题。

采集数据

数据采集其实也属于数据清洗,同时也是数据清洗的前提。因为要将从获取的html或者json使用selector转换成csv格式的数据。所以在从网页获取数据时,需要先判断数据是静态网页渲染还是XHR异步请求。

1. 静态和XHR

静态网页渲染,就是用户访问网站发起请求时,是网站后台将数据渲染(填写)到html上,返回给浏览器展示,这里的数据渲染是后台来做。爬虫使用css或者Xpath选择器处理数据,对应Python的BS4或者Slector模块。

而XHR异步请求,是网站先将空的html返回给浏览器,然后浏览器再发起XHR(Ajax)来请求数据(大部分是Json),最后浏览器将数据渲染到空html上进行展示,所以这里的数据渲染是浏览器(前端)去做。所以Python使用json模块来处理数据。

2. 区分方法

这里就拿腾讯视频来简单介绍一下:

我们在F12进入开发者控制台时,可以看到动漫列表和热搜榜的数据。

当我们点击热搜搜索框是时,热搜榜的div就会修改,这就是局部刷新的XHR异步加载。

我们在控制台看一下Network中的XHR信息。

所以,判断是静态网页渲染还是XHR,有很多种方法。

  1. 可以根据自己的经验。例如热搜榜肯定是实时更新的,所以每次点击搜索框都是最新的,所以需要异步XHR
  2. 也可以在观察控制台的变化。当我点击搜索框时,代表热搜榜的div就会刷新,这就是XHR的表现
  3. 查看网页源码。网页源码表示后台返回的html原始网页。源码里面的数据就是静态网页渲染,源码里没有而网站页面上有的数据就是XHR
  1. 程序开发过程中去发现。

数据清洗

数据清洗可以发生在数据采集阶段,也可以发生在数据存储阶段,数据大都存储在数据库中,然后使用SQL进行数据清洗。但是我更偏向于前者,在源头制定好数据规范,这也是数据治理的一部分。所以,能在程序中处理,就不要把数据问题丢给使用者,

1. 数据去重

用SQL处理重复数据,使用distinct() 方法,传入的字段来确定数据的唯一性,例如一个视频的id。这个唯一字段需要自己在开发过程中去确定。

在原生爬虫requets中,我给出两种数据去重的方案:

  1. 依靠程序内部设计,使用set/list/map集合来判断数据是否唯一
  2. 依靠外部数据库,每次爬取都去数据库查询数据是否已存在

方案一优点是不需要与外部系统频繁交互,缺点就是程序重启集合数据就会清空,而且多线程情况下还要考虑线程安全。方案二稳定,但是需要依赖数据库,数据库的响应速度会影响程序的性能。在上面讲的poi爬虫就使用了方案二。

以上两种情况,都需要自己实现代码,各有利弊。而Scrapy使用的是scrapy-deltafetch插件实现的,里面使用了内嵌数据库BerkerlyDB,即不需要与外部系统交互,重启也不会丢失数据,只需要安装之后添加几行配置就能使用。感兴趣的话:可以跳转到scrapy-deltafetch文章连接进行学习:https://cloud.tencent.com/developer/article/2194956

这里先启动程序,爬取一个指定的url。

当爬取完上面url之后,第二次启动再遇到这个url时,就会看到Ignoring关键字,提示忽略已经爬取的url,不再进行爬取。

这里的用url作为数据去重的标准,如果想要重新爬取之前爬取过的url,启动前添加deltafetch_reset=1参数即可。

同时,这个插件也解决了爬虫问题中的断点续爬的问题。

2. 字段缺失处理

在爬取某些网页时,爬取的都是字段的并集。所以某些字段在某个网页并不存在,当使用选择器获取这些字段时,就会出现空指针或者数据越界的异常。

数据字段缺失还是比较好处理的。通常是使用if进行判断,然后设置缺省值。下面是scrapy处理字段缺失的代码:

3. 数据转换

时间戳转换成日期、日期格式转换、字符串替换都算是数据转换。下面是Python实现日期格式转化的代码:

代码很简单,主要用到了datetime模块。

在上面的代码中,使用replace()进行了字符串的替换,其中包括将空格、换行等字符替换成空字符。

4. 异常值处理

异常值在爬虫开发中还是比较少见的,常见的有网页编码问题导致数据的乱码,还有一些数据填充的错误。这两个问题我记得遇到过,找了好久代码没有找到,这里就简单的说一下思路。

乱码就通过chardet.detect(str) 来检测一下字符串的编码格式,或者直接去看一下网页信息。使用charset关键字进行搜索:

通过meta可以看到字符集是UTF-8。至于数据填充错误,只能具体情况具体分析,通过错误数据来反推,然后在程序中进行改进。

思考

看到这里可能会有人问:前面写的分布式、断点续爬、url去重以及动态加载和数据清洗有什么关系呢?

站在我个人的角度,我觉得是紧密联系的。我在上面谈及数据去重的时候,说了有两种方案:集合和数据库。如果在多台机器上使用分布式,集合去重这一个方案绝对是被pass了,因为你没法在多个进程共用一个集合对象。至于数据库,就要考虑如何设计才能保证数据的一致性了。

至于断点续爬、url去重就是数据去重的一个思路介绍。动态加载就是对数据采集中XHR的一个介绍。所以,爬虫也有很多东西可以学,学会requets ≠ 精通爬虫。

结语

上面就是个人在爬虫开发过程中,对常用的数据清洗方式的一个总结,都是一些简单的程序处理逻辑,希望对大家了解爬虫、开发爬虫有所帮助。

同时,我个人也写了关于requests爬虫入门的7篇文章,以及Scrapy爬虫的10篇文章,有兴趣的可以去参考一下。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 数据
  • 关于Scrapy
  • 爬虫开发问题
    • 1.分布式
      • 2.url去重
        • 3.断点续爬
          • 4.动态加载
          • 采集数据
            • 1. 静态和XHR
              • 2. 区分方法
              • 数据清洗
                • 1. 数据去重
                  • 2. 字段缺失处理
                    • 3. 数据转换
                      • 4. 异常值处理
                      • 思考
                      • 结语
                      相关产品与服务
                      数据保险箱
                      数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档