资讯专栏INFORMATION COLUMN

从0开始写一个多线程爬虫(2)

yangrd / 460人阅读

摘要:继续看上一段循环的代码,是遍历,将已经挂了的线程去除掉,那么在这个中线程什么情况下会死掉就是类中的方法中的这段代码如果为空会循环,此时对应的线程会死掉。此时主函数的循环将死掉的线程去除,在线程数不足个的情况下,接下来的循环继续制造新的线程。

上一篇文章: 从0开始写一个多线程爬虫(1)


我们用继承Thread类的方式来改造多线程爬虫,其实主要就是把上一篇文章的代码写到线程类的run方法中,代码如下:

import re
import requests
from threading import Thread


class BtdxMovie(Thread):
    
    # 初始化时传入3个list,含义见上文,并为当前线程取个名字
    def __init__(self, total_url_list, used_url_list, movie_url_list, thread_name="MyThread"):
        super(BtdxMovie, self).__init__()
        self.all_url = total_url_list
        self.used_url = used_url_list
        self.movie_url = movie_url_list
        self.name = thread_name

    def run(self):
        while 1:
            # 从all_url中获取第一条url,如果all_url为空则break,这会导致线程死掉(is_alive()为False)
            try:
                url = self.all_url.pop(0)
            except IndexError:
                break
            # 如果url是电影详情页,则将其加入到movie_url中
            if re.match("https://www.btdx8.com/torrent/.*?html", url):
                    if url not in self.movie_url:
                        self.movie_url.append(url)
            try:
                html = requests.get(url).text
                new_url = re.findall("href="(https://.*?)"", html)
                for u in new_url:
                    # 只要同一个域名下的url
                    if not re.match("https://.*?btdx8.com", u):
                        continue
                    # "#"在url中是代表网页位置的,这里处理一下,避免url重复
                    if "#" in u:
                        u = u.split("#")[0]
                    if u in self.used_url or u in self.all_url:
                        continue
                    self.all_url.append(u)
            except:
                pass
            self.used_url.append(url)
            # 每次循环打印当前线程id和各个list的长度
            curr_thread = "[{}]".format(self.name)
            info = "ALL: {}, USED: {}, MOV: {}".format(len(self.all_url), len(self.used_url), len(self.movie_url))
            print(curr_thread + ": " + info)

此时线程类就已经写好了,接下来要做的就是生成多个实例,并开启线程,继续追加如下代码:

# 网站首页
base_url = r"https://www.btdx8.com/"

# 爬取到的新url会继续加入到这个list里
total_url_list = [base_url]
# 存放已经爬取过的url
used_url_list = []
# 存放是电影详情页的url
movie_url_list = []

# 存入线程对象的list
thread_list = []
thread_id = 0

while total_url_list or thread_list:
    for t in thread_list:
        if not t.is_alive():
            thread_list.remove(t)
    while len(thread_list) < 5 and total_url_list:
        thread_id += 1
        thread_name = "Thread-{}".format(str(thread_id).zfill(2))
        t = BtdxMovie(total_url_list, used_url_list, movie_url_list, thread_name)
        t.start()
        thread_list.append(t)

此时运行脚本,就可以以多线程的方式抓取url了,运行之后print的信息如下:

[Thread-04]: ALL: 2482, USED: 84, MOV: 55
[Thread-01]: ALL: 2511, USED: 85, MOV: 56
[Thread-02]: ALL: 2518, USED: 86, MOV: 57
[Thread-05]: ALL: 2555, USED: 87, MOV: 58
[Thread-03]: ALL: 2587, USED: 88, MOV: 59
[Thread-01]: ALL: 2595, USED: 89, MOV: 60
[Thread-04]: ALL: 2614, USED: 90, MOV: 61
[Thread-05]: ALL: 2644, USED: 91, MOV: 62
[Thread-03]: ALL: 2686, USED: 92, MOV: 63

我们来解释一下while循环里的代码,先看内嵌的while循环,是当total_url_list不为空,并且thread_list长度小于5的时候执行,利用thread_id获得thread_name,实例化一个线程实例t,并用t.start()开启线程,然后将其加入到thread_list中,因此很容易可以理解这段代码,就是确保当前运行的线程数为5,并且给每个新线程一个从1开始自增长的id
继续看上一段for循环的代码,是遍历thread_list,将已经挂了的线程去除掉,那么在这个case中线程什么情况下会死掉?就是BtdxMovie类中的run方法中的这段代码:

            try:
                url = self.all_url.pop(0)
            except IndexError:
                break

如果all_url为空会break循环,此时对应的线程会死掉。这里可能很容易误以为所有的url都已经爬取完了导致线程退出,实际上,目前的代码没有对爬取的url深度做控制,可能永远都不会爬完,当all_url为空时候,很大可能是all_url里的url被线程取走了,但还没来得及把爬取到新的url加入到all_url中,所以很容易理解这种情况会在程序刚开始运行的时候发生,因为一开始all_url中只有一个url,被第一个线程取走,在第一个线程还没返回结果的时候,后续的线程去取url都会导致循环break,然后线程死掉。此时主函数的for循环将死掉的线程去除,在线程数不足5个的情况下,接下来的while循环继续制造新的线程。
那么外层的while循环的条件也很容易就明白了,不能在total_url_list为空的时候退出,要在total_url_listthread_list都为空的时候才能退出。如果就是在total_url_list为空的时候退出会发生什么?程序会在第一个url被取走导致total_url_list为空的时候退出循环并结束吗?严格来说是的,我们可以在程序的末尾加入一个print语句,就可以验证修改while条件之后,while循环就退出了,但这个时候是主线程结束了,新增的线程并没有结束,此时还有一个线程在不断的运行和爬取url,这个线程就是获取了第一个url的线程,线程可以设置成随主线程一起停止,也可以让主线程挂起等待其余线程运行完成,默认情况下是我们这种,主线程运行完成并停止,而其余线程继续运行。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/42294.html

相关文章

  • 0开始一个线程爬虫(1)

    摘要:最近发现有个电影下载网站叫做比特大雄,下了几部电影之后,打算写个爬虫把网站的电影信息都爬取下来。结果我就发现,速度太慢了因为决定将其改成多线程爬虫,欢迎继续阅读后续的此系列文章。 最近发现有个电影下载网站叫做比特大雄,下了几部电影之后,打算写个爬虫把网站的电影信息都爬取下来。 一开始思路是这样的,从首页开始,解析首页的所有链接,如果这个链接是电影详情页的链接,就将其html解析成想要...

    imccl 评论0 收藏0
  • Python

    摘要:最近看前端都展开了几场而我大知乎最热语言还没有相关。有关书籍的介绍,大部分截取自是官方介绍。但从开始,标准库为我们提供了模块,它提供了和两个类,实现了对和的进一步抽象,对编写线程池进程池提供了直接的支持。 《流畅的python》阅读笔记 《流畅的python》是一本适合python进阶的书, 里面介绍的基本都是高级的python用法. 对于初学python的人来说, 基础大概也就够用了...

    dailybird 评论0 收藏0
  • 关于Python爬虫种类、法律、轮子的一二三

    摘要:一般用进程池维护,的设为数量。多线程爬虫多线程版本可以在单进程下进行异步采集,但线程间的切换开销也会随着线程数的增大而增大。异步协程爬虫引入了异步协程语法。 Welcome to the D-age 对于网络上的公开数据,理论上只要由服务端发送到前端都可以由爬虫获取到。但是Data-age时代的到来,数据是新的黄金,毫不夸张的说,数据是未来的一切。基于统计学数学模型的各种人工智能的出现...

    lscho 评论0 收藏0
  • Python协程(真才实学,想学的进来)

    摘要:所以与多线程相比,线程的数量越多,协程性能的优势越明显。值得一提的是,在此过程中,只有一个线程在执行,因此这与多线程的概念是不一样的。 真正有知识的人的成长过程,就像麦穗的成长过程:麦穗空的时候,麦子长得很快,麦穗骄傲地高高昂起,但是,麦穗成熟饱满时,它们开始谦虚,垂下麦芒。 ——蒙田《蒙田随笔全集》 上篇论述了关于python多线程是否是鸡肋的问题,得到了一些网友的认可,当然也有...

    lykops 评论0 收藏0
  • Python爬虫学习路线

    摘要:以下这些项目,你拿来学习学习练练手。当你每个步骤都能做到很优秀的时候,你应该考虑如何组合这四个步骤,使你的爬虫达到效率最高,也就是所谓的爬虫策略问题,爬虫策略学习不是一朝一夕的事情,建议多看看一些比较优秀的爬虫的设计方案,比如说。 (一)如何学习Python 学习Python大致可以分为以下几个阶段: 1.刚上手的时候肯定是先过一遍Python最基本的知识,比如说:变量、数据结构、语法...

    liaoyg8023 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<