当前位置:   article > 正文

python如何在网络爬虫程序中使用多线程(threading.Thread)_python多线程爬虫

python多线程爬虫

一、多线程的基础知识

关于多线程相关的基础知识,已经在另一篇文章中有过详细描述,此处不再赘述。有需要的可以参考:
Python并发编程之threading模块

要点主要是:

  • 使自己的类继承自threading.Thread
  • 在自己的类里重写run方法

二、在网络爬虫中使用多线程

2.1 从单线程版本入手

这里,我们仍然以下载单张图片的简单程序为例,首先,我们可以实现一个单线程版的图片下载器,代码范例可以从这篇文章中获取:
python使用requests库下载单张图片的简单示例

import requests
import random
import os

class PicDownloader():
    def __init__(self):
        self.count = 0
        self.file_name_prefix = 'img'
        self.working_dir = '.'
    
    def download_img(self, img_url, dir='.'):
        '''
        下载url指定的图片到本地dir目录
        '''
        self.count += 1
        file_name = self.file_name_prefix + '_' + str(self.count) + '.jpg'

        if os.path.isdir(dir):
            self.working_dir = dir
        else:
            try:
                os.makedirs(dir)
                self.working_dir = dir
            except OSError as e:
                print(f'create dir: {dir} failed')
        
        file_path = os.path.join(self.working_dir, file_name)
        print(f'开始下载文件:{file_path}')
        headers = self.get_headers()
        try:
            res = requests.get(url=img_url, headers=headers)
            if 200 == res.status_code:
                with open(file_path, 'wb') as file:
                    file.write(res.content)
        except Exception as e:
            print(f'下载文件失败: {file_path}')
            print(repr(e))
        print('文件下载成功')
        
    # 本例中实际不需要这么多
    def get_headers(self):
        user_agent_list = [
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
            "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
            "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
            "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
            "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"]

        userAgent = random.choice(user_agent_list)
        headers = {'User-Agent': userAgent}
        return headers


if __name__== '__main__':
    downloader = PicDownloader()
    downloader.download_img('https://images.weserv.nl/?url=https%3A%2F%2F23img.com%2Fi%2F2022%2F08%2F17%2Fqykvvx.jpg', 'd:/test')
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

2.2 将单线程版本改写为多线程版本

如何将其改造为多线程版本呢?

首先,导入threading模块,并使PicDownloader继承自threading.Thread, 构造函数中带入一些必要的参数,即可

class PicDownloader(threading.Thread):
    def __init__(self, name, img_url, file_name, dir='.'):
        threading.Thread.__init__(self)   # 注意,这一步是必须的,先对Thread进行相关初始化
        self.setName(name)                # 线程名是可选的,这里设置只是为了后面观察线程方便
        self.daemon = True                # 按需设置
        self.url = img_url
        self.file_name = file_name
        self.working_dir = dir
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

现在PicDownloader类就可以以线程的方式来运行了,我们还需要重写一下run()方法, 其实就是把原先的主要工作方法download_img()里面的内容挪到了run()方法内,你甚至可以理解为就是把download_img()重命名为run()方法。

def run(self):
    '''
    下载url指定的图片到本地dir目录
    '''
    if os.path.isdir(self.working_dir):
        pass
    else:
        try:
            os.makedirs(self.working_dir)
        except OSError as e:
            print(f'create dir: {dir} failed')
    
    if not self.file_name.endswith('.jpg'):
        self.file_name = self.file_name + '.jpg'
    
    file_path = os.path.join(self.working_dir, self.file_name)
    print(f'thread:{self.name} -> 开始下载文件:{file_path}')
    headers = self.get_headers()
    try:
        res = requests.get(url=self.url, headers=headers)
        if 200 == res.status_code:
            with open(file_path, 'wb') as file:
                file.write(res.content)
    except Exception as e:
        print(f'下载文件失败: {file_path}')
        print(repr(e))
    print(f'thread:{self.name} -> 文件下载成功:{file_path}')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

至此,多线程的改造基本结束。

完整的代码:

# coding=utf-8

import requests
import threading
import random
import os

class PicDownloader(threading.Thread):
    def __init__(self, name, img_url, file_name, dir='.'):
        threading.Thread.__init__(self)
        self.setName(name)
        self.daemon = True
        self.url = img_url
        self.file_name = file_name
        self.working_dir = dir
    
    def run(self):
        '''
        下载url指定的图片到本地dir目录
        '''
        if os.path.isdir(self.working_dir):
            pass
        else:
            try:
                os.makedirs(self.working_dir)
            except OSError as e:
                print(f'create dir: {dir} failed')
        
        if not self.file_name.endswith('.jpg'):
            self.file_name = self.file_name + '.jpg'
        
        file_path = os.path.join(self.working_dir, self.file_name)
        print(f'thread:{self.name} -> 开始下载文件:{file_path}')
        headers = self.get_headers()
        try:
            res = requests.get(url=self.url, headers=headers)
            if 200 == res.status_code:
                with open(file_path, 'wb') as file:
                    file.write(res.content)
        except Exception as e:
            print(f'下载文件失败: {file_path}')
            print(repr(e))
        print(f'thread:{self.name} -> 文件下载成功:{file_path}')
        
    def get_headers(self):
        user_agent_list = [
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
            "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
            "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
            "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
            "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
            "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"]

        userAgent = random.choice(user_agent_list)
        headers = {'User-Agent': userAgent}
        return headers


if __name__== '__main__':
    downloader = PicDownloader(img_url='https://images.weserv.nl/?url=https%3A%2F%2F23img.com%2Fi%2F2022%2F08%2F17%2Fqykvvx.jpg', file_name='1', dir='d:/test')
    downloader.start()
    downloader.join()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

2.3 运行多线程版本程序

多线程版本的类class PicDownloader(threading.Thread)改造完成后,我们只需要调用start()方法就可以启动线程。例如:

if __name__== '__main__':
    downloader = PicDownloader(img_url='https://images.weserv.nl/?url=https%3A%2F%2F23img.com%2Fi%2F2022%2F08%2F17%2Fqykvvx.jpg', file_name='1', dir='d:/test')
    downloader.start()
    downloader.join()
  • 1
  • 2
  • 3
  • 4
  • start()
    此方法会在一个单独的控制线程中调用run()方法,启动线程。此方法只能调用一次。

  • join([timeout])
    等待直到线程终止或者出现超时为止。timeout是一个浮点数,用于指定以秒为单位的超时时间。必须在start()方法之后调用,如果在线程启动之前就连接它将出现错误。

2.4 将多线程应用到爬虫程序中

以上程序单独执行,即下载单张图片,还不能体现多线程的优势。结合爬虫程序,我们看看如何运用多线程版本的图片下载器。

假设一个最简单的场景,就是我们想从一个页面上,爬取该页面上所有的图片。

当然了,这前期有一些工作要做,比如将所有的图片url都解析出来(本例中存到了列表img_url_list中),那么之后就可以发挥出多线程版本图片下载器的威力了。示例代码如下:

count = 0
path = 'd:/test'
pic_download_threads = []
for img_url in img_url_list:
    count += 1
    file_name = 'img_' + str(count)
    pic_download_threads.append(PicDownloader(str(count), img_url, file_name, path))

for working_thread in pic_download_threads:
    working_thread.start()

for working_thread in pic_download_threads:
    working_thread.join(5)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

即使img_url_list里只有十几张图片的url,多线程版本也比单线程版本要快的多。如果图片url数目更多的话,速度优势就更明显了。

三、考虑使用线程池

上面是常规的多线程处理方式,如果img_url_list里只有十几张图片的url,多线程版本将比单线程版本要快的多。

但我们回过头来考虑以上程序,假如img_url_list里面的成员数非常多的话,比如该页面可能有数百张图片需要下载时,此时会发生什么?如果像上面这么做,我们将启动数百个工作线程!!!而系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。并且,当系统中包含有大量的并发线程时,会导致系统性能急剧下降,甚至导致 Python 解释器崩溃。

在这种情况下,使用线程池就可以很好地提升性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。

关于线程池的使用,请移步我的另一篇博文:《在Python网络爬虫程序中使用线程池》

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/189669
推荐阅读
相关标签
  

闽ICP备14008679号