在软件工程中,有着这么几个字“高内聚低耦合”,意思就是说:大模块分割成一个个小模块实现,每一个模块之间的独立性较高,修改某个模块,对其他模块或整个项目影响较小。


我们以一个图片下载的爬虫为示例,让大家更能清楚。


错误示例


 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图片 网站下载图片的爬虫,根据这样的结构,后期再来使用维护会方便很多。


“高内聚低耦合”是一种思想,并没有固定的编码结构,只是这样来写代码的话,不仅便于自己后期维护,给别人读可读性也挺高的。