在软件工程中,有着这么几个字“高内聚低耦合”,意思就是说:大模块分割成一个个小模块实现,每一个模块之间的独立性较高,修改某个模块,对其他模块或整个项目影响较小。
我们以一个图片下载的爬虫为示例,让大家更能清楚。
错误示例
import re
import requests
def Visit(url,regularity,regularity_1): #参数1,网页地址,参数2,正则表达式规则 参数3二级网页正则规则 参数4页码 参数5总页码
r = requests.get(url)
r.encoding = r.apparent_encoding
web_source=r.text
regular_ls=re.findall(regularity,web_source)
for i in range(len(regular_ls)):
yema=regular_ls[i]
print(yema)
url_1="https://www.236www.com/"+regular_ls[i] #提取的二级网页地址
print(url_1)
html= requests.get(url_1)
html.encoding = html.apparent_encoding
web_source_html = html.text
regular_ls_1 = re.findall(regularity_1, web_source_html)
for n in range(len(regular_ls_1)):
try:
picture_url=regular_ls_1[n]
picture_html=requests.get(picture_url)
address = "D:\\图片\\"+str(yema[17:])+"--"+str(i)+"--"+str(n)+".jpg" #图片下载的本地路径
with open(address, "wb") as f:
f.write(picture_html.content)
print(address, '下载完成')
except:
print(str(i)+str(n),"打印失败")
def web_batch(The_number_of):
regularity = '<a href="(.*?)" target="_blank' # 一级网页正则规则
regularity_1 = '<img src="(.*?)" />' # 二级网页正则规则
number=1
for i in range(The_number_of):
url = "https://www.0606rr.com/Html/63/index-2" + str(number) + ".html" # 访问网页地址
Visit(url, regularity, regularity_1)
number =number + 1
web_batch(5)
以上代码是一个下载图片的爬虫,不知道大家看了第一眼是什么感觉呢?
反正小编当时看的时候,就觉得可读性好低啊,不仅没有关键注释而且模块之间全部塞在一起,不一行一行的读,还真不太看得明白这个 py 文件是干嘛的。
以上代码小编觉得有这么几点缺点:
1.功能模块之间要么没空行,要么只空了一行。在 Python 编码规范中建议大家是每个模块之间空两行。
2.代码不健壮,请求没有异常处理,假如有一张图片的请求失败,那程序就崩溃了。
3.每个函数功能做的事情太多,跟“高内聚低耦合”相差太大,后期维护不方便。
修改后
修改一下上面代码,小编按照“高内聚低耦合”以及面向对象的思想修改了一下,代码量增加了一些,但爬虫不仅更健壮了,每一个模块的功能一目了然,关键注释也完善了。
爬虫想健壮且便于维护的话,一般都是按照这样的结构来写的,一般分 5 个模块,大型爬虫项目都是这样架构的,例如 Scrapy 框架也是基于这样的架构,如下:
spiderMan:
主逻辑模块,业务逻辑在这里实现。
from 图片.串行爬取.urlManager import urlManager
from 图片.串行爬取.htmlDownload import htmlDownload
from 图片.串行爬取.parseHtml import parseHtml
from 图片.串行爬取.dataOutput import dataOutput
import json
import time
class spipderMan():
"""
主逻辑
"""
def __init__(self):
"""
初始化各个模块
"""
self.manager = urlManager()
self.download = htmlDownload()
self.parse = parseHtml()
self.output = dataOutput()
def get_url(self):
"""
获取每一页的 url
:return:
"""
page_urls = self.manager.create_url()
self.request_url(page_urls)
def request_url(self,page_urls):
"""
请求每一页的 url
:return:
"""
for page_url in page_urls:
response = self.download.request_url(page_url)
# 判断是否请求成功
if response == None:
print('访问网页失败,可能被反爬或网络出问题了噢~')
break
# 判断是否是最后一页
html = json.loads(response.text)
if html['end'] != False:
print('没有更多图片了,下载完毕!')
break
else:
self.get_img_url(html)
def get_img_url(self,html):
"""
解析获取每一页所有图片的 url
:return:
"""
img_urls = self.parse.get_this_page_img_urls(html)
self.get_img(img_urls)
def get_img(self,img_urls):
"""
下载图片
:return:
"""
self.output.download_img(img_urls)
if __name__ == '__main__':
# 运行主接口
start_time = time.time()
spider = spipderMan()
spider.get_url()
end_time = time.time()
print(end_time - start_time)
urlManager:
控制 url 调度的模块。
class urlManager():
"""
管理 url
"""
def __init__(self):
"""
初始化需要拼接的 url
"""
self.base_url = 'http://image.so.com/zj?ch=beauty&sn={}&listtype=new&temp=1'
def create_url(self):
"""
构造每一页的 url
:return:
"""
urls = [self.base_url.format(str(i)) for i in range(0,1020,30)]
return urls
htmlDownload:
网页请求下载的模块。
import requests
import random
class htmlDownload():
"""
网页下载
"""
def __init__(self):
"""
初始化请求头
"""
USER_AGENTS = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.204 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/66.0',
]
self.headers = {'User-Agent' : random.choice(USER_AGENTS)}
def request_url(self,url):
"""
请求
:param url:
:return:
"""
response = requests.get(url,self.headers)
if response.status_code == 200:
return response
else:
return None
parseHtml:
解析获取数据的模块
class parseHtml():
"""
解析网页,提取数据
"""
def __init__(self):
self.img_urls = [] # 存储图片标题和 url 的列表
def get_this_page_img_urls(self,html):
"""
获取此页图片的 url
:param html:
:return: 存储图片 url 的列表
"""
# 打印当前下载了多少图片,先判断是否访问成功,成功就打印
if html['list']:
img_count = html['lastid'] - 30
print('当前已下载 {} 张,有误差,同名的会过滤~'.format(img_count))
for item in html['list']:
img_title = item['group_title']
img_url = item['qhimg_url']
self.img_urls.append((img_title,img_url))
return self.img_urls
dataOutput:
数据处理的模块,比如存入数据库,清洗等操作。
import os
from 图片.串行爬取.htmlDownload import htmlDownload
class dataOutput():
"""
数据输出处理
"""
def __init__(self):
"""
创建图片保存路径
"""
self.root_path = r'C:\Users\13479\Desktop\python项目\我的爬虫\有无多进程,线程图片,视频下载对比\图片\串行爬取\images\\'
# 如果没有文件路径就创建
if not os.path.exists(self.root_path):
os.mkdir(self.root_path)
self.download = htmlDownload()
def download_img(self,img_urls):
"""
下载图片的函数
:param img_urls: 图片名称,url 对应的列表
:return:
"""
for img_url in img_urls:
# 构造图片的完整下载路线
download_path = '{}{}.jpg'.format(self.root_path,img_url[0])
if not os.path.exists(download_path):
response = self.download.request_url(img_url[1])
try:
with open(download_path, 'wb') as f:
f.write(response.content)
except:
pass
else:
pass
以上是小编从 360图片 网站下载图片的爬虫,根据这样的结构,后期再来使用维护会方便很多。
“高内聚低耦合”是一种思想,并没有固定的编码结构,只是这样来写代码的话,不仅便于自己后期维护,给别人读可读性也挺高的。
文件获取
关注公众号“木下学Python”,回复“360图片爬虫”获取源代码。
end
小编这次写的是串行爬虫,下载 1000 赞图片用了 6 分钟,下次会修改为多进程,多线程跟大家分享。