python多线程爬虫下载wallhaven超清壁纸

2023年 7月 31日 29.7k 0

9-wallhaven-exwgw8.png

大家好我是前端少年汪!痴迷技术,对programming有着极大的兴趣和爱好。
从明天起,做一个新思维的人 继承,多态,层层封装 从明天起,不再关心内存管理 让每一条数据,自动放到合适的位子上 从明天起,我将为每一个对象 取一个温暖的名字 它们用驼峰命名,优雅,大方 陌生人,我也祝福你哈 愿你不再为系统级bug烦恼 愿你在平台之间肆意游荡 愿你不再关心溢出与异常

爬取的目标网站:wallhaven.cc/

image.png

使用到的库:

  • requests
  • Xpath

分析目标网站的DOM结构

我们可以看到这个网站总共分为六个大的模块:Latest,Hot,Toplist,Random,Upload,Forums
我爬取的主要是latest,hot,toplist,random这四个模块的图片.
这四个模块对应的url网址分别为:

  • wallhaven.cc/latest
  • wallhaven.cc/hot
  • wallhaven.cc/toplist
  • wallhaven.cc/random

图片的预览页面
image.png

这里可以看到page=2的页面总共🈶️24张图片,这个只是缩略图,点击单个item项就可以进入到图片的详情页,可以看到对应的图片的详情页面的url在section>ul>li>figure>a标签的href属性中。

图片的详情页
image.png

可以看到真正的图片的url是存在于img标签的src属性,只要我们拿到这个图片的地址就可以下载图片到本地了。

梳理一下逻辑:

  • 请求latest,hot,toplist,random四个模块中的任意一个页面获取到缩略图的a标签里面的url,保存到一个列表里面
  • 遍历缩略图列表,获取详情页img标签的src图片地址
  • 请求图片地址,下载保存到本地
  • 大概的逻辑就是这么简单

    代码实现

    导入相关包,定义全局变量

    import os
    import threading
    
    import requests
    from lxml import etree
    
    """
    爬取网站:https://wallhaven.cc/
    """
    # 伪装浏览器请求
    headers = {
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36'
    }
    # 存放下载图片的文件夹名称
    new_folder = 'img'
    # 5个模块对应的字符串列表
    type_list = ['hot', 'toplist', 'latest', 'random']
    # 线程池
    threads = []
    

    获取缩略图页面

    # 单页下载,只下载一页24张图片
    def get_wall_one_page(type_index: int, page_num) -> None:
        # page_num爬取的页码
        url = 'https://wallhaven.cc/{}?page={}'.format(type_list[type_index], page_num)
        print(url)
        # 获取当前目录
        current_dir = os.getcwd()
    
        # 新文件夹的名称
        # new_folder = 'img_{}'.format(type_list[type_index])  # 创建新文件夹
        if not os.path.exists(new_folder):
            os.mkdir(os.path.join(current_dir, new_folder))
    
        res = requests.get(url, headers=headers).text
        selector = etree.HTML(res)
        img_urls = selector.xpath('//a[@class='preview']/@href')
        # 调用获取详情页方法
        detail_img(img_urls)
    

    获取图片详情页面

    def detail_img(img_urls):
        """
        图片详情
        :param img_urls:获取到单个图片item的详情url
        :return:
        """
        i = 0
    
        for item in img_urls:
            son_res = requests.get(item, headers=headers).text
            selector = etree.HTML(son_res)
            src_arr = selector.xpath('//img[@id='wallpaper']/@src')
            # 使用多线程下载图片,调用下载方法
            t = threading.Thread(target=download_img, args=(src_arr[0], i))
            t.start()
            threads.append(t)
            # download_img(src_arr[0], i)
    
            i += 1
            # 等待所有线程完成
        for t in threads:
            t.join()
        print("当前页所有图片下载完成")
    

    下载图片

    def download_img(img_src, i):
        """
        图片下载
        :param img_src: 图片的src
        :param i: 序号
        :return: void
        """
        with open('./{}/{}-{}'.format(new_folder, i, img_src.split('/')[-1]), 'wb') as f:
            f.write(requests.get(img_src).content)
        print(i, img_src)
    

    以上就是爬取单页(一页24张图片的方法),观察url可以得知,通过改变page这个参数就可以不断爬取不同页码的图片了。可以有2种不同的思路:

  • 爬取所有页,从1到total。有多少就爬多少
  • 输入指定页码范围,爬取指定多少页
  • 如果是第一种的话,我们还需要获取到total总页码数。因为页码是懒加载的,鼠标滑动到底部才会请求加载下一页,这个时候才能看到总页码。
    我想到了两种思路:

  • 直接请求第二页。如果存在的话,就获取到总页码,如果不存在第二页说明这个只有一页,返回1。
  • 使用selenium库,控制鼠标滑动页码到底部,加载下一页,然后获取总页码。
  • 我感觉两种方法都差不多,就用简单一点的第一种方法

    获取总页码

    
    def get_num(type_index):
        """ 获取总页码
        :param type_index:爬取的类型索引
        :return:当前的页码数量
        """
        url = 'https://wallhaven.cc/{}?page=2'.format(type_list[type_index])
        selector = etree.HTML(requests.get(url, headers=headers).text)
        page_num = selector.xpath('//header[@class='thumb-listing-page-header']/h2//text()')
        return ''.join(page_num).split(' ')[-1]
    

    爬取指定类型,页码范围or爬取全部页

    循环起始页到最后页,无非就是在爬取单页的基础上再多一层循环。
    如果不传入开始页码和最后页码的话,那开始页码就是第一页,总页码就是当前页面最后页码

    
    def get_total(type_index, start, end):
        """
        爬取指定类型,页码范围
        :param type_index:
        :param start:
        :param end:
        :return:
        """
        # total = int(get_num(type_index))
        if end is None:
        	end = total
        if start is None:
            start = 1
        try:
            for i in range(start, end):
                url = 'https://wallhaven.cc/{}?page={}'.format(type_list[type_index], i)
                print(url, '开始下载第{}/{}页'.format(i, end - 1))
                res = requests.get(url, headers=headers).text
                selector = etree.HTML(res)
                img_urls = selector.xpath('//a[@class='preview']/@href')
                detail_img(img_urls)
                # threading.Thread(target=detail_img, args=(img_urls)).start()
        except Exception as e:
            print(e)
    
    
    

    这是获取到的图片结果
    image.png

    最后是所有代码

    import os
    import threading
    
    import requests
    from lxml import etree
    
    """
    爬取网站:https://wallhaven.cc/
    """
    # 伪装浏览器请求
    headers = {
        'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36'
    }
    # 存放下载图片的文件夹名称
    new_folder = 'img'
    # 5个模块对应的字符串列表
    type_list = ['hot', 'toplist', 'latest', 'random']
    # 线程池
    threads = []
    
    
    # 单页下载,只下载一页24张图片
    def get_wall_one_page(type_index: int, page_num) -> None:
        # page_num爬取的页码
        url = 'https://wallhaven.cc/{}?page={}'.format(type_list[type_index], page_num)
        print(url)
        # 获取当前目录
        current_dir = os.getcwd()
    
        # 新文件夹的名称
        # new_folder = 'img_{}'.format(type_list[type_index])  # 创建新文件夹
        if not os.path.exists(new_folder):
            os.mkdir(os.path.join(current_dir, new_folder))
    
        res = requests.get(url, headers=headers).text
        selector = etree.HTML(res)
        img_urls = selector.xpath('//a[@class='preview']/@href')
        detail_img(img_urls)
    
    
    def detail_img(img_urls):
        """
        图片详情
        :param img_urls:获取到单个图片item的详情url
        :return:
        """
        i = 0
    
        for item in img_urls:
            son_res = requests.get(item, headers=headers).text
            selector = etree.HTML(son_res)
            src_arr = selector.xpath('//img[@id='wallpaper']/@src')
            # 使用多线程下载图片
            t = threading.Thread(target=download_img, args=(src_arr[0], i))
            t.start()
            threads.append(t)
            # download_img(src_arr[0], i)
    
            i += 1
            # 等待所有线程完成
        for t in threads:
            t.join()
        print("当前页所有图片下载完成")
    
    
    def download_img(img_src, i):
        """
        图片下载
        :param img_src: 图片的src
        :param i: 序号
        :return: void
        """
        with open('./{}/{}-{}'.format(new_folder, i, img_src.split('/')[-1]), 'wb') as f:
            f.write(requests.get(img_src).content)
        print(i, img_src)
    
    
    def get_num(type_index):
        """ 获取总页码
        :param type_index:爬取的类型索引
        :return:当前的页码数量
        """
        url = 'https://wallhaven.cc/{}?page=2'.format(type_list[type_index])
        selector = etree.HTML(requests.get(url, headers=headers).text)
        page_num = selector.xpath('//header[@class='thumb-listing-page-header']/h2//text()')
        return ''.join(page_num).split(' ')[-1]
    
    
    def get_total(type_index, start, end):
        """
        爬取指定类型,页码范围
        :param type_index:
        :param start:
        :param end:
        :return:
        """
        # total = int(get_num(type_index))
        if end is None:
        	end = total
        if start is None:
            start = 1
        try:
            for i in range(start, end):
                url = 'https://wallhaven.cc/{}?page={}'.format(type_list[type_index], i)
                print(url, '开始下载第{}/{}页'.format(i, end - 1))
                res = requests.get(url, headers=headers).text
                selector = etree.HTML(res)
                img_urls = selector.xpath('//a[@class='preview']/@href')
                detail_img(img_urls)
                # threading.Thread(target=detail_img, args=(img_urls)).start()
        except Exception as e:
            print(e)
    
    
    # https://wallhaven.cc/search?q=code&page=1
    # todo 根据输入关键词,查找下载
    
    if __name__ == '__main__':
        """
        0:'hot', 热榜
        1:'toplist', top排名
        2:'latest', 最新
        3:'random' 随机
        4:'search' 关键字查找
        """
        get_wall_one_page(1,1)  # 爬取单页
        # get_total(2, 1, 11)  # 爬取多个指定页码
    
    

    上面代码还有优化的空间,还可以加入关键词查询爬取,还有标签爬取,条件爬取等等,欢迎评论不同意见

    想要壁纸的也可以私信我发你!
    image.png

    0-wallhaven-d6w2dj.png
    6-wallhaven-7prmdv.jpg

    喜欢我的小伙伴可以点一波关注。点个赞, 欢迎评论区留言讨论,看到会回复.

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论