Python基于Xpath、PyQuery、正则表达式的网页源码整理工具

Python基于Xpath、PyQuery、正则表达式的网页源码整理工具

需求背景

  • 网络爬虫使用逐渐广泛

  • 爬虫抓取网页会有很多网页源码影响我们对数据的使用

  • 网上各种清理方法良莠不齐,无法真正的为我们提供便利

清理方法


使用Xpath 处理
 def xpath_clean(self, text: str, xpath_dict: dict) -> str:
     '''
     xpath 清除不必要的元素
     :param text: html_content
     :param xpath_dict: 清除目标xpath
     :return: string type html_content
     '''
     remove_by_xpath = xpath_dict if xpath_dict else dict()

     # 必然清除的项目 除非极端情况 一般这些都是要清除的
     remove_by_xpath.update({
         '_remove_2': '//iframe',
         '_remove_4': '//button',
         '_remove_5': '//form',
         '_remove_6': '//input',
         '_remove_7': '//select',
         '_remove_8': '//option',
         '_remove_9': '//textarea',
         '_remove_10': '//figure',
         '_remove_11': '//figcaption',
         '_remove_12': '//frame',
         '_remove_13': '//video',
         '_remove_14': '//script',
         '_remove_15': '//style'
     })

     parser = etree.HTMLParser(remove_blank_text=True, remove_comments=True)
     selector = etree.HTML(text, parser=parser)

     # 常规删除操作,不需要的标签删除
     for xpath in remove_by_xpath.values():
         for bad in selector.xpath(xpath):
             bad_string = etree.tostring(bad, encoding='utf-8',
                                         pretty_print=True).decode()
             logger.debug(f"clean article content : {bad_string}")
             bad.getparent().remove(bad)

     skip_tip = "name()='img' or name()='tr' or " \
                "name()='th' or name()='tbody' or " \
                "name()='thead' or name()='table'"

     # 判断所有p标签,是否有内容存在,没有的直接删除
     for p in selector.xpath(f"//*[not({skip_tip})]"):
         # br标签特殊判断规则
         if p.xpath('name()') == 'br':
             br = etree.tostring(p, encoding='utf-8',
                                 pretty_print=True).decode()
             if bool(re.sub('\s|<br\s?/?>', '', br)):
                 continue
         # 跳过逻辑
         if p.xpath(f".//*[{skip_tip}]") or \
                 bool(re.sub('\s', '', p.xpath('string(.)'))):
             continue

         bad_p = etree.tostring(p, encoding='utf-8',
                                pretty_print=True).decode()
         logger.debug(f"clean p tag : {bad_p}")
         p.getparent().remove(p)

     return etree.tostring(selector, encoding='utf-8',
                           pretty_print=True).decode()

# 执行样例
text = '''<div class="content">
    <p>7月27日,由中国电力科学研究院牵头,中国科学院微电子研究所、北京有色金属研究总院联合承担的国家电网公司科技项目“锂离子<a href="http://cn.large.net/energy-storage-battery/" class="seo-anchor" data-anchorid="299" target="_blank">储能电池</a>原位检测技术研究”顺利通过验收。项目研究成果填补了国内在储能电池领域原位检测技术基础研究方面的空白。 </p>
    <video tabindex="2" mediatype="video" src="https://vd3.bdstatic.com/mda-kiajuuwyi24hpcj6/v1-cae/1080p/mda-kiajuuwyi24hpcj6.mp4?auth_key=1599808408-0-0-8de87dbed16c9e4ee502a2a0b083ae11&bcevod_channel=searchbox_feed&pd=1&pt=3&abtest=8797_1" style="position: absolute; top: 0px; left: 0px;"></video>
    <br/>
    <p>项目成果为后续的深入研究奠定了理论和实践基础,为未来实现运行过程中电池的原位检测提供了有益的探索,对保障电力储能用锂离子电池的安全运行具有重要意义。</p>
</div>'''
xpath_dict = {"remove_1" : "//br", "remove_2" : "//table"}
pure_text = xpath_clean(text=text, xpath_dict=xpath_dict)
print(pure_text)
  • 默认删除iframe、button、form、input、select、option、textarea、figure、figcaption、frame、video、script、style标签,可通过配置remove_by_xpath来控制默认删除标签

  • 通过传入删除字典参数,同样可以处理我们不需要的标签


PyQuery 处理
    def pyquery_clean(self, text, url, pq_dict) -> object:
        '''
        pyquery 做出必要的处理,
        :param text:
        :param url:
        :param pq_dict:
        :return:
        '''
        # 删除pq表达式字典
        remove_by_pq = pq_dict if pq_dict else dict()
        # 标签属性白名单
        attr_white_list = ['rowspan', 'colspan']
        # 图片链接key
        img_key_list = ['src', 'data-echo', 'data-src', 'data-original']
        # 生成pyquery对象
        dom = pq(text)

        # 删除无用标签
        for bad_tag in remove_by_pq.values():
            for bad in dom(bad_tag):
                bad_string = pq(bad).html()
                logger.debug(f"clean article content : {bad_string}")
            dom.remove(bad_tag)

        # 标签各个属性处理
        for tag in dom('*'):
            for key, value in tag.attrib.items():
                # 跳过逻辑,保留表格的rowspan和colspan属性
                if key in attr_white_list:
                    continue
                # 处理图片链接,不完整url,补充完整后替换
                if key in img_key_list:
                    img_url = self.absolute_url(url, value)
                    pq(tag).remove_attr(key)
                    pq(tag).attr('src', img_url)
                    pq(tag).attr('alt', '')
                # img标签的alt属性保留为空
                elif key == 'alt':
                    pq(tag).attr(key, '')
                # 其余所有属性做删除操作
                else:
                    pq(tag).remove_attr(key)

        return dom.text(), dom.html()
  • 同样,我们也可以使用pyquery删除垃圾标签,通过传入pq_dict,使我们可以轻松去除垃圾标签

  • pyquery可以dom操作html源码,因此可以通过配置attr_white_list ,保留我们需要的标签属性,例如:rowspan,colspan 这两种元素属性可以使我们展示的表格格式更规整

  • img_key_list 的配置,也是至关重要的,因为部分网页源码有展示图片链接,我们可以通过配置该参数,可以轻松提炼出img链接,同样可以使用以下方法,补充url链接:

@staticmethod
    def absolute_url(baseurl: str, url: str) -> str:
        '''
        补充url
        :param baseurl:scheme url
        :param url: target url
        :return: complete url
        '''
        target_url = url if urlsplit(url).scheme else urljoin(baseurl, url)
        return target_url
baseurl = 'https://avatar.csdnimg.cn/'
url = 'csdnimg.cn/7/9/1/1_m0_50596262_1599273869.jpg'
tar_url = absolute_url(baseurl, url)
print(url)

打印结果如下:

https://avatar.csdnimg.cn/7/9/1/1_m0_50596262_1599273869.jpg

  • 此功能,在爬虫中应用广泛,可以通过代码智能的补充不完整的url链接,你值得拥有~~

正则表达式处理细节
# 空白行清理
text = text.replace('&#13;', '').replace('\u3000', '').replace('\t', '').replace('\xa0', '')
text = re.sub('\s{2,}', '', text)
text = re.sub('\n{2,}', '\n', text)
text = text.strip('\n').strip()

# 标签美化以及换行调整
text = re.sub('<br\s?/?>', '<br>', text)
text = re.sub(
	   '</?a>|</?em>|</?html>|</?body>|'
	   '</?head>|<[a-zA-Z]{1,10}\s?/>|'
	   '</?strong>|</?blockquote>|</?b>|'
	   '</?span>|</?i>|</?hr>|</?font>'
	   '|</?center>',
	   '',
	   text)
text = re.sub('\n', '', text)
text = re.sub('<h[1-6]>', '<p>', text)
text = re.sub('</h[1-6]>', '</p>', text)
text = text.replace('</p>', '</p>\n').replace('<br>', '<br/>\n')
  • 从此,妈妈再也不担心我的代码会混乱不堪了~~~~

代码整合结果

#!/usr/bin/env python
# -*-coding:utf-8-*-
'''
author: szhan
date:2020-09-14
summery: 清理html_conent以及获取纯净数据格式
'''
import re
from lxml import etree
from loguru import logger
from pyquery import PyQuery as pq
from urllib.parse import urlsplit, urljoin


class CleanArticle:

    def __init__(
            self,
            text: str,
            url: str = '',
            xpath_dict: dict = None,
            pq_dict: dict = None
    ):
        self.text = text
        self.url = url
        self.xpath_dict = xpath_dict or dict()
        self.pq_dict = pq_dict or dict()

    @staticmethod
    def absolute_url(baseurl: str, url: str) -> str:
        '''
        补充url
        :param baseurl:scheme url
        :param url: target url
        :return: complete url
        '''
        target_url = url if urlsplit(url).scheme else urljoin(baseurl, url)
        return target_url

    @staticmethod
    def clean_blank(text):
        '''
        空白处理
        :param text:
        :return:
        '''
        text = text.replace('&#13;', '').replace('\u3000', '').replace('\t', '').replace('\xa0', '')
        text = re.sub('\s{2,}', '', text)
        text = re.sub('\n{2,}', '\n', text)
        text = text.strip('\n').strip()
        return text

    def run(self):
        '''
        :return:处理后的content, html_content
        '''
        if (not bool(self.text)) or (not isinstance(self.text, str)):
            raise ValueError('html_content has a bad type value')
        # 首先,使用xpath去除空格,以及注释,iframe, button, form, script, style, video等标签
        text = self.xpath_clean(self.text, self.xpath_dict)

        # 第二步,使用pyquery处理具体细节方面
        str1, str2 = self.pyquery_clean(text, self.url, self.pq_dict)

        # 最终的正则处理
        content, html_content = self.regular_clean(str1, str2)

        return content, html_content

    def xpath_clean(self, text: str, xpath_dict: dict) -> str:
        '''
        xpath 清除不必要的元素
        :param text: html_content
        :param xpath_dict: 清除目标xpath
        :return: string type html_content
        '''
        remove_by_xpath = xpath_dict if xpath_dict else dict()

        # 必然清除的项目 除非极端情况 一般这些都是要清除的
        remove_by_xpath.update({
            '_remove_2': '//iframe',
            '_remove_4': '//button',
            '_remove_5': '//form',
            '_remove_6': '//input',
            '_remove_7': '//select',
            '_remove_8': '//option',
            '_remove_9': '//textarea',
            '_remove_10': '//figure',
            '_remove_11': '//figcaption',
            '_remove_12': '//frame',
            '_remove_13': '//video',
            '_remove_14': '//script',
            '_remove_15': '//style'
        })

        parser = etree.HTMLParser(remove_blank_text=True, remove_comments=True)
        selector = etree.HTML(text, parser=parser)

        # 常规删除操作,不需要的标签删除
        for xpath in remove_by_xpath.values():
            for bad in selector.xpath(xpath):
                bad_string = etree.tostring(bad, encoding='utf-8',
                                            pretty_print=True).decode()
                logger.debug(f"clean article content : {bad_string}")
                bad.getparent().remove(bad)

        skip_tip = "name()='img' or name()='tr' or " \
                   "name()='th' or name()='tbody' or " \
                   "name()='thead' or name()='table'"

        # 判断所有p标签,是否有内容存在,没有的直接删除
        for p in selector.xpath(f"//*[not({skip_tip})]"):
            # br标签特殊判断规则
            if p.xpath('name()') == 'br':
                br = etree.tostring(p, encoding='utf-8',
                                    pretty_print=True).decode()
                if bool(re.sub('\s|<br\s?/?>', '', br)):
                    continue
            # 跳过逻辑
            if p.xpath(f".//*[{skip_tip}]") or \
                    bool(re.sub('\s', '', p.xpath('string(.)'))):
                continue

            bad_p = etree.tostring(p, encoding='utf-8',
                                   pretty_print=True).decode()
            logger.debug(f"clean p tag : {bad_p}")
            p.getparent().remove(p)

        return etree.tostring(selector, encoding='utf-8',
                              pretty_print=True).decode()

    def pyquery_clean(self, text, url, pq_dict) -> object:
        '''
        pyquery 做出必要的处理,
        :param text:
        :param url:
        :param pq_dict:
        :return:
        '''
        # 删除pq表达式字典
        remove_by_pq = pq_dict if pq_dict else dict()
        # 标签属性白名单
        attr_white_list = ['rowspan', 'colspan']
        # 图片链接key
        img_key_list = ['src', 'data-echo', 'data-src', 'data-original']
        # 生成pyquery对象
        dom = pq(text)

        # 删除无用标签
        for bad_tag in remove_by_pq.values():
            for bad in dom(bad_tag):
                bad_string = pq(bad).html()
                logger.debug(f"clean article content : {bad_string}")
            dom.remove(bad_tag)

        # 标签各个属性处理
        for tag in dom('*'):
            for key, value in tag.attrib.items():
                # 跳过逻辑,保留表格的rowspan和colspan属性
                if key in attr_white_list:
                    continue
                # 处理图片链接,不完整url,补充完整后替换
                if key in img_key_list:
                    img_url = self.absolute_url(url, value)
                    pq(tag).remove_attr(key)
                    pq(tag).attr('src', img_url)
                    pq(tag).attr('alt', '')
                # img标签的alt属性保留为空
                elif key == 'alt':
                    pq(tag).attr(key, '')
                # 其余所有属性做删除操作
                else:
                    pq(tag).remove_attr(key)

        return dom.text(), dom.html()

    def regular_clean(self, str1: str, str2: str):
        '''
        正则表达式处理数据格式
        :param str1: content
        :param str2: html_content
        :return: 返回处理后的结果
        '''

        def new_line(text):
            text = re.sub('<br\s?/?>', '<br>', text)
            text = re.sub(
                '</?a>|</?em>|</?html>|</?body>|'
                '</?head>|<[a-zA-Z]{1,10}\s?/>|'
                '</?strong>|</?blockquote>|</?b>|'
                '</?span>|</?i>|</?hr>|</?font>'
                '|</?center>',
                '',
                text)
            text = re.sub('\n', '', text)
            text = re.sub('<h[1-6]>', '<p>', text)
            text = re.sub('</h[1-6]>', '</p>', text)
            text = text.replace('</p>', '</p>\n').replace('<br>', '<br/>\n')
            return text

        str1, str2 = self.clean_blank(str1), self.clean_blank(str2)  # TODO 处理空白行问题

        # TODO  html_content处理 1,删除多余的无法使用的标签以及影响数据展示的标签  2,换行符问题处理以及更换

        str2 = new_line(text=str2)

        return str1, str2


if __name__ == '__main__':
    with open('html_content.html', 'r', encoding='utf-8') as f:
        lines = f.readlines()
        html = ''
        for line in lines:
            html += line
    ca = CleanArticle(text=html)
    _, html_content = ca.run()
    print(html_content)
  • 看了这么久,给博主点个赞呗,如有不同意见可留言交流,谢谢!!!

  • 如果对爬虫感兴趣,欢迎关注博主,大量干货,等你来拿!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值