【Python爬虫】静态网页完整爬虫项目详解,学会爬虫构建思路

前言

  首先明确:针对一个网站的爬虫一般不能直接套用在另一个网站上,但构建的思路是相通的。换句话说,写爬虫基本都是具体问题具体分析,但是分析的思路都是一致的。当然,通过代码整理也可以实现一个爬虫项目爬取多个网站,但是相关参数的改动是必然的。
  需要提前了解的一点东西:
  (1)url:形式上为网址“https:……”,即资源存放的地方,访问它就能获得资源;
  (2)浏览器一般都有开发者工具,可以直接鼠标右键再选择“检查”、或在网页菜单里有“开发者工具”、或浏览器设置搜索“开发者工具”,该工具可以分析网页元素背后的代码、查看爬虫需要的请求头,我使用Edge浏览器,打开后默认是“元素”板块,如下图;

吐
  (3)静态网页:不翻动网页,工具界面的元素已经具有网页全部信息,使用requests库足以获取全部信息;
  (4)动态网页:只有向下翻网页,开发者工具界面的某些元素才会显现,requests库就不够看了,可以使用selenium库模拟鼠标事件来模拟翻动网页获取更多信息。

本项目应用场景

  大部分漫画都有“分类板块”的网页如下图,这种网页上排列多个分类结果且分页

图1
  点击一个分类结果进入到新网页,新网页含有章节信息,如下图。

图2
  点击某一章进入新网页,新网页含有资源,也就是我们要爬取的目标资源,如下图。

图3
  具有上述网页结构的静态网页,目标为爬取多部漫画,则可以使用本爬虫项目。其中漫画也可以替换成小说、视频,但必须具有上述网页结构、为静态网页。如果不是爬取多部漫画而是一部,对本项目做简单修改即可,后续说明。

思路分析

  以上述应用场景为例,将获取的信息分为三级:漫画级、章节级、目标级。则爬取的过程可以简单概括为:先通过分类网页(第一张图)爬取多个漫画级url然后通过漫画级url(第二张图)获得章节级url最后通过章节级url(第三张图)获得目标级资源。因此,要爬取多部漫画,本项目的输入就是分类网页(第一页)的url;只爬取一部的,看完后续分析,就能轻松修改本项目代码,达到目的。
  此外,爬取的过程并不是一帆风顺的,因此本项目会保存访问异常的url。由此,我们编写的代码函数需要实现的功能可以有:

  • 函数1-----输入:分类网页url;输出:多个漫画级url
  • 函数2-----输入:一个漫画级url;输出:多个章节级url,漫画名、章节名
  • 函数3-----输入:多个章节级url,章节名,存储目录;下载目标资源,无输出
  • 函数4-----输入:文件名;具体的资源保存过程,无输出
  • 函数5-----输入:异常url文件名;重新访问异常url,无输出

  以上为本爬虫项目中实现的函数。当然,读者也可以构思其他功能的函数,比如“通过多个章节级url下载资源”可以改成“通过一个章节级url下载资源”或者全放在一个函数里也行。如果只爬取一部漫画,只需弃用第一个功能,从第二个功能开始调用即可。看到这里,最关键的问题出来了:如何从一个网页的全部信息中得到目标信息?这也是爬虫最难的部分,可以使用re正则化匹配,本项目使用的是XPATH匹配,XPATH的匹配语法:Python使用XPath解析HTML:从入门到精通
  掌握一定XPATH语法规则后,就可以通过开发者工具分析网页元素、确定目标信息的匹配模式。
  到此,对具有某一类网络结构的网页构建爬虫的分析结束,大致框架已经确定,接下来就是具体的代码工作。

代码分析

函数1

  代码如下,其中的注释解释了更具体的细节,这里不再赘述1 。同时附有测试结果2

def batchget_comics(page_urls, comic_urls, url=URL, header=HEADER):
    '''----------批量获取指向漫画的url-----------\n
    page_urls:必须为列表[url],url即输入参数url\n
    comic_urls:存储漫画的url\n
    url:类似分类板块的网址,被第一个爬取,因此为默认值\n
    header:请求头,默认值\n
    return:(全部漫画的url,网站‘下一页’的url) or None'''
    try:
        response = requests.get(url=url, headers=header, timeout=TIMEOUT)
        # 使用try语句避免请求卡死,程序失去响应
    except Exception:
        print('#----------爬取网址URL异常!已保存异常及相关信息!请检查相关输入或此网址不可爬取!---------#')
        with open(ABNORMAL_SAVE_PATH, 'a') as file:
            file.write('website:' + url + '\n')
            # url访问异常,追加形式保存,可用于事后手动检查、爬取
        return None
    else:
        text = etree.HTML(response.content)
        k = 0
        a = [item for item in text.xpath(MATCH_PAGE_URL) if item.find('page') >= 0]
        nextpage_url = ROOT_URL + a[0]
        # 获得当前页上的‘下一页’指向的url
        comic_urls.append(text.xpath(MATCH_COMIC_URL))
        # 最先存入传入参数url上的全部漫画url,即第一页的内容
        print('开始爬取:|#', end='')
        sleep(SLEEP_TIME)
        # 每次发出请求后,休眠0.05秒,避免给网站造成压力或被反爬
        while nextpage_url != page_urls[-1]:
            # ‘下一页’的url不是已经访问过的url,就继续执行
            page_urls.append(nextpage_url)
            # 存入‘下一页’url
            try:
                response = requests.get(url=nextpage_url, headers=header, timeout=TIMEOUT)
            except Exception:
                print('\n#-------“下一页”url访问异常!已保存异常及相关信息!并停止向“下一页”爬取!-------#')
                with open(ABNORMAL_SAVE_PATH, 'a') as file:
                    file.write('page:' + nextpage_url + '\n')
                break
            else:
                text = etree.HTML(response.content)
                #-------------------------------#
                #这段对匹配结果做修饰
                a = [item for item in text.xpath(MATCH_PAGE_URL) if item.find('page') >= 0]
                if k == 0:
                    nextpage_url = ROOT_URL + a[0]
                    k += 1
                else:
                    nextpage_url = ROOT_URL + a[1]
                #--------------------------------#
                comic_urls.append(text.xpath(MATCH_COMIC_URL))
                # ‘下一页’url上的全部漫画url
                sleep(SLEEP_TIME)
                print('#', end='')
        print('#|')
        return [ROOT_URL + comic for urls in comic_urls for comic in urls], page_urls

  爬取对象为分类网页urlhttps://www.ucmanhua.com,分类结果有23个page,如下图:

在这里插入图片描述
  函数1测试结果如下,打印前5个漫画级url和23个page_url,读者可以复制url尝试,不过那时可能该网站已经不存在或是分类结果发生改动,导致读者找不到和上图一样的地方:

开始爬取:|########################|
comic_urls: ['https://www.ucmanhua.com/comic/9j3Ne8kzbo', 'https://www.ucmanhua.com/comic/9j3Ne8kzbo', 'https://www.ucmanhua.com/comic/Wqb1ENG1bG', 'https://www.ucmanhua.com/comic/Wqb1ENG1bG', 'https://www.ucmanhua.com/comic/ZqaBRwNraP', 'https://www.ucmanhua.com/comic/ZqaBRwNraP', 'https://www.ucmanhua.com/comic/njaypy7Z3W', 'https://www.ucmanhua.com/comic/njaypy7Z3W', 'https://www.ucmanhua.com/comic/9ZM4y79Ab4', 'https://www.ucmanhua.com/comic/9ZM4y79Ab4']
page_urls: ['https://www.ucmanhua.com/category/list/1/tags/541', 'https://www.ucmanhua.com/category/list/1/tags/541/page/2', 'https://www.ucmanhua.com/category/list/1/tags/541/page/3', 'https://www.ucmanhua.com/category/list/1/tags/541/page/4', 'https://www.ucmanhua.com/category/list/1/tags/541/page/5', 'https://www.ucmanhua.com/category/list/1/tags/541/page/6', 'https://www.ucmanhua.com/category/list/1/tags/541/page/7', 'https://www.ucmanhua.com/category/list/1/tags/541/page/8', 'https://www.ucmanhua.com/category/list/1/tags/541/page/9', 'https://www.ucmanhua.com/category/list/1/tags/541/page/10', 'https://www.ucmanhua.com/category/list/1/tags/541/page/11', 'https://www.ucmanhua.com/category/list/1/tags/541/page/12', 'https://www.ucmanhua.com/category/list/1/tags/541/page/13', 'https://www.ucmanhua.com/category/list/1/tags/541/page/14', 'https://www.ucmanhua.com/category/list/1/tags/541/page/15', 'https://www.ucmanhua.com/category/list/1/tags/541/page/16', 'https://www.ucmanhua.com/category/list/1/tags/541/page/17', 'https://www.ucmanhua.com/category/list/1/tags/541/page/18', 'https://www.ucmanhua.com/category/list/1/tags/541/page/19', 'https://www.ucmanhua.com/category/list/1/tags/541/page/20', 'https://www.ucmanhua.com/category/list/1/tags/541/page/21', 'https://www.ucmanhua.com/category/list/1/tags/541/page/22', 'https://www.ucmanhua.com/category/list/1/tags/541/page/23']

函数2

  代码如下,其中的注释解释了更具体的细节,这里不再赘述。

def get_suburls(url, header=HEADER):
    '''--------------获取一部漫画下指向章节的全部url--------------\n
    url:一部漫画网址\n
    header:请求头,默认值\n
    return:(漫画名,章节名,章节urls) or None'''
    try:
        response = requests.get(url=url, headers=header, timeout=TIMEOUT)
    except Exception:
        print('\n#------------资源访问失败!已保存异常及相关信息!-----------#')
        with open(ABNORMAL_SAVE_PATH, 'a') as file:
            file.write('comic:' + url + '\n')
        return None
    else:
        text = etree.HTML(response.content)
        comic_name = fix_text(text.xpath(MATCH_COMIC_NAME)[0]).split(' ')[0]
        # 获取漫画名字
        chapter_names = text.xpath(MATCH_CHAPTER_NAME)
        chapter_names = [fix_text(item) for item in chapter_names]
        # 获取漫画章节名字
        chapter_urls = text.xpath(MATCH_CHAPTER_URL)
        # 获取漫画章节的url
        sleep(SLEEP_TIME)
        return comic_name, chapter_names, chapter_urls

  爬取对象为一个漫画级urlhttps://www.ucmanhua.com/comic/9j3Ne8kzbo,即上面测试结果comic_urls的第一个,漫画名为“死灵法师!我即是天灾”,共85话,如下图:

图4
  函数2测试结果如下:

comic_name: 死灵法师!我即是天灾
chapter_name: ['001话:转职!死灵法师!', '002话:骷髅战士!', '003话:贪吃的宁伊伊!', '004话:新技能!', '005话:单刷噩梦级', '006话:王朝公会仗势欺人?!', '007话:团灭王朝公会', '008话:夏大小姐 三观崩塌', '009话:大考开始!!', '010话 林默语断崖领先!', '011话 无限秒杀!!', '012话 神秘恶魔 入侵试练塔!', '013话 我的经验值?', '014话 全国状元?!', '015话 状元奖励!', '016话 试炼任务!', '017话 试炼开始!', '018话 林默语强势突围', '019话 骷髅全军出击', '020话 独占人鱼之泪', '021话 白神的奖励', '022话 我单刷!', '023话 经验圣地,蜘蛛巢穴!', '024话 全新装备', '025话 单刷大型副本', '026话 白银级的骷髅法师', '027话 1人VS一城', '028话 百里学院丢大脸!', '029话 大神求带', '030话 百里学院的阴谋', '031话 开学典礼!', '032话 召唤封印符?', '033话 前往元战场!', '034话 深渊魔犬', '035话 绝对数量碾压!', '036话 至今最强BOSS?', '037话 再次出发!', '038话 带你见识地狱级副本!', '039话 男女搭配,刷本不累!', '040话 深渊狗魔!不堪一击!', '041话 冤家路窄', '042话 深渊突袭', '043话 一人成军', '044话 大战结束!军衔提升!', '045话 古罗教延?火神斗士', '046话 副本跟随追杀', '047话 古罗教延即将覆灭', '048话 古罗教延覆灭!', '049话 职业者大赛报名!', '050话 白神的新任务!', '051话 进入元素秘境', '052话 骷髅VS元素将军', '053话 元素女王', '054话 二转职业者', '055话 三号战场!求救信号?', '056话 刷军功的好去处!', '057话 对决!深渊影魔!', '058话 尸体爆裂!烈焰魔王的报复!', '本周停更一话0203', '059话 旷古大战!', '060话 地狱级BOSS之间的对决!', '061话 真BOSS现身!', '062话 惊现神级法师残魂!', '063话 恶魔再次来袭!', '064话 战场鬼王现!', '第066话 招纳新的灵魂!', '第065话 狂战士还是小丑?', '第067话 拜师白神!', '第068话 强者云集', '第069话 大赛开始!', '第070话 圣剑舞者', '第071话 团体赛开战!', '第072话 势 不可挡', '第073话 单挑神剑士', '第074话 强敌出现!', '第075话 决赛!', '第076话 强势夺冠!', ' 第077话 海啸天崩', '第078话 深渊突袭?!', '第078话 单人赛一人成军', '第080话 挑战赛开始!', ' 第081话 挑战赛二轮!', '第082话 第三轮挑战我也要赢', '第083话 第四轮挑战!', '第085话 手段尽出!']
chapter_urls: ['/chapter/Qg4dRi20mX.html', '/chapter/1ENWBtNK27.html', '/chapter/RyemQc6ZKW.html', '/chapter/552POugJ0p.html', '/chapter/wVZdLSdL8B.html', '/chapter/KLr4GSYAwJ.html', '/chapter/A61pDceDym.html', '/chapter/p8Zg9SnANr.html', '/chapter/BRwJdfkJQW.html', '/chapter/JqOgyuPmYp.html', '/chapter/Ne8kzS4v2v.html', '/chapter/OEjnqtOR0B.html', '/chapter/rXZeGfm7kL.html', '/chapter/7Ajz7iAp82.html', '/chapter/2kO9pc24jD.html', '/chapter/8Og62cnqND.html', '/chapter/jxeOmtkK4y.html', '/chapter/gOjpncYnPX.html', '/chapter/4y7GNcK6D2.html', '/chapter/k8ErxSQLeo.html', '/chapter/eN2AEcxeyp.html', '/chapter/EKArYu7Ov6.html', '/chapter/nBZ80fVnkP.html', '/chapter/D1KOxSVleY.html', '/chapter/mzn2ZtKWAG.html', '/chapter/d17KeSw0z1.html', '/chapter/xzZmLtZo1Q.html', '/chapter/Yq1ypudmYW.html', '/chapter/Z4wGJcEBlz.html', '/chapter/09V0AsvVnX.html', '/chapter/WPRWAcwJE5.html', '/chapter/994R8sEmR1.html', '/chapter/ypyVLSRm7v.html', '/chapter/GKp20uoP21.html', '/chapter/PBgD6f5P4m.html', '/chapter/opZjWSz6L1.html', '/chapter/zpXYLSLlBO.html', '/chapter/qzZ4yt2X24.html', '/chapter/lKk7YuZ5ZZ.html', '/chapter/XxQ9BtYyYK.html', '/chapter/Lg7ePi9L9K.html', '/chapter/VeV0KSgygr.html', '/chapter/vWZDqT767K.html', '/chapter/6d8orfJRJ0.html', '/chapter/RyemQc6Z6W.html', '/chapter/Qg4dRi202X.html', '/chapter/1ENWBtNKN7.html', '/chapter/wVZdLSdLdB.html', '/chapter/552POugJgp.html', '/chapter/KLr4GSYAYJ.html', '/chapter/A61pDceDem.html', '/chapter/BRwJdfkJkW.html', '/chapter/p8Zg9SnAnr.html', '/chapter/JqOgyuPmPp.html', '/chapter/Ne8kzS4v4v.html', '/chapter/OEjnqtOROB.html', '/chapter/rXZeGfm7mL.html', '/chapter/7Ajz7iApA2.html', '/chapter/2kO9pc242D.html', '/chapter/qzZ4yt2XyZ.html', '/chapter/lKk7YuZ5Jq.html', '/chapter/lKk7YuZzrD.html', '/chapter/Lg7ePi9eZz.html', '/chapter/XxQ9BtYo9X.html', '/chapter/vWZDqT7jeZ.html', '/chapter/6d8orfJ4ex.html', '/chapter/6d8orfJ4Xr.html', '/chapter/RyemQc6Gxe.html', '/chapter/Lg7ePi96po.html', '/chapter/zpXYLSLEzj.html', '/chapter/qzZ4yt2wPE.html', '/chapter/WPRWAcwGkw.html', '/chapter/EKArYu72Nw.html', '/chapter/k8ErxSQBOO.html', '/chapter/Yq1ypudkZr.html', '/chapter/rXZeGfmdWl.html', '/chapter/Lg7ePi9wQZ.html', '/chapter/vWZDqT7k4L.html', '/chapter/VeV0KSgODP.html', '/chapter/lKk7YuZn9v.html', '/chapter/gOjpncYexn.html', '/chapter/jxeOmtkVwX.html', '/chapter/EKArYu7BO9.html', '/chapter/k8ErxSQqL5.html', '/chapter/rXZeGfmdpp.html']

函数3

  代码如下,其中的注释解释了更具体的细节,这里不再赘述。

def download_suburl_resource(suburls, subnames, comic_dir, header=HEADER):
    '''-----------下载一部漫画全部章节下url指向的资源------------\n
    suburls:漫画章节网址\n
    subnames:url资源的名字\n
    comic_dir:漫画在下载目录的文件夹\n
    header:请求头,默认值\n'''
    for suburl, subname in zip(suburls, subnames):
        # 获取每章节的url,章节名
        url = ROOT_URL + suburl
        try:
            response = requests.get(url=url, headers=header, timeout=TIMEOUT)
        except Exception:
            print('\n#------------资源访问失败!已保存异常及相关信息!休息10秒-----------#')
            with open(ABNORMAL_SAVE_PATH, 'a') as file:
                file.write(comic_dir.split('/')[-1] + ':' + subname  + ':' + url + '\n')
                # 存入“漫画名:章节名:url”
            sleep(10)
        else:
            text = etree.HTML(response.text)
            resource_urls = text.xpath(MATCH_RESOURCE_URL)
            # 获取每一章节下目标全部资源的url
            print('resource_urls:', resource_urls)
            sleep(SLEEP_TIME)
            for num, res_url in tenumerate(resource_urls, desc=subname, colour='blue'):
                # 遍历每个资源
                try:
                    resource = requests.get(url=res_url, headers=header, timeout=TIMEOUT)
                except Exception:
                    print('\n#------------资源下载失败!已保存异常及相关信息!休息10秒-----------#')
                    with open(ABNORMAL_SAVE_PATH, 'a') as file:
                        file.write(comic_dir.split('/')[-1] + ':' + subname + ':' + str(num) + ':' + res_url + '\n')
                        # 存入“漫画名:章节名:num号资源:url”
                    sleep(10)
                else:
                    file_name = comic_dir + '/' + subname + str(num) + FORMAT
                    get = threading.Thread(target=download, kwargs={'file_name': file_name, 'resource': resource.content}, daemon=True)
                    # 监控资源保存到设备的过程
                    get.start()
                    get.join(timeout=20)
                    # 超过时限,停止保存,事后设备中可能存在打开异常或内容不完整的文件
                    sleep(SLEEP_TIME)

  爬取对象为一个漫画的全部章节级url,测试只使用一个章节级url:https://www.perfectmanhua
  函数3测试结果如下,读者可以复制下面的url并进入,应该可以看到一张具体的漫画:

resource_urls: ['https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/1_8543-kkk0bj5tbsz.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/2_6583-f2jnnqvt3m1.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/3_8900-ltauirsli1v.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/4_8485-v12emnxs5x0.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/5_5002-4fcxj41tmbf.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/6_2342-5dff4j1bcsw.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/7_9786-ln4d2pkv0ls.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/8_3772-dnx05ake2js.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/9_3352-vq5qrckaj5k.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/10_8536-rvkdgoizgox.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/11_6031-nt0drpmed14.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/12_9827-kew3lgk4aim.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/13_6487-fcjkpu1f355.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/14_4581-ax30blnr0xu.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/15_3430-xmi5r4notbc.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/16_5915-0bsoxfv1ydm.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/17_6010-oht0f3xboga.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/18_7711-dexlbu1hthj.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/19_9168-brv2cyuz1nr.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/20_6010-gijd2kdwvzc.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/21_5688-wymliz4i0cw.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/22_2798-zotii2eq5le.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/23_3427-sq5mqer4eed.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/24_3238-m2bbbx5ztay.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/25_6596-gj4gspiixbk.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/26_4045-3iyz412rx32.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/27_1441-xt3euls53yr.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/28_7470-wm5fppq2bc4.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/29_6561-0giimfvvbxg.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/30_8651-gfialo1s4cg.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/31_2810-qtsn4yzonzu.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/32_7865-1ykls4mn3qp.jpg', 'https://pic.manhuayuedu.com/4343/huo_xing_yi_chong/110094/33_3605-jbm01chv3vu.jpg']
第二话 种的突变: 100%|██████████| 33/33 [00:58<00:00,  1.76s/it]

函数4

  代码如下,其中的注释解释了更具体的细节,这里不再赘述。

def download(file_name, resource):
    '''------一个资源保存在本地的具体过程------\n
    file_name:保存文件名\n
    resource:资源'''
    with open(file_name, 'wb') as file:
        file.write(resource)

  该函数比较简单,只有文件的写入操作,不再测试。

函数5

  代码如下,其中的注释解释了更具体的细节,这里不再赘述。

def back_tracking(abnormal_file=ABNORMAL_SAVE_PATH, header=HEADER):
    '''---------对异常url重新访问,补录未下载的资源-----------\n
    ----------对于漫画默认为第几话或第几话的某些图访问异常---------\n
    abnormal_file:保存异常url的文件,默认值\n
    header:请求头,默认值'''
    print('#---------------开始对异常url重新访问---------------#')
    rest_urls = list()
    # 存储新异常url
    with open(abnormal_file, 'w') as file:
        abnorm_urls = file.readlines()
        for info_url in tqdm(abnorm_urls, desc='异常url访问进度', colour='blue'):
            # 遍历每个异常url
            url = info_url.split(':')[-1]
            info = info_url.replace(':' + url, '').split(':')
            try:
                response = requests.get(url=url, headers=header, timeout=TIMEOUT)
            except Exception:
                rest_urls.append(info_url)
                print('\n#--------仍有异常!已重新保存异常!运行结束后请手动查看!休息10秒--------#')
                sleep(10)
            else:
                sleep(SLEEP_TIME)
                if len(info) == 2:
                    # info = [comic_name, chapter_name],这与前面存储异常对应
                    text = etree.HTML(response.content)
                    resource_urls = text.xpath(MATCH_RESOURCE_URL)
                    for num, res_url in resource_urls:
                        try:
                            resource = requests.get(url=res_url, headers=header, timeout=TIMEOUT)
                        except Exception:
                            print('\n#------------资源下载失败!已保存异常及相关信息!休息10秒-----------#')
                            rest_urls.append(info[0] + ':' + info[1] + ':' + str(num) + ':' + res_url + '\n')
                            sleep(10)
                        else:
                            file_name = DOWNLOAD_DIR  + '/' + info[0] + '/' + info[1] + str(num) + FORMAT
                            get = threading.Thread(target=download, kwargs={'file_name': file_name, 'resource': resource.content}, daemon=True)
                            get.start()
                            get.join(timeout=20)
                            sleep(SLEEP_TIME)
                elif len(info) == 3:
                    # info = [comic_name, chapter_name, resource_index]
                    file_name = DOWNLOAD_DIR  + '/' + info[0] + '/' + info[1] + str(info[2]) + FORMAT
                    get = threading.Thread(target=download, kwargs={'file_name': file_name, 'resource': response.content}, daemon=True)
                    get.start()
                    get.join(timeout=20)
                    sleep(SLEEP_TIME)
                else:
                    rest_urls.append(info_url)
                    print('此url超过本文件的处理范围!已重新保存!请在运行结束后查看此url指向何种资源并自行处理!')
    save_urls(rest_urls, len(rest_urls), save_path=ABNORMAL_SAVE_PATH)
    # 保存新出现的异常url

  该函数与函数1、2、3大同小异,只是从文件读取url然后进行同样的爬取操作,因此也不再测试。

源码

  获取完整项目请点击:源码

创作不易,如果有所帮助,求支持,谢谢!

图


  1. 代码中出现的字符全部大写的变量均为全局变量,代表环境参数,即到了读者的设备上依据环境可以自行更改的参数。 ↩︎

  2. 注意:直接复制函数不能得到测试结果,测试只是验证函数功能的有效性,测试结果均来自测试代码,测试代码除了调用展示的函数外,还修改了相应参数并对匹配结果做了其他修正操作。 ↩︎

  • 22
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值