「Python」爬虫自然语言清洗组件 v1.0.0

2018-05-10  本文已影响0人  HughDong

公告:博主因使用魔理沙的扫把表达清洗,已被车万粉拉去祭天。

设计思路

我认为从网站上爬取下来的内容要清洗的有两大块:通用清洗和规则清洗,换句话说就是可复用的和不可复用的。
通用清洗是每个爬虫常见的问题,比如特殊编码、html标签、换行空格符等。
特殊清洗是在通用清洗的基础上,网站结构产生的特殊问题,比如多余的固定字符等。

通用清洗

通用清洗涵盖以下几个方面:

空字段补全
筛选附件和图片
特殊Unicode符号
HTML标签注释
其他字符(\r\n\t...)

通用清洗要注意顺序,否则会引起不必要的麻烦

空字段补全

优先把空内容(null/None/NaN)转换成空字符串(""),这样后续String类型操作不会报出TypeError。



不同网页间爬取下来的字段有些微差别,也将不存在的的字段进行了补全


筛选附件和图片

我爬正文数据下来时是整个正文元素直接获取的,所以也就在这里筛选出正文中的附件和图片

<a href="...">
<img src="...">

通过正则匹配到后判断链接是以http还是./ /开头,如果没有域名则添加该站的域名

最重要的一步是对图片链接进行清洗,如果只是提取链接的话会出现很多小图标


所以我在类规则中图片的deny和access规则,列表中存放的是正则表达式

rules = {
        'spider1': {
            'img': {
                'access': [],
                'deny': ['icon_.*?\.gif', '\.gif'],
            },
            'file': {
                'access': [],
                'deny': ['docid'],
            },
        },
    }

优先排除不需要的图片和保留一定需要的图片,剩下的部分使用String.BytesIO()判断图片尺寸,
保留长宽像素相乘>10000的图片

特殊Unicode符号

Python清洗Unicode数据

HTML标签注释

使用正则删除掉 <> /**/ 中的内容

re.compile(r'\<.*?\>|\/\*.*?\*\/').sub(' ', str)

其他字符

前面的清洗完成后基本还剩下换行符和标识符,使用str.replace()替换即可

str.replace('\\n', '') \
   .replace('\\r', '') \
   .replace('\\t', '') \
   .replace('\\xa0', '') \
   .replace('\\xc2', '') \
   .replace('\\u3000', '')

规则清洗

通用的清洗后会有一些特殊数据残留,我将特殊规则写在类中,根据具体规则实现字符串替换等操作

rules = {
        'spider1': {
            'content': {
                'replace': ["\',\'"],
            },
        },
    }

方法链调用

为了使用方便,封装在一条方法链中,清洗时只需要依次根据需求调用即可

Clear_Data(item) \
    .empty_key() \
    .catch_file_img() \
    .unicode_char('content') \
    .unicode_char('title') \
    .html_label('content') \
    .word_wrap('content') \
    .special_rules()

完整代码

clear.py
update /18.03.12.1

import pymongo
import re
from io import BytesIO
from PIL import Image
import requests


class Clear_Data():
    rules = {
        'spider1': {
            'img': {
                'access': [],
                'deny': ['icon_.*?\.gif', '\.gif'],
            },
            'file': {
                'access': [],
                'deny': ['docid'],
            },
            'content': {
                'replace': ["\',\'"]
            }
        },
        'spider2': {
            'img': {
                'access': ['_upload\/article'],
                'deny': ['icon_.*?\.gif', '\.gif'],
            },
            'file': {
                'access': [],
                'deny': [],
            }
        },
        'spider3': {
            'img': {
                'access': [],
                'deny': ['comm_20\.jpg', 'doc\.gif', 'arrow3\.gif', 'icon_.*?\.gif', '\.gif'],
            },
            'file': {
                'access': [],
                'deny': [],
            }
        }
    }

    def __init__(self, item):
        item.pop('_id')
        self.item = item

    def rep(self):
        return self.item

    def empty_key(self):
        fields = ['title', 'url', 'date', 'content', 'category', 'index', 'classify', 'institution',
                  'abstract', 'license', 'source', 'file', 'img']
        for key, value in self.item.items():
            if value == None:  # 清除空字段
                if key == 'file' or key == 'img':
                    self.item[key] = []
                else:
                    self.item[key] = ''

        for field in fields:  # 补全字段
            if not field in self.item:
                if field == 'file' or field == 'img':
                    self.item[field] = []
                else:
                    self.item[field] = ''
        return self

    def word_wrap(self, key):  # 去除换行空格
        self.item[key] = self.item[key] \
            .replace('\\n', '') \
            .replace('\\r', '') \
            .replace('\\t', '') \
            .replace('\\xa0', '') \
            .replace('\\xc2', '') \
            .replace('\\u3000', '')
        return self

    def html_label(self, key):  # 清除html标签
        self.item[key] = re.compile(r'\<.*?\>').sub(' ', self.item[key])
        return self

    def unicode_char(self, key):  # 清除unicode异常字符
        self.item[key] = re \
            .compile( \
            u"[^"
            u"\u4e00-\u9fa5"
            u"\u0041-\u005A"
            u"\u0061-\u007A"
            u"\u0030-\u0039"
            u"\u3002\uFF1F\uFF01\uFF0C\u3001\uFF1B\uFF1A\u300C\u300D\u300E\u300F\u2018\u2019\u201C\u201D\uFF08\uFF09\u3014\u3015\u3010\u3011\u2014\u2026\u2013\uFF0E\u300A\u300B\u3008\u3009"
            u"\!\@\#\$\%\^\&\*\(\)\-\=\[\]\{\}\\\|\;\'\:\"\,\.\/\<\>\?\/\*\+"
            u"]+") \
            .sub('', self.item[key])
        return self

    def catch_file_img(self):
        file = []
        img = []
        img_access_rule = '|'.join(self.rules[self.item['source']]['img']['access'])
        img_deny_rule = '|'.join(self.rules[self.item['source']]['img']['deny'])
        file_access_rule = '|'.join(self.rules[self.item['source']]['file']['access'])
        file_deny_rule = '|'.join(self.rules[self.item['source']]['file']['deny'])
        domain_name = re \
            .search(r'(?i)https?:\/\/.*?\/', self.item['url']) \
            .group()

        for content in re.findall(re.compile(r'\<img.*?src=.*?\>'), self.item['content']):
            if re.search(r'(?i)gif|jpg|png|psd|swf|bmp|emf|gif|webp', content):
                _url_ = re \
                            .search(r'(?i)src=[\'\"].*?[\'\"]', content) \
                            .group()[5:-1]
                if re.search(r'(?i)^http', _url_):  # 链接头部没有域名则添加
                    img_url = _url_
                else:
                    img_url = domain_name + _url_

                if img_deny_rule and re.search('(?i)' + img_deny_rule, img_url):  # 匹配deny规则丢弃
                    continue
                elif img_access_rule and re.search('(?i)' + img_access_rule,
                                                   img_url):  # 匹配access规则丢弃
                    img.append(img_url)
                else:  # 其他判断图片尺寸
                    try:
                        requests.adapters.DEFAULT_RETRIES = 5
                        r = requests.get(img_url)
                        tmp_im = BytesIO(r.content)
                        im = Image.open(tmp_im)
                    except OSError:
                        pass
                    else:
                        if im.size[0] * im.size[1] > 10000:
                            img.append(img_url)

        for content in re.findall(re.compile(r'\<a.*?href=.*?\>'), self.item['content']):
            if re.search(r'(?i)doc|docx|pdf|xlsx|xls|csv|txt|ppt|pptx|zip|rar|7z', content):
                _url_ = re \
                            .search(r'(?i)href=[\'\"].*?[\'\"]', content) \
                            .group()[6:-1]
                if re.search(r'(?i)^http', _url_):
                    file_url = _url_
                else:
                    file_url = domain_name + _url_

                if file_deny_rule and re.search('(?i)' + file_deny_rule, file_url):  # 匹配deny规则丢弃
                    continue
                elif file_access_rule and re.search('(?i)' + file_access_rule,
                                                    file_url):  # 匹配access规则丢弃
                    file.append(file_url)
                else:  # 未匹配则加入
                    print(file_url)
                    file.append(file_url)

        self.item['file'] = file
        self.item['img'] = img
        return self

    def special_rules(self):
        content_replace_rule = '|'.join(self.rules[self.item['source']]['content']['replace'])
        item['content'] = item['content'].replace(content_replace_rule, '')
        return self

count = 10000 # 分页请求数据
page = 0
while True:
    page = page + 1
    skip = (page - 1) * count
    items = list(db['data_raw'].find({}).skip(skip).limit(count))
    for item in items:
        Clear_Data(item) \ # 清洗过程
            .empty_key() \
            .catch_file_img() \
            .unicode_char('content') \
            .unicode_char('title') \
            .html_label('content') \
            .word_wrap('content') \
            .special_rules()
    db['data_value'].insert(items) # 批量插入
    print(str(page))
    if len(items) < count:
        break
上一篇下一篇

猜你喜欢

热点阅读