可长时间爬取大量图片的Python爬虫

该爬虫功能如下:

        1.可对网站进行进行翻页操作,并搜索获取与关键字匹配的链接。

        2.对网页图片搜索并下载下来。

        3.对下载下来的图片可使用文件格式过滤或者文件大小过滤。

        4.对于当前下载的图片会创建名为'images'的文件夹,稍后以 “关键字” + “_”  + 任务ID的形式命名新文件夹。

        5.在搜索链接完毕后会创建以pkl为后缀,关键字为名称的文件用于保存任务,当用户在控制台输入相同的关键字时,便会进行搜索。若搜索到该本件便会询问用户是否加载,若否,程序也并不会直接删除pkl文件而是在第二次搜索链接完毕后直接覆盖。当所有任务完成时(包括由于超过重试次数而跳过的链接),会删除pkl文件。

该爬虫需要注意的地方:

        1.该爬虫最初编写的时候只是想要能下载某一网页的图片,后面加需求再变成这样的,有些地方可能看起来写的莫名奇妙。

        2.该爬虫没有设置访问频率,需要自行添加。(可以直接import time,然后time.sleep()就行了,笔者以高频率爬取了某网站4万张图片,成功被网站拉黑了)

        3.如果出现特殊情况,某一任务未成功下载,可将pkl文件的workPoint重写。(该代码并不会删除pkl文件中的任何链接,只是会修改workPoint)

        4.用到了外部库,可以用一下命令进行安装。

        pip install beautifulsoup4

        pip install requests

        5.虽然说是可以下载大量图片,但是几十万张图片及以上的量该爬虫应该是不能够胜任的。个人认为代码运行时间越长就越应该考虑各种各样的情况,但该代码并不能处理这些可能的情况(当然一般人也不会需要爬取这么多图片,需要爬这么多图片的人也看不上咱这代码。。。)

        6.该爬虫并不能处理动态访问的网站。

        7.该代码重新加载pkl文件后,对于上一次下载遗留下来images文件夹,处理里面的内容的做法并不是接着上一个图片去下载,而是删除images文件夹中的所有内容然后重头开始下载。

        8.过滤的图片的手段并不是直接在爬取前识别文件,而是下载下来后识别再删除。

        9.对于不同的网站搜索链接部分的代码可能需要不同程度上的修改。

import os
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
from base64 import b64decode
import pickle

#根网址
Baseurl = 'https://hd.wve7n.com/2046/'
#初始页面/当前页面
nowDepth = 80
#存储所有链接的列表
targeturl_list = []
#对完成的文件夹计数
haveDownloaded = 0
#对于失败请求,最多重试5次
trytimes = 5 
#网站响应时间
waittime = 8
#标记当前处理到列表的哪一个元素
workPoint = 0
#总任务数
totalTask = 0
#用于存档文件
orginal_list = []

#用于获取关键字匹配的链接
def SearchSection(targetword, pageDepth):
    global nowDepth
    global targeturl_list
    global totalTask
    global orginal_list
    while nowDepth <= pageDepth:
        url = f'https://hd.wve7n.com/2046/thread.php?fid=278&page={nowDepth}'
        # 发送HTTP请求获取网页内容
        isresponse = False
        times = 0
        while not isresponse:
            try:
                response = requests.get(url, timeout=waittime)
                print(f"第{nowDepth}页网页成功响应")
                isresponse = True
            except requests.exceptions.RequestException as e:
                times += 1
                print(f'请求异常第{times}次:{e}')
                if times >= trytimes:
                    print(f'请求失败,已达到最大重试次数')
                    break

        #当5次请求都失败时,直接跳过该页
        if not isresponse:
            nowDepth += 1
            continue

        #确保请求成功
        if response.status_code == 200:
            #使用BeautifulSoup解析网页内容
            soup = BeautifulSoup(response.text, 'html.parser')
            #统计找到的链接数
            thisurl = 0
            #查找所有<a>标签
            for link in soup.find_all('a'):
                #获取链接的href属性,即链接地址
                href = link.get('href')
                #获取链接的文本内容
                text = link.get_text()
                #检查链接文本中是否包含特定关键词
                if targetword in text:
                    thisurl += 1
                    print(f'找到包含{targetword}的链接:{Baseurl+href}')
                    targeturl_list.append(Baseurl+href)

        else:
            print('网页请求获取失败')

        if isresponse == True:
            print(f'第{nowDepth}页url搜索完毕')

        if thisurl == 0:
            print(f"该页未发现包含“{targetword}”的链接")

        #页面搜索完毕,准备进入下一页
        nowDepth += 1

    print(targeturl_list)

    print(f'检索到{targeturl_list.__len__()}个包含{targetword}的链接')

    #创建链接任务列表的副本用于存档和方便后续代码编写
    orginal_list = targeturl_list.copy()
    totalTask = targeturl_list.__len__()

    #保存列表
    with open(f'{targetword}_task.pkl', 'wb') as f:
        pickle.dump((orginal_list, workPoint), f)

def DownloadSection(filtertype, targetword):
    global workPoint
    global haveDownloaded
    global workPoint
    global totalTask
    global orginal_list
    
    for eachurl in targeturl_list:
    #目标网站URL
        url = eachurl

        #请求头
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
        }

        isresponse = False
        times = 0
        while not isresponse:
            try:
                #发送HTTP请求
                response = requests.get(url, headers=headers, timeout=waittime)
                isresponse = True
            except requests.exceptions.RequestException as e:
                times += 1
                print(f'请求异常第{times}次:{e}')
                if times >= trytimes:
                    print(f'请求失败,已达到最大重试次数')
                    break

        #确保请求成功
        if not isresponse:
            print('跳过当前url地址')
            continue

        if os.path.exists('images') == False:
            os.mkdir('images')
        #检查请求是否成功
        if response.status_code == 200:

            picnum = 0
            #使用BeautifulSoup解析HTML内容
            soup = BeautifulSoup(response.text, 'html.parser')
            
            #查找所有的<img>标签
            img_tags = soup.find_all('img')
            
            for img in img_tags:
                #获取<img>标签的src属性
                img_url = img.get('src')
                
                #如果src属性为相对路径,将其转换为绝对路径
                if not img_url.startswith(('http:', 'https:', 'data:')):
                    img_url = urljoin(url, img_url)
                
                #处理数据 URI
                if img_url.startswith('data:'):
                    isresponse = False
                    times = 0
                    while not isresponse:
                        try:
                            #从数据 URI 中提取 base64 编码的图片数据
                            _, img_data = img_url.split(',', 1)
                            #解码 base64 图片数据
                            img_data = b64decode(img_data)
                            #生成图片名称
                            img_name = 'image_base64.png'
                            isresponse = True
                            picnum += 1
                            print(f"图片下载进度:({picnum}/{img_tags.__len__()})")
                        except Exception:
                            times += 1
                            print(f"下载图片失败:{img_url}(第{times}次)")
                            if times >= trytimes:
                                print(f'无法下载图片,已达到最大重试次数')
                                break
                
                else:
                    isresponse = False
                    times = 0
                    while not isresponse:
                        try:
                            #获取图片的二进制内容
                            img_data = requests.get(img_url, headers=headers, timeout=10).content
                            #获取图片名称
                            img_name = os.path.basename(img_url)
                            isresponse = True
                            picnum += 1
                            print(f"图片下载进度:({picnum}/{img_tags.__len__()})")
                        except Exception:
                            times += 1
                            print(f"下载图片失败:{img_url}(第{times}次)")
                            if times >= trytimes:
                                print(f'无法下载图片,已达到最大重试次数')
                                break
                #检查是否获取到图片数据
                if isresponse == False:
                    continue
                #保存图片
                with open(f'images/{img_name}', 'wb') as f:
                    f.write(img_data)
                    print(f"图片已保存:{img_name}")
        else:
            print(f"请求失败,状态码:{response.status_code}")

        #多余的杂图删除
        path = 'images'
        #用于根据文件格式过滤图片
        extensions = ('png', 'gif')
        #统计删除图片数
        removenum = 0

        #根据文件格式删除
        if filtertype == '1':
            for filename in os.listdir(path):

                file_path = os.path.join(path, filename)

                if os.path.isfile(file_path) and filename.endswith(extensions):
                    
                    os.remove(file_path)

                    removenum += 1

                    print(f"已删除文件:{filename}")

        #根据文件大小删除
        else:
            for filename in os.listdir(path):

                file_path = os.path.join(path, filename)
                #硬编码:当文件大小小于100k时,删除该文件
                if os.path.isfile(file_path) and os.path.getsize(file_path) < 102400:

                    os.remove(file_path)

                    removenum += 1

                    print(f"已删除文件:{filename}")
        
        print(f"已删除文件数量:{removenum}")

        print(f"实际保留有效图片数:{img_tags.__len__() - removenum}")

        #依据任务进度,重命名文件夹
        haveDownloaded += 1
        workPoint = haveDownloaded
        new_name = targetword + '_' + f'{haveDownloaded}'

        if os.path.exists(path):
            os.rename(path, new_name)
            print(f"文件夹已重命名为“{new_name}”")
        else:
            print("文件夹不存在")

        print(f"{url}中图片下载完成")
        #统计下载情况
        #刷新任务文件
        with open(f'{targetword}_task.pkl', 'wb') as f:
            pickle.dump((orginal_list, workPoint), f)

        print(f"任务进程情况({haveDownloaded}/{totalTask})")
        print(f"已完成{int(haveDownloaded / totalTask * 100_00)/100}%")
        print("--------------------------------------")
    
    if os.path.exists(f'{targetword}_task.pkl'):
        
        os.remove(f'{targetword}_task.pkl')

if __name__ == '__main__':
    # 获取关键词
    targetword = input('请输入关键词:')    
    asking = '0'
    if os.path.exists(f'{targetword}_task.pkl'):

        asking = input('检测到已存在任务文件,是否加载?\n1:是;\n2:否') 

        #加载任务文件事件    
        if asking == '1':
            #图片过滤方式
            filterType = input('请输入图片过滤方式:\n1:文件格式过滤;\n2:文件大小过滤')

            with open(f'{targetword}_task.pkl', 'rb') as f:

                targeturl_list, workPoint = pickle.load(f)

            totalTask = targeturl_list.__len__()

            print(f"任务进程情况({workPoint}/{totalTask})")
            print(f"上次已完成{int(workPoint / totalTask * 100_00)/100}%")

            if workPoint >= 1:
                targeturl_list = targeturl_list[workPoint:]
            #统一数值
            haveDownloaded = workPoint

            if os.path.exists('images') and os.path.isdir('images'):
                for filename in os.listdir('images'):
                    file_path = os.path.join('images', filename)
                    try:
                        # 如果是文件,则删除
                        if os.path.isfile(file_path) or os.path.islink(file_path):
                            os.unlink(file_path)
                        # 如果是文件夹,则递归删除
                        elif os.path.isdir(file_path):
                            os.rmdir(file_path)
                            
                    except Exception :
                        print(f"删除文件夹'images' 中的文件或子文件夹时出错")

            DownloadSection(filtertype=filterType, targetword=targetword)
        #不加载任务文件事件
        elif asking == '2':

            #最终页面
            pageDepth = int(input('请输入搜索页数:'))
            #图片过滤方式
            filterType = input('请输入图片过滤方式:\n1:文件格式过滤;\n2:文件大小过滤')

            targeturl_list = []

            SearchSection(targetword=targetword, pageDepth=pageDepth)
            DownloadSection(filtertype=filterType, targetword=targetword)

        else:
            pass
    #无任务文件事件
    if asking == '0':

        #最终页面
        pageDepth = int(input('请输入搜索页数:'))
        #图片过滤方式
        filterType = input('请输入图片过滤方式:\n1:文件格式过滤;\n2:文件大小过滤')
        SearchSection(targetword=targetword, pageDepth=pageDepth)
        DownloadSection(filtertype=filterType, targetword=targetword)
        

 (主要是做做分享。。。)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值