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