是否可以找到具有相同 dom 结构的节点

Is it possible to find the nodes with same dom structure

我用Scrapy从很多站点抓取了很多html(内容相似),但是dom结构不一样

例如,其中一个站点使用以下结构:

<div class="post">
    <section class='content'>
        Content1
    </section>

    <section class="panel">
    </section>
</div>
<div class="post">
    <section class='content'>
        Conent2
    </section>

    <section class="panel">
    </section>
</div>

我想提取数据 ContentContent2

虽然另一个站点可能使用这样的结构:

<article class="entry">
    <section class='title'>
        Content3
    </section>
</article>
<article class="entry">
    <section class='title'>
        Conent4
    </section>
</article>

我想提取数据 Content3Content4

虽然最简单的解决方案是为所有站点一个一个地标记所需的数据 xpath。那将是一项乏味的工作。

所以我想知道是否可以自动提取结构。其实我只要定位到重复的根节点(上例中的div.postarticle.entry),就可以按照一定的规则抽取数据了。

这可能吗?

顺便说一句,我不太确定这种算法的名称,所以这个 post 的标签可能是错误的,如果是的话请随意修改。

您必须至少了解一些常见模式才能制定确定性提取规则。下面的解决方案非常原始,绝不是最佳解决方案,但它可能对您有所帮助:

# -*- coding: utf-8 -*-
import re

import bs4
from bs4 import element
import scrapy


class ExampleSpider(scrapy.Spider):
    name = "example"
    start_urls = ['http://quotes.toscrape.com/']

    def parse(self, response):
        min_occurs = 5
        max_occurs = 1000
        min_depth = 7
        max_depth = 7
        pattern = re.compile('^/html/body/.*/(span|div)$')
        extract_content = lambda e: e.css('::text').extract_first()
        #extract_content = lambda e: ' '.join(e.css('*::text').extract())

        doc = bs4.BeautifulSoup(response.body, 'html.parser')

        paths = {}
        self._walk(doc, '', paths)
        paths = self._filter(paths, pattern, min_depth, max_depth,
                             min_occurs, max_occurs)

        for path in paths.keys():
            for e in response.xpath(path):
                yield {'content': extract_content(e)}

    def _walk(self, doc, parent, paths):
        for tag in doc.children:
            if isinstance(tag, element.Tag):
                path = parent + '/' + tag.name
                paths[path] = paths.get(path, 0) + 1
                self._walk(tag, path, paths)

    def _filter(self, paths, pattern, min_depth, max_depth, min_occurs, max_occurs):
        return dict((path, count) for path, count in paths.items()
                        if pattern.match(path) and
                                min_depth <= path.count('/') <= max_depth and
                                min_occurs <= count <= max_occurs)

它是这样工作的:

  1. 探索HTML文档并构建文档中所有元素路径及其出现的字典。
  2. 根据您从网页推断的一般规则过滤这些路径。
  3. 使用一些常见的提取逻辑从这些过滤后的路径中提取内容。

为了构建路径字典,我只是使用 BeautifulSoup 遍历文档并计算每个元素路径的出现次数。这可以在以后用于过滤任务以仅保留最重复的路径。

接下来我根据一些基本规则筛选出路径。要保留路径,它必须:

  • 至少出现 min_occurs 次,最多出现 max_occurs 次。
  • 长度至少为 min_depth,至多为 max_depth
  • 匹配 pattern.

可以用类似的方式添加其他规则。

最后一部分使用 extract_content.

定义的一些通用逻辑循环遍历过滤后离开您的路径并从元素中提取内容

如果您的网页相当简单并且您可以推断出这样的规则,那么它可能会起作用。否则,我猜您将不得不查看某种机器学习任务。