前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >爬虫的结构是什么样的呢?

爬虫的结构是什么样的呢?

作者头像
用户6825444
发布2019-12-18 11:49:06
8810
发布2019-12-18 11:49:06
举报
文章被收录于专栏:木下学Python木下学Python

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

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

错误示例

代码语言:javascript
复制
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:

主逻辑模块,业务逻辑在这里实现。

代码语言:javascript
复制
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 调度的模块。

代码语言:javascript
复制
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:

网页请求下载的模块。

代码语言:javascript
复制
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:

解析获取数据的模块

代码语言:javascript
复制
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:

数据处理的模块,比如存入数据库,清洗等操作。

代码语言:javascript
复制
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 分钟,下次会修改为多进程,多线程跟大家分享。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-05-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 木下学Python 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档