V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
NightTeam
V2EX  ›  分享创造

开源一个网页智能解析算法库

  •  6
     
  •   NightTeam · 2020-07-07 16:29:38 +08:00 · 4513 次点击
    这是一个创建于 1602 天前的主题,其中的信息可能已经有所发展或是发生改变。

    文章作者:「 夜幕团队 NightTeam 」 - 崔庆才

    本文首发于「 崔庆才|静觅 」微信公众号,如需转载请在微信端发消息告知。

    文中提取效果部分由于触发了 V2EX 的敏感词过滤,已全部进行替换,提取效果请前往项目 GitHub 仓库查看。


    之前我写过几篇文章介绍过有关爬虫的智能解析算法,包括商业化应用 Diffbot 、Readability 、Newspaper 这些库,另外我有一位朋友之前还专门针对新闻正文的提取算法 GeneralNewsExtractor,这段时间我也参考和研究了一下这些库的算法,同时参考一些论文,也写了一个智能解析库,在这里就做一个非正式的介绍。

    引入

    那首先说说我想做的是什么。

    比如这里有一个网站,网易新闻,https://news.163.com/rank/,这里有个新闻列表,预览图如下:

    image-20200705212920277

    任意点开一篇新闻,看到的结果如下:

    image-20200705213058853

    我现在需要做到的是在不编写任何 XPath 、Selector 的情况下实现下面信息的提取:

    对于列表页来说,我要提取新闻的所有标题列表和对应的链接,它们就是图中的红色区域:

    image-20200705213401471

    这里红色区域分了多个区块,比如这里一共就是 40 个链接,我都需要提取出来,包括标题的名称,标题的 URL 。

    我们看到页面里面还有很多无用的链接,如上图绿色区域,包括分类、内部导航等,这些需要排除掉。

    对于详情页,我主要关心的内容有标题、发布时间、正文内容,它们就是图中红色区域:

    image-20200705213723198

    其中这里也带有一些干扰项,比如绿色区域的侧边栏的内容,无用的分享链接等。

    总之,我想实现某种算法,实现如上两大部分的智能化提取。

    框架

    之前我开发了一个叫做 Gerapy https://github.com/Gerapy/Gerapy 的框架,是一个基于 Scrapy 、Scrapyd 的分布式爬虫管理框架,属 1.x 版本。现在正在开发 Gerapy 2.x 版本,其定位转向了 Scrapy 的可视化配置和调试、智能化解析方向,放弃支持 Scraypd,转而支持 Docker 、Kubernetes 的部署和监控。

    对于智能解析来说,就像刚才说的,我期望的就是上述的功能,在不编写任何 XPath 和 Selector 的情况下实现页面关键内容的提取。

    框架现在发布了第一个初步版本,名称叫做 Gerapy Auto Extractor,名字 Gerapy 相关,也会作为 Gerapy 的其中一个模块。

    GitHub 链接: https://github.com/Gerapy/GerapyAutoExtractor

    现在已经发布了 PyPi,https://pypi.org/project/gerapy-auto-extractor/,可以使用 pip3 来安装,安装方式如下:

    pip3 install gerapy-auto-extractor
    

    安装完了之后我们就可以导入使用了。

    功能

    下面简单介绍下它的功能,它能够做到列表页和详情页的解析。

    列表页:

    • 标题内容
    • 标题链接

    详情页:

    • 标题
    • 正文
    • 发布时间

    先暂时实现了如上内容的提取,其他字段的提取暂时还未实现。

    使用

    要使用 Gerapy Auto Extractor,前提我们必须要先获得 HTML 代码,注意这个 HTML 代码是我们在浏览器里面看到的内容,是整个页面渲染完成之后的代码。在某些情况下如果我们简单用「查看源代码」或 requests 请求获取到的源码并不是真正渲染完成后的 HTML 代码。

    要获取完整 HTML 代码可以在浏览器开发者工具,打开 Elements 选项卡,然后复制你所看到的 HTML 内容即可。

    先测试下列表页,比如我把 https://news.163.com/rank/ 这个保存为 list.html,

    image-20200705220428754

    然后编写提取代码如下:

    import json
    from gerapy_auto_extractor.extractors.list import extract_list
    
    html = open('list.html', encoding='utf-8').read()
    print(json.dumps(extract_list(html), indent=2, ensure_ascii=False, default=str))
    

    就是这么简单,核心代码就一行,就是调用了一个 extract_list 方法。

    运行结果如下:

    [
      {
        "title": "内容中疑似包含敏感话题,请修改或者放弃",
        "url": "https://news.163.com/20/0705/05/FGOFE1HJ0001875P.html"
      },
      {
        "title": "内容中疑似包含敏感话题,请修改或者放弃",
        "url": "https://news.163.com/20/0705/02/FGO66FU90001899O.html"
      },
      {
        "title": "内容中疑似包含敏感话题,请修改或者放弃",
        "url": "https://news.163.com/20/0705/08/FGOPG3AM0001899O.html"
      },
      {
        "title": "内容中疑似包含敏感话题,请修改或者放弃",
        "url": "https://news.163.com/20/0705/01/FGO42EK90001875O.html"
      },
      {
        "title": "内容中疑似包含敏感话题,请修改或者放弃",
        "url": "https://home.163.com/20/0705/07/FGOLER1200108GL2.html"
      },
      {
        "title": "内容中疑似包含敏感话题,请修改或者放弃",
        "url": "https://sports.163.com/20/0704/12/FGML920300058782.html"
      },
      {
        "title": "内容中疑似包含敏感话题,请修改或者放弃",
        "url": "https://home.163.com/20/0705/07/FGOLEL1100108GL2.html"
      },
      {
        "title": "内容中疑似包含敏感话题,请修改或者放弃",
        "url": "https://money.163.com/20/0705/07/FGON5T7B00259DLP.html"
      },
      ...
    ]
    

    可以看到想要的内容就提取出来了,结果是一个列表,包含标题内容和标题链接两个字段,由于内容过长,这里就省略了一部分。

    接着我们再测试下正文的提取,随便打开一篇文章,比如 https://news.ifeng.com/c/7xrdz0kysde,保存下 HTML,命名为 detail.html 。

    image-20200705222433951

    编写测试代码如下:

    import json
    from gerapy_auto_extractor.extractors import extract_detail
    html = open('detail.html', encoding='utf-8').read()
    print(json.dumps(extract_detail(html), indent=2, ensure_ascii=False, default=str))
    

    运行结果如下:

    {
      "title": "内容中疑似包含敏感话题,请修改或者放弃",
      "datetime": "2020-07-05 18:54:15",
      "content": "balabalabala\nbalabalabala"
    }
    

    成功输出了标题、正文、发布时间等内容。

    这里就演示了基本的列表页、详情页的提取操作。

    算法

    整个算法的实现比较杂,我看了几篇论文和几个项目的源码,然后经过一些修改实现的。

    其中列表页解析的参考论文:

    详情页解析的参考论文和项目:

    这些都是不完全参考,然后加上自己的一些修改最终才形成了现在的结果。

    算法在这里就几句话描述一下思路,暂时先不展开讲了。

    列表页解析:

    • 找到具有公共父节点的连续相邻子节点,父节点作为候选节点。
    • 根据节点特征进行聚类融合,将符合条件的父节点融合在一起。
    • 根据节点的特征、文本密度、视觉信息(尚未实现)挑选最优父节点。
    • 从最优父节点内根据标题特征提取标题。

    详情页解析:

    • 标题根据 meta 、title 、h 节点综合提取
    • 时间根据 meta 、正则信息综合提取
    • 正文根据文本密度、符号密度、视觉信息(尚未实现)综合提取。

    后面等完善了之后再详细介绍算法的具体实现,现在如感兴趣可以去看源码。

    说明

    本框架仅仅发布了最初测试版本,测试覆盖度比较少,目前仅仅测试了有限的几个网站,尚未大规模测试和添加对比实验,因此准确率现在还没有标准的保证。

    参考:关于详情页正文的提取我主要参考了 GeneralNewsExtractor 这个项目,原项目据测试可以达到 90% 以上的准确率。

    列表页我测试了腾讯、网易、知乎等都是可以顺利提取的,如:

    19841593922229_.pic_hd

    image-20200705224404571

    image-20200705224419759

    后面会有大规模测试和修正。

    项目初版,肯定存在很多不足,希望大家可以多发 Issue 和提 PR 。

    另外这里建立了一个 Gerapy 开发交流群,之前在 QQ 群的也欢迎加入,以后交流就在微信群了,大家在使用过程遇到关于 Gerapy 、Gerapy Auto Extractor 的问题欢迎交流。

    这里放一个临时二维码,后期可能会失效,失效后大家可以到公众号「进击的 Coder 」获取加群方式。

    image-20200705225922008

    待开发功能

    • 视觉信息的融合
    • 文本相似度的融合
    • 分类模型的融合
    • 下一页翻页的信息提取
    • 正文图片、视频的提取
    • 对接 Gerapy

    最后感谢大家的支持!

    6 条回复    2021-09-04 17:53:40 +08:00
    NightTeam
        1
    NightTeam  
    OP
       2020-07-07 16:44:08 +08:00
    Gerapy 开发交流群满 200 人了,要进群的话直接加微信 CQCcqc 吧。
    Austin2035
        2
    Austin2035  
       2020-07-07 20:21:15 +08:00
    有点通用爬虫内味了,表现如何先不说,性能也是不可忽略的一环。
    jones2000
        3
    jones2000  
       2020-07-08 01:21:45 +08:00
    1. 抓取的最麻烦的就是抓的页面格式变了, 原来配的页面的东西就需要重新配置过, 才能定位到需要的内容。
    是否可以考虑 使用 AI 训练来过滤内容。比如我希望抓取财经类新闻, 通过大量财经类新闻训练让 AI 识别这类内容,后续通过 AI 就可以准确过滤。
    2. 另外一个是版权问题, 是否可以考虑把抓取的内容提取中心内容,这样就可以和原始的内容不一样,规避版权问题。

    不知道楼主有什么这方面的建议吗?
    cqcreer
        4
    cqcreer  
       2020-07-09 21:34:15 +08:00
    @jones2000 1. 这个是可行的,可以通过 NLP 、视觉和 DOM 综合计算得出,这个库是无监督的,更轻量级一些。
    2. 这个其实不是抓取的问题了,涉及到语义改写,可以看下 NLP 相关 Task 。
    cqcreer
        5
    cqcreer  
       2020-07-09 21:35:13 +08:00
    @lookcos 性能没有具体测试,基本上 1-2s 内就出结果了,相比爬虫本身延迟还是可以接受的。
    gancl
        6
    gancl  
       2021-09-04 17:53:40 +08:00
    path = "logs/生成过内容的记录表单.csv" # 文件保存路径
    WebSite = "sohu.com" # 网站主页链接
    Url = "https://www.sohu.com/a/419892530_603537" # 具体内容链接
    Title = extract_detail(requests.get(Url).text)["title"] # 文章的标题
    报错了 AttributeError: 'backports.zoneinfo.ZoneInfo' object has no attribute 'localize'
    @cqcreer
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3048 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 14:15 · PVG 22:15 · LAX 06:15 · JFK 09:15
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.