python 用递归实现通用爬虫解析器


Posted in Python onApril 16, 2021

我们在写爬虫的过程中,除了研究反爬之外,几乎全部的时间都在写解析逻辑。那么,生命苦短,为什么我们不写一个通用解析器呢?对啊!为什么不呢?开整!

需求分析

爬虫要解析的网页类型无外乎 html、json 以及一些二进制文件(video、excel 文件等)。既然要做成通用解析器,我们有两种实现方式,一种是将网页内容转换成统一的形式,然后用对应的解析规则去解析,比如全部将网页内容转换成 html 形式,然后用 xpath 去提取。

python 用递归实现通用爬虫解析器

另外一种是配置文件预先告知的方式,你配置成什么类型,解析器就通过对应的解析规则去解析。

统一网页形式,需要做大量的网页内容形式转换,而配置文件预先告知则需要在配置时指定更多解析字段。相比较而言,通过第二种方式,未来改变较多的是配置规则,不需要动核心代码,引入 bug 的可能性较低。因此这里我们采用第二种方式实现解析器

进一步分析

解析器对于网页内容的提取,本质上和我们在本地电脑上查找和整理文件,没有什么差别。比如像下面这样

python 用递归实现通用爬虫解析器

解析内容就是从中提取我们想要的信息,然后整理成我们希望的格式。比如上面的内容,我们提取出来的形式应该是这样

{
  "design": "设计图.psd",
  "software": "sketch.dmg"
}

而在实际的爬虫开发过程中,网页形式远比以上的复杂。其实遇到最多的问题是在一组列表中嵌套一个列表,我们需要把这种形式提取出来。比如像下面这种形式

{
    "a": "a",
    "b": [
        {"c": "c1", "d": "d1"},
        {"c": "c2", "d": "d2"}]
}

他提取出信息后应该是这样

[
  {
    "a": "a",
    "c": "c1",
    "d": "d1"
  },
  {
    "a": "a",
    "c": "c2",
    "d": "d2"
  }
]

如果小伙伴对于算法熟悉的话,应该能察觉出这种遍历用递归来写是非常方便的。但要注意的是 python 会限定递归的层数,小伙伴可以通过下面这个方法查看递归限定的层数

import sys
print(sys.getrecursionlimit())

>>>1000

我这边限定的层数是 1k。对于解析网页来说完全够用了,如果哪个人把网页解析逻辑嵌套了 1000 层,我建议你直接跟老板提放弃这个网页吧!

再进一步分析

我们已经知道对于通用解析来说,就是通过配置解析规则提取页面的对应信息。而针对有列表层级的网页可能还涉及递归遍历问题。那如何去配置这种解析规则呢?其实很简单,只需要在进入每一个层级之前先指定该层的数据形式,比如下面这个原数据

{
  "a": "a",
  "b": [
          {"c": "c1", "d": "d1"},
          {"c": "c2", "d" : "d2"}
       ]
}

想提取嵌套信息,我们的解析规则就应该是这样的

[
 {
  "$name": "a",
  "$value_type": "raw",
  "$parse_method": "json",
  "$parse_rule": "a",
  "$each": []
 },
 {
  "$name": "__datas__",
  "$value_type": "recursion",
  "$parse_method": "json",
  "$parse_rule": "b",
  "$each": [
        {  
         "$name": "c",
          "$value_type": "raw",
         "$parse_method": "json",
         "$parse_rule": "c",
         "$each": []
        },
        {  
         "$name": "d",
          "$value_type": "raw",
         "$parse_method": "json",
         "$parse_rule": "d",
         "$each": []
        }
      ]
 }
]

其中 $name 字段表示我们最终希望最外层数据所拥有的字段名,当然如果是需要递归到内层的字段,则将列表保存为 __datas__ ,然后根据这个 __datas__ 进行内层结构的解析。最终我们得到的数据结构应该是这样的

[
  {"a": "a", "c": "c1", "d": "d1"}, 
  {"a": "a", "c": "c2", "d": "d2"}
]

以上我们只演示了 json 的解析规则,如果要拿来解析 html 对象呢?很简单,将解析方式改为 xpath 对象,然后传入 xpath 解析语法即可。

代码实现

总共分成两部分,一部分根据原最终结果和规则进行打包,将所有涉及 recursion 逻辑的字段进行转换,代码如下

def _pack_json(result, rules):
        item = {}

        for p_rule in rules:

            if p_rule.get("$value_type") == "raw":
                if p_rule.get("$parse_method") == "json":
                    item[p_rule.get("$name")] = glom(result, p_rule.get("$parse_rule"))

            elif p_rule.get("$value_type") == "recursion":
                if p_rule.get("$parse_method") == "json":
                    tmp_result = glom(result, p_rule.get("$parse_rule"))
                    total_result = []
                    for per_r in tmp_result:
                        total_result.append(_pack_json(per_r, p_rule.get("$each")))
                    item[p_rule.get("$name")] = total_result
        return item

另外一部分将上一步得到的进行解析,将打包得到的结果进行解包,即将所有内嵌的数据提到最外层,代码如下

def _unpack_datas(result: dict) -> list:
        if "__datas__" not in result:
            return [result]

        item_results = []
        all_item = result.pop("__datas__")

        for per_item in all_item:
            if "__datas__" in per_item:
                tmp_datas = per_item.pop("__datas__")
                for per_tmp_data in tmp_datas:
                    tmp_item = _unpack_datas(per_tmp_data)
                    for per_tmp_item in tmp_item:
                        item_results.append({**per_tmp_item, **per_item})
            else:
                item_results.append({**result, **per_item})

        return item_results

后再包一层执行入口就可以了,完整代码如下

from loguru import logger

from glom import glom


def parse(result, rules):

    def _pack_json(result, rules):
        item = {}

        for p_rule in rules:

            if p_rule.get("$value_type") == "raw":
                if p_rule.get("$parse_method") == "json":
                    item[p_rule.get("$name")] = glom(result, p_rule.get("$parse_rule"))

            elif p_rule.get("$value_type") == "recursion":
                if p_rule.get("$parse_method") == "json":
                    tmp_result = glom(result, p_rule.get("$parse_rule"))
                    total_result = []
                    for per_r in tmp_result:
                        total_result.append(_pack_json(per_r, p_rule.get("$each")))
                    item[p_rule.get("$name")] = total_result
        return item

    def _unpack_datas(result: dict) -> list:
        if "__datas__" not in result:
            return [result]

        item_results = []
        all_item = result.pop("__datas__")

        for per_item in all_item:
            if "__datas__" in per_item:
                tmp_datas = per_item.pop("__datas__")
                for per_tmp_data in tmp_datas:
                    tmp_item = _unpack_datas(per_tmp_data)
                    for per_tmp_item in tmp_item:
                        item_results.append({**per_tmp_item, **per_item})
            else:
                item_results.append({**result, **per_item})

        return item_results

    pack_result = _pack_json(result, rules)
    logger.info(pack_result)
    return _unpack_datas(pack_result)

以上,就是通用解析器的完整案例。案例中仅实现了对于 json 的支持,小伙伴可以基于自己的项目,改造成其他的解析形式。通用解析其实是鸡仔为了偷懒写的,因为鸡仔发现,在爬虫开发中,大部分工作都耗在解析这部分。而有了通用解析的前端页面,运营和数据分析师就可以根据自己的需要配置自己想爬取的站点了。人生苦短,你懂得。我去摸鱼了~

实现方式请移步至 github 查看:https://github.com/hacksman/learn_lab/blob/master/small_bug_lab/general_parser.py

以上就是python 用递归实现通用爬虫解析器的详细内容,更多关于python 递归实现爬虫解析器的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
使用Django的模版来配合字符串翻译工作
Jul 27 Python
Python格式化输出%s和%d
May 07 Python
python3 对list中每个元素进行处理的方法
Jun 29 Python
Python3.7中安装openCV库的方法
Jul 11 Python
Python3编码问题 Unicode utf-8 bytes互转方法
Oct 26 Python
python实现整数的二进制循环移位
Mar 08 Python
django框架自定义模板标签(template tag)操作示例
Jun 24 Python
Python实现微信小程序支付功能
Jul 25 Python
Python 利用邮件系统完成远程控制电脑的实现(关机、重启等)
Nov 19 Python
python循环输出三角形图案的例子
Nov 22 Python
Python列表推导式实现代码实例
Sep 09 Python
python实现网络五子棋
Apr 11 Python
MATLAB 如何求取离散点的曲率最大值
用Python远程登陆服务器的步骤
Matlab求解数组中的最大值及它所在的具体位置
Apr 16 #Python
python 机器学习的标准化、归一化、正则化、离散化和白化
Apr 16 #Python
python中print格式化输出的问题
Apr 16 #Python
CocosCreator ScrollView优化系列之分帧加载
深度学习tensorflow基础mnist
You might like
php循环检测目录是否存在并创建(循环创建目录)
2011/01/06 PHP
分享十五个最佳jQuery 幻灯插件和教程
2010/03/27 Javascript
网页中CDATA标记的说明
2010/09/12 Javascript
javascript实现可全选、反选及删除表格的方法
2015/05/15 Javascript
浅谈jQuery的offset()方法及示例分享
2015/07/17 Javascript
JS实现支持多选的遍历下拉列表代码
2015/08/20 Javascript
js实现对table的增加行和删除行的操作方法
2016/10/13 Javascript
javascript中json基础知识详解
2017/01/19 Javascript
Angularjs 实现移动端在线测评效果(推荐)
2017/04/05 Javascript
js脚本编写简单刷票投票系统
2017/06/27 Javascript
vue-cli项目如何使用vue-resource获取本地的json数据(模拟服务端返回数据)
2017/08/04 Javascript
vue父子组件的嵌套的示例代码
2017/09/08 Javascript
JavaScript设计模式之建造者模式实例教程
2018/07/02 Javascript
详解Node.js中path模块的resolve()和join()方法的区别
2018/10/29 Javascript
vue-cli和v-charts实现可视化图表过程解析
2019/10/08 Javascript
jQuery插件simplePagination的使用方法示例
2020/04/28 jQuery
javascript的hashCode函数实现代码小结
2020/08/11 Javascript
python使用xlrd实现检索excel中某列含有指定字符串记录的方法
2015/05/09 Python
Python selenium 三种等待方式详解(必会)
2016/09/15 Python
Python实现树的先序、中序、后序排序算法示例
2017/06/23 Python
对tensorflow 的模型保存和调用实例讲解
2018/07/28 Python
浅谈Python编程中3个常用的数据结构和算法
2019/04/30 Python
django框架用户权限中的session缓存到redis中的方法
2019/08/06 Python
Python配置pip国内镜像源的实现
2020/08/20 Python
python用分数表示矩阵的方法实例
2021/01/11 Python
巧用 CSS3的webkit-box-reflect 倒影实现各类动效
2021/03/05 HTML / CSS
HTML5 Canvas鼠标与键盘事件demo示例
2013/07/04 HTML / CSS
详解使用双缓存解决Canvas clearRect引起的闪屏问题
2019/04/29 HTML / CSS
党员培训思想汇报
2014/01/07 职场文书
微型企业创业投资计划书
2014/01/10 职场文书
《白鹅》教学反思
2014/04/13 职场文书
小学校长个人总结
2015/03/03 职场文书
预备党员入党感想
2015/08/10 职场文书
Python趣味挑战之教你用pygame画进度条
2021/05/31 Python
TV动画《史上最强大魔王转生为村民A》番宣CM公布
2022/04/01 日漫
用PYTHON去计算88键钢琴的琴键频率和音高
2022/04/10 Python