python爬虫用多线程还是多进程_爬虫进阶Python多线程和多进程
Python支持多线程,并提供thread标准库。许多开发者认为Python的多线程功能存在不足之处,并建议转向使用多进程以提高性能和稳定性。

Python为了安全考虑有一个GIL。每个CPU在同一时间只能执行一个线程
Global Interpreter Lock(简称GIL)是其英文名称,它类似于一个通行证.每个线程都需要首先申请这个通行证.获得这个通行证后,才有可能进入中央处理器进行操作.
每个线程的执行方式:
1、获取GIL
2、执行代码直到sleep或者是python虚拟机将其挂起。
3、释放GIL
每次释放完GIL锁后, 线程会在释放后进行争用以及切换至另一个线程的过程中导致资源消耗。同时, 由于存在GIL机制的存在, 在Python中同一个进程也只能同时运行一个能够获得该资源的子进程(只有获得GIL才能运行);因此, 在具有多个处理器核的情况下, Python的整体多子进程执行效率相对较低。
本例采用了多线队列机制作为实现框架,在测试过程中采用Selenium技术进行数据抓取,并基于Chrome浏览器运行在无脚本模式下。运行速度较慢的情况下可以直接调用相关的Python库模块来加速抓取过程;如果需要完成整个网站的数据获取则建议使用Scrapy框架来进行处理
通过Threading模块实现线程的创建,并直接继承自threading.Thread类;同时具体实现init和run功能。
线程同步
如果多个线程共同修改同一个数据,则可能导致不可预知的结果。为确保数据的一致性,必须对所有线程实施同步机制。
通过Thread对象提供的Lock与Rlock类可实现基本的线程同步机制。这些类均配备有各自的acquire与release方法。对于仅限一个线程执行的操作数据,则应在相应的acquire与release方法之间进行处理。例如:
主要优势体现在能够并行执行多个任务(至少从我们的角度来看)。然而,在某些情况下(如涉及共享数据时),可能会导致数据不一致的问题。
假设一个列表中所有的元素都初始化为0值,在这种情况下有两个进程协同工作:其中一个进程逆序地将每个元素设置为1;另一个进程则按照顺序遍历并输出整个列表的内容。
那么,在线程set开始执行修改时(即"set"操作完成),当print试图访问共享的列表(即"print"操作发起),就会出现一半0一半1的情况(即数据不一致)。为了避免这种情况的发生(即避免数据不一致现象),引入了锁(即锁机制)的概念(即锁概念)。
该系统中存有两种类型的互斥资源控制机制——一种是被锁定状态(locked),另一种是未被锁定状态(unlocked)。当任意一个执行操作如'set'的操作单元试图访问共享资源时,则必须先尝试获取当前锁;若发现当前已有其他进程正在执行操作如'print'而占用该资源,则会使得该操作单元的操作暂时搁置(阻塞)直至被允许重新开始执行为止。
经过这样的优化处理,在打印列表时只能是全0或全1,并避免出现部分为0部分为1的情况。
线程优先级队列
Python的Queue模块提供了实现同步与线程安全功能的队列类,并包含三种类型:FIFO(先进先出)队列为Queue类、LIFO(后进先出)队列为LifoQueue类以及优先级队列PriorityQueue。这些类均实现了锁原语机制,并可在多线程环境中直接应用。通过合理配置这些数据结构能够有效实现各线程之间的协调运作。
Queue模块中的常用方法:
Queue.qsize() 返回队列的大小
Queue.empty() 如果队列为空,返回True,反之False
Queue.full() 如果队列满了,返回True,反之False
Queue.full 与 maxsize 大小对应
Queue.get([block[, timeout]])获取队列,timeout等待时间
Queue.get_nowait() 相当Queue.get(False)
Queue.put(item) 写入队列,timeout等待时间
Queue.put_nowait(item) 相当Queue.put(item, False)
Queue.task_done()函数在一项任务完成后向相关队列发出通知
Queue.join() 实际上意味着等到队列为空,再执行别的操作
importqueue
importthreading
fromselenium importwebdriver
fromselenium.webdriver.chrome.options importOptions
exitFlag = 0q = queue.Queue()
chrome_options = Options()
chrome_options.add_argument('--headless')
classscrapy_biquge():
defget_url(self):
browser = webdriver.Chrome(chrome_options=chrome_options)
browser.get('http://www.xbiquge.la/xuanhuanxiaoshuo/')
content = browser.find_element_by_class_name("r")
content = content.find_elements_by_xpath('//ul/li/span[@class="s2"]/a')
fori incontent:
title = i.text
href = i.get_attribute('href')
print(title+'+'+href)
q.put(title+'+'+href)
browser.close()
browser.quit()
子类myThread继承自库中的基础线程类threading.Thread。注释:#作为库提供的基础线程类threading.Thread的子类
def init(self, threadID, name, counter):
使用初始化方法初始化实例参数包括线程标识、名称以及计数器变量
threading.Thread.init(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self): #将执行的代码注入到run函数内 #将要运行的操作嵌入到该方法中 将要运行的操作包含在run方法体内#当线程被创建时 该方法会被自动执行
while notexitFlag:
queueLock.acquire()
if notq.empty():
item = q.get()
queueLock.release()
title = item.split('+')[0]
href = item.split('+')[1]
get_content(title,href)
else:
print('数据全部结束')
queueLock.release()
defget_content(title,href):
browser = webdriver.Chrome(chrome_options=chrome_options)
browser.get(href)
browser.find_element_by_id('list')
novel_content = browser.find_elements_by_xpath('//dl/dd/a')
fornovel innovel_content:
novel_dir = novel.text
novel_dir_href = novel.get_attribute('href')
print(title,novel_dir,novel_dir_href)
browser.close()
browser.quit()
if__name__ == 'main':
#所有url进队列以后,启动线程scrapy_biquge().get_url()
threadList = ["Thread-1","Thread-2","Thread-3"]
queueLock = threading.Lock()
threads = []
threadID = 1#创建新线程fortName inthreadList:
thread = myThread(threadID,tName,q)
thread.start()
threads.append(thread)
threadID += 1#等待队列清空while notq.empty():
pass#通知线程是时候退出exitFlag = 1#等待所有线程完成fort inthreads:
t.join()
print("Exiting Main Thread")

上面的例子用了FIFO队列。当然你也可以换成其他类型的队列.
LifoQueue后进先出
Priority Queue优先队列
Python多进程,multiprocessing,下次使用多进程跑这个代码。
参考:https://cuiqingcai.com/3325.html

