美文网首页
「Python」爬虫自然语言清洗组件 v1.0.0

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

作者: HughDong | 来源:发表于2018-05-10 17:12 被阅读0次

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

    设计思路

    我认为从网站上爬取下来的内容要清洗的有两大块:通用清洗和规则清洗,换句话说就是可复用的和不可复用的。
    通用清洗是每个爬虫常见的问题,比如特殊编码、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
    

    相关文章

      网友评论

          本文标题:「Python」爬虫自然语言清洗组件 v1.0.0

          本文链接:https://www.haomeiwen.com/subject/mjktdftx.html