1. 程式人生 > >「Python」爬蟲自然語言清洗元件 v1.0.0

「Python」爬蟲自然語言清洗元件 v1.0.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

原文出處:https://blog.csdn.net/qq_33282586/article/details/80637257