「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符號
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