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

模式棱镜
• 阅读 2079

上一篇文章: 从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的线程,线程可以设置成随主线程一起停止,也可以让主线程挂起等待其余线程运行完成,默认情况下是我们这种,主线程运行完成并停止,而其余线程继续运行。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Stella981 Stella981
3年前
SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)
首先在shiro配置类中注入rememberMe管理器!复制代码(https://oscimg.oschina.net/oscnet/675f5689159acfa2c39c91f4df40a00ce0f.gif)/cookie对象;rememberMeCookie()方法是设置Cookie的生成模
Stella981 Stella981
3年前
Linux查看GPU信息和使用情况
1、Linux查看显卡信息:lspci|grepivga2、使用nvidiaGPU可以:lspci|grepinvidia!(https://oscimg.oschina.net/oscnet/36e7c7382fa9fe49068e7e5f8825bc67a17.png)前边的序号"00:0f.0"是显卡的代
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Easter79 Easter79
3年前
SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)
首先在shiro配置类中注入rememberMe管理器!复制代码(https://oscimg.oschina.net/oscnet/675f5689159acfa2c39c91f4df40a00ce0f.gif)/cookie对象;rememberMeCookie()方法是设置Cookie的生成模
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(