聚沙成塔--爬虫系列(六)(请做个内外兼修的高手)

风清扬

版权声明:本文为作者原创文章,可以随意转载,但必须在明确位置标明出处!!!

上一篇文章我们介绍了函数的使用,以及函数设计应该参考的原则,但这些还不够的,函数只是让我们的代码可读性、可复用性提高了。并没有让我们的程序看上去那么健壮,专业术语叫「稳定行」;什么是稳定性呢,就是我们的程序经不起考验,就像大海中的孤舟经不起一点风浪的拍打,我们上一篇文章的代码只适合在风平浪静的时候出海,因为程序并没有一点容错处理,下面该是修炼内功的时候了,不能只有招式没有内涵啊...,那样的话你讲永远成为不了高手。

内功修炼总纲--异常处理

什么是异常处理,异常处理就是处理程序运行期间出现的任何意外或异常情况,程序员的日常生活中,错误几乎是每天都会发现,有些错误是致命的,会直接导致程序终止,直到错误被修复后再次执行程序,但是有些错误是可以忽略的,所有我们需要有一个机制能在程序运行中捕获到异常信息,这样可以帮助我们更快的解决问题。

什么是错误

错误是指语法或是逻辑上的,语法错误是值不能被解释器解释或不能被编译器编译,这些错误必须在程序执行前纠正。

当语法上没有什么错后,就剩下逻辑上的错误了,逻辑错误可能是由用户不完整或不合法的输入所致。

什么是异常

程序出现了错误而在正常流程以外采取的行为,这个行为分为两个阶段,首先是引起异常发生的错误,其次是采取可能措施的阶段。

第一个阶段是发生在一个异常条件后发生的,只要检测到并意识到异常条件,解释器就会触发一个异常。
第二个阶段是异常发生后,程序员可以做出各种不同的处理逻辑,当然也也可忽略异常。异常发生后需要特别注意的是当前异常发生后的逻辑都不会被执行了,如下我们定义了一个长度为4的list,而我们试图去访问第五个元素,那么就会产生一个越界的异常,而hello world将不再会被执行到

items = [1,2,3,4]
print(items[5])

print('hello world')

结果

Traceback (most recent call last):
  File "exception.py", line 2, in <module>
    print(items[5])
IndexError: list index out of range

检测和处理异常语法

异常可以通过try语句来检测,任何在try语句块里的代码都将被监测,except用来捕获异常,任何在try语句块里被检测到的异常都会被except捕获到,所以对于程序员来说我们需要考虑当异常发生后做一些收尾工作,像释放资源,重连数据库等等...

常见的异常错误

错误类型 描述
AttributeError 属性错误,特性引用和赋值失败时会引发属性错误
NameError 试图访问的变量名不存在
SyntaxError 语法错误,代码形式错误
Exception 所有异常的基类,因为所有python异常类都是基类Exception的其中一员,异常都是从基类Exception继承的,并且都在exceptions模块中定义。
IOError 一般常见于打开不存在文件时会引发IOError错误,也可以解理为输出输入错误
KeyError 使用了映射中不存在的关键字(键)时引发的关键字错误
IndexError 索引错误,使用的索引不存在,常索引超出序列范围,什么是索引
TypeError 类型错误,内建操作或是函数应于在了错误类型的对象时会引发类型错误
ZeroDivisonError 除数为0,在用除法操作时,第二个参数为0时引发了该错误
ValueError 值错误,传给对象的参数类型不正确,像是给int()函数传入了字符串数据类型的参数。

try-except语句

try-except是最常见的异常检测捕获写法,它由try块和except块组成,也可以有一个可选的错误原因

try:
    try_suite # watch for exceptions here 监控这里的异常
except Exception[, reason]:
    except_suite # exception-handling code 异常处理代码

try-except-finally语句

在try-except语句中我们可以有多个except语句块来捕获不同的错误类型,finally语句块是不管有没有异常发生都会被执行到,它通常用来最后释放资源。

try:
    try_suite
except Exception1:
    suite_for_Exception1
except (Exception2, Exception3, Exception4):
    suite_for_Exceptions_2_3_and_4
except Exception5, Argument5:
    suite_for_Exception5_plus_argument
except (Exception6, Exception7), Argument67:
    suite_for_Exceptions6_and_7_plus_argument
except:
    suite_for_all_other_exceptions
else:
    no_exceptions_detected_suite
finally:
    always_execute_suite

总纲目录

同过上面的介绍可能有人要问我写代码的时候不知道可能会发生什么异常,有可能一种异常,也有可能几种异常,那怎么办呢,嗯,不错能想到这个问题的人证明都是认真去思考过的人,下面我们看看异常的继承关系,这里还没有讲到类的继承关系,不过不要紧,你就想象成你与你爹,你爷爷的关系就好,你爷爷把财产都留给你了你爹,你爹以后也会把财产留给你。

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
           +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

可以看到BaseException是异常的祖先,BaseException下有4兄弟,分别是SystemExit、KeyboardInterrupt、GeneratorExit、Exception异常,所以如何你要捕捉Ctrl + c终端异常那么你的except语句应该写成except KeyboardInterrupt as e;那么从上图我们可以看到绝大部分的异常都是继承自Exception的,所以如果我们要捕获除SystemExit、KeyboardInterrupt、GeneratorExit三种异常外都可以写成except Exception as e;

努力做个像风清扬一样的高手

通过上面的介绍,接下来我们来分析分析我为什么说之前的代码只适合在风平浪静的时候出海呢,从我们的代码可以看出我们的代码写出来的逻辑是理想中没有任何错误才能得出正确结果的,那么如果我们的url地址是个不可访问的地址会发生什么呢,我们将url地址改为「https://www.qiushibaike1.com」看看会有什么结果:

from urllib import request
import re

url = 'https://www.qiushibaike1.com'
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'
headers = {'User-Agent': user_agent}


def read_html(url, headers, codec):
    '''[read_html]
    
    [读取html页面内容]
    
    Arguments:
        url {[string]} -- [url地址]
        headers {[dict]} -- [用户代理,这里是一个字典类型]
        codec {[string]} -- [编码方式]
    
    Returns:
        [string] -- [页面内容]
    '''
    # 构建一个请求对象
    req = request.Request(url, headers=headers)
    # 打开一个请求
    response = request.urlopen(req)
    # 读取服务器返回的页面数据内容
    content = response.read().decode(codec)

    return content

def match_element(content, pattern):
    '''[match_element]
    
    [匹配元素]
    
    Arguments:
        content {[string]} -- [文本内容]
        pattern {[object]} -- [匹配模式]

    Returns:
        [list] -- [匹配到的元素]
    '''
    # 匹配所有用户信息
    
    userinfos = re.findall(pattern, content)
    
    return userinfos

content = read_html(url, headers, 'utf-8')
pattern = re.compile(r'<div class="article block untagged mb15[\s\S]*?class="stats-vote".*?</div>', re.S)
userinfos = match_element(content, pattern)

if userinfos:
    pattern = re.compile(r'<a href="(.*?)".*?<h2>(.*?)</h2>.*?<div class="content">(.*?)</div>', re.S)
    for userinfo in userinfos:
        item = match_element(userinfo, pattern)
        #print(item)
        if item:
            userid, name, content = item[0]
            # 去掉换行符,<span></span>,<br/>符号
            userid = re.sub(r'\n|<span>|</span>|<br/>', '', userid)
            name = re.sub(r'\n|<span>|</span>|<br/>', '', name)
            content = re.sub(r'\n|<span>|</span>|<br/>', '', content)
            print((userid, name, content))

结果

Traceback (most recent call last):
 File "func_scrap.py", line 49, in <module>
   content = read_html(url, headers, 'utf-8')
 File "func_scrap.py", line 25, in read_html
   response = request.urlopen(req)
 File "E:\Program Files\Python36\lib\urllib\request.py", line 223, in urlopen
   return opener.open(url, data, timeout)
 File "E:\Program Files\Python36\lib\urllib\request.py", line 526, in open
   response = self._open(req, data)
 File "E:\Program Files\Python36\lib\urllib\request.py", line 544, in _open
   '_open', req)
 File "E:\Program Files\Python36\lib\urllib\request.py", line 504, in _call_chain
   result = func(*args)
 File "E:\Program Files\Python36\lib\urllib\request.py", line 1361, in https_open
   context=self._context, check_hostname=self._check_hostname)
 File "E:\Program Files\Python36\lib\urllib\request.py", line 1320, in do_open
   raise URLError(err)
urllib.error.URLError: <urlopen error [Errno 11001] getaddrinfo failed>

可以从结果中看出我们解释器检测到了一个urllib.error.URLError异常,异常信息是urlopen error [Errno 11001] getaddrinfo failed(获取地址信息失败)

为程序增加容错处理机制,让程序变得更健壮

修改后的程序代码如下:

from urllib import request
import re

url = 'https://www.qiushibaike1.com'
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'
headers = {'User-Agent': user_agent}


def read_html(url, headers, codec):
    '''[read_html]
    
    [读取html页面内容]
    
    Arguments:
        url {[string]} -- [url地址]
        headers {[dict]} -- [用户代理,这里是一个字典类型]
        codec {[string]} -- [编码方式]
    
    Returns:
        [string] -- [页面内容]
    '''
    # 构建一个请求对象
    try:
        req = request.Request(url, headers=headers)
        # 打开一个请求
        response = request.urlopen(req)
        # 读取服务器返回的页面数据内容
        content = response.read().decode(codec)

        return content
    except Exception as e:
        print(e)
        return None
    

    

def match_element(content, pattern):
    '''[match_element]
    
    [匹配元素]
    
    Arguments:
        content {[string]} -- [文本内容]
        pattern {[object]} -- [匹配模式]

    Returns:
        [list] -- [匹配到的元素]
    '''
    # 匹配所有用户信息
    
    userinfos = re.findall(pattern, content)
    
    return userinfos

content = read_html(url, headers, 'utf-8')
pattern = re.compile(r'<div class="article block untagged mb15[\s\S]*?class="stats-vote".*?</div>', re.S)
print('执行到这里了,表示我还没有终止!')
if content:
    userinfos = match_element(content, pattern)

    if userinfos:
        pattern = re.compile(r'<a href="(.*?)".*?<h2>(.*?)</h2>.*?<div class="content">(.*?)</div>', re.S)
        for userinfo in userinfos:
            item = match_element(userinfo, pattern)
            #print(item)
            if item:
                userid, name, content = item[0]
                # 去掉换行符,<span></span>,<br/>符号
                userid = re.sub(r'\n|<span>|</span>|<br/>', '', userid)
                name = re.sub(r'\n|<span>|</span>|<br/>', '', name)
                content = re.sub(r'\n|<span>|</span>|<br/>', '', content)
                print((userid, name, content))

执行结果

<urlopen error [Errno 11001] getaddrinfo failed>
执行到这里了,表示我还没有终止!

可以看到我们的程序并没有在抛出异常导致程序终止,如果我们不写try-except再去执行上面的代码,程序肯定执行不到“执行到这里了,表示我还没有终止!”这里了。大家可以试试

urllib.error.URLError异常

从上面的途中我们可以看到程序抛出了urllib.error.URLError异常,所以我们可以捕捉这个异常,这个异常的参数定义可以查看开发文档如下

urllib.error.URLError定义

所以我们可以把except Exception as e:,改写城except urllib.error.URLError as e:异常的精准捕获。


note: 做为开发人员要时刻提醒自己对异常,对容错能力的处理,让我们开发的程序能够更稳定,更坚固,让我们的程序能在服务器上长时间运行不宕机,当然这也是成为一个高手的必要素质。

欢迎关注我:「爱做饭的老谢」,老谢一直在努力...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 1 前言 作为一名合格的数据分析师,其完整的技术知识体系必须贯穿数据获取、数据存储、数据提取、数据分析、数据挖掘、...
    whenif阅读 18,051评论 45 523
  • Python异常处理 异常概念: 异常:就是不正常的情况,程序开发过程中错误和BUG都是补充正常的情况 异常发生的...
    youngkun阅读 919评论 0 4
  • 第十一章 使用加载项自定义ArcGIS界面 ||| 附录A 自动执行Python脚本 我们将在本章介绍以下案例: ...
    muyan阅读 8,748评论 0 2
  • 一、简介 Python最强大的结构之一就是它的异常处理能力,所有的标准异常都使用类来实现,都是基类Exceptio...
    随风化作雨阅读 3,060评论 0 1
  • “波罗的海上散列的成千岛屿,将斯德哥尔摩附近的水面全匀摆的波平如镜,如同无限延伸的大湖,大多时候,津浦无人,桅樯参...
    最亲爱的李扬阅读 158评论 0 0