网页新闻抓取(一个典型的新闻网页包括几个不同区域:如何写爬虫)

优采云 发布时间: 2021-10-27 19:05

  网页新闻抓取(一个典型的新闻网页包括几个不同区域:如何写爬虫)

  我们之前实现的新闻爬虫运行后很快就可以爬取大量的新闻网页。数据库中存储的网页的html代码并不是我们想要的最终结果。最终的结果应该是结构化数据,至少包括url、标题、发布时间、正文内容、来源网站等。

  

  因此,爬虫不仅要做下载的工作,还要做数据清洗和提取的工作。所以,写爬虫是综合能力的体现。

  一个典型的新闻页面包括几个不同的区域:

  

  新闻页面区

  我们要提取的新闻元素收录在:

  导航栏区域和相关链接区域的文字不属于新闻元素。

  新闻的标题、发布时间、正文内容一般都是从我们抓取的html中提取出来的。如果只是网站的一个新闻页面,提取这三个内容很简单,写三个正则表达式就可以完美提取。但是,我们的爬虫抓取了数百个 网站 网页。将正则表达式写到这么多不同格式的网页上会很累,而且一旦对网页稍作修改,表达式可能会失效,维护这组表达式也很累。

  当然,穷尽的方法我们想不通,还得探索一个好的算法来实现。

  1. 标题提取

  基本上,标题会出现在html标签中,但会附加频道名称、网站名称等信息;

  标题也会出现在网页的“标题区”中。

  那么这两个地方哪里比较容易提取title呢?

  网页的“标题区”没有明显的标记,不同网站的“标题区”的html代码部分差别很大。所以这个区域不容易提取。

  然后只剩下标签。这个标签很容易提取,无论是正则表达式还是lxml解析。频道名称、网站名称等信息如何去除,并不容易。

  我们先来看看,标签中的附加信息是:

  观察这些标题,不难发现新闻标题、频道名称、网站名称之间有一些连接符号。然后我可以通过这些连接器拆分标题,并找出最长的部分是新闻标题。

  这个想法也很容易实现。这里就不写代码了,留给小猴子们作为思考练习自己去实现。

  2. 发布时间提取

  发布时间是指这个网页在这个网站上上线的时间,一般出现在文本标题下——元数据区。从html代码来看,这块区域并没有什么特别的地方可供我们定位,尤其是在很多网站板子前面,几乎不可能定位到这个区域。这就需要我们另辟蹊径。

  就像标题一样,我们来看看一些网站的发布时间是怎么写的:

  这些写在网页上的发布时间都有一个共同的特点,就是一个代表时间、年、月、日、时、分、秒的字符串,无非就是这些元素。通过正则表达式,我们列出一些具有不同时间表达式的正则表达式(也就是几个),然后我们就可以从网页文本中匹配和提取发布时间。

  这也是一个很容易实现的想法,但是细节比较多,应该尽量覆盖表达。写一个这样的函数来提取发布时间并不是那么容易的。小猴子们充分发挥自己的动手能力,看看能写出什么样的函数实现。这也是小猿的一种练习。

  3. 文本提取

  正文(包括新闻图片)是新闻网页的主体部分,视觉上占据中间位置,是新闻内容的主要文本区域。提取文本的方法有很多,实现起来复杂而简单。本文介绍的方法是基于老猿多年实践经验和思考的一种简单快捷的方法。我们称之为“节点文本密度方法”。

  我们知道,一个网页的html代码是由不同标签(tags)的树状结构树组成的,每个标签都是树的一个节点。通过遍历这个树结构的每个节点,找到文本最多的节点,就是文本所在的节点。按照这个思路,我们来实现代码。

  3.1 实现源码

  

#!/usr/bin/env python3

#File: maincontent.py

#Author: veelion

import re

import time

import traceback

import cchardet

import lxml

import lxml.html

from lxml.html import HtmlComment

REGEXES = {

    'okMaybeItsACandidateRe': re.compile(

        'and|article|artical|body|column|main|shadow', re.I),

    'positiveRe': re.compile(

        ('article|arti|body|content|entry|hentry|main|page|'

         'artical|zoom|arti|context|message|editor|'

         'pagination|post|txt|text|blog|story'), re.I),

    'negativeRe': re.compile(

        ('copyright|combx|comment||contact|foot|footer|footnote|decl|copy|'

         'notice|'

         'masthead|media|meta|outbrain|promo|related|scroll|link|pagebottom|bottom|'

         'other|shoutbox|sidebar|sponsor|shopping|tags|tool|widget'), re.I),

}

class MainContent:

    def __init__(self,):

        self.non_content_tag = set([

            'head',

            'meta',

            'script',

            'style',

            'object', 'embed',

            'iframe',

            'marquee',

            'select',

        ])

        self.title = ''

        self.p_space = re.compile(r'\s')

        self.p_html = re.compile(r' len(title) or len(ti) > 7:

                    title = ti

        return title

    def shorten_title(self, title):

        spliters = [' - ', '–', '—', '-', '|', '::']

        for s in spliters:

            if s not in title:

                continue

            tts = title.split(s)

            if len(tts)  text_node * 0.4:

                    to_drop.append(node)

        for node in to_drop:

            try:

                node.drop_tree()

            except:

                pass

        return tree

    def get_text(self, doc):

        lxml.etree.strip_elements(doc, 'script')

        lxml.etree.strip_elements(doc, 'style')

        for ch in doc.iterdescendants():

            if not isinstance(ch.tag, str):

                continue

            if ch.tag in ['div', 'h1', 'h2', 'h3', 'p', 'br', 'table', 'tr', 'dl']:

                if not ch.tail:

                    ch.tail = '\n'

                else:

                    ch.tail = '\n' + ch.tail.strip() + '\n'

            if ch.tag in ['th', 'td']:

                if not ch.text:

                    ch.text = '  '

                else:

                    ch.text += '  '

            # if ch.tail:

            #     ch.tail = ch.tail.strip()

        lines = doc.text_content().split('\n')

        content = []

        for l in lines:

            l = l.strip()

            if not l:

                continue

            content.append(l)

        return '\n'.join(content)

    def extract(self, url, html):

        '''return (title, content)

        '''

        title, node = self.get_main_block(url, html)

        if node is None:

            print('\tno main block got !!!!!', url)

            return title, '', ''

        content = self.get_text(node)

        return title, content

  3.2 代码分析

  像新闻爬虫一样,我们将整个算法实现为一个类:MainContent。

  首先,定义一个全局变量:REGEXES。它采集了一些经常出现在标签的class和id中的关键词。这些词表明标签可能是身体,也可能不是。我们用这些词来计算标签节点的权重,这是calc_node_weight()方法的函数。

  MainContent 类的初始化首先定义了一些不收录文本的标签 self.non_content_tag。当遇到这些标签节点时,忽略它们即可。

  提取标题的算法在函数 get_title() 中实现。首先,它首先获取label的内容,然后尝试从中找到title,然后尝试从中找到id和class收录title的节点,最后比较可能是不同地方获取的title的文本,并最终获得标题。比较的原则是:

  要从标签中获取标题,就需要解决标题清洗的问题。这里实现了一个简单的方法:clean_title()。

  在这个实现中,我们使用lxml.html将网页的html转换成树状,从body节点开始遍历每个节点,查看直接收录的文本长度(不带子节点),找到最长的节点的文本。这个过程在方法中实现:get_main_block()。其中一些细节可以被小猿体验。

  细节之一是 clean_node() 函数。get_main_block() 获得的节点可能收录相关新闻的链接。这些链接收录大量新闻标题。如果不去除,就会给相关新闻的新闻内容(标题、概览等)带来杂质。

  还有一个细节,get_text() 函数。我们从主块中提取文本内容,而不是直接使用text_content(),而是做一些格式化处理,比如在一些标签后添加换行符以匹配\n,以及在表格的单元格之间添加空格。经过这样的处理,得到的文本格式更加符合原网页的效果。

  爬虫知识点

  1. cchardet 模块

  快速判断文本编码的模块

  2. lxml.html 模块

  结构化html代码模块,通过xpath解析网页的工具,高效易用,是家庭爬虫必备模块。

  3. 内容提取的复杂性

  这里实现的文本提取算法基本可以正确处理90%以上的新闻网页。

  然而,世界上没有千篇一律的网页,也没有一劳永逸的提取算法。在*敏*感*词*使用本文算法的过程中,你会遇到奇怪的网页。这时候就必须针对这些网页来完善这个算法类。

  来自“ITPUB博客”,链接:,如需转载请注明出处,否则将追究法律责任。

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线