什么是线程,进程
进程:每个进程都有自己独立的内存空间,不同进程之间的内存空间不共享。
密集CPU任务,需要充分使用多核CPU资源(服务器,大量的并行计算)时,用多进程。
进程之间的通信有操作系统传递,导致通讯效率低,切换开销大。线程:一个进程可以有多个线程,所有线程共享进程的内存空间,通讯效率高,切换开销小。
共享意味着竞争,导致数据不安全,为了保护内存空间的数据安全,引入"互斥锁"。
一个线程在访问内存空间的时候,其他线程不允许访问,必须等待之前的线程访问结束,才能使用这个内存空间。
谈了线程和进程,不得不说线程锁(一种安全有序的让多个线程访问内存空间的机制)
来源:百度百科
曾经我看过一篇文章,对于爬虫有这么一个比喻,爬虫就是去果园摘水果,但是一个人多摘不了这么多,这不叫上了隔壁老,咱们一块去,其实就是就是一个线程,人太多了,不如分两个队伍,其实就是两个进程,可是两个队伍摘水果,万一他们都摘了,重复进入别人的领地,所以需要线程锁来隔开两个进程的地盘
线程典型例子
定义一个生产者,再定义一个消费者
import threading
import time
import random
MONEY = 0
gLock = threading.Lock()
def Procuder():
while True:
global MONEY
random_money = random.randint(10,100)
# 开锁
gLock.acquire()
MONEY += random_money
gLock.release()
print ('生产者%s-生产了%d' % (threading.current_thread,random_money))
time.sleep(0.5)
def Customer():
while True:
global MONEY
random_money = random.randint(10,100)
if MONEY > random_money:
print ('消费者%s-消费了:%d' % (threading.current_thread,random_money))
gLock.acquire()
MONEY -= random_money
gLock.release()
else:
print ('需要消费的钱为:%d,余额为:%d,' % (random_money,MONEY))
time.sleep(0.5)
def p_c_test():
# 执行3个线程,来当作生产者
for x in range(3):
th = threading.Thread(target=Procuder)
th.start()
# 执行3个线程,来当作消费者
for x in range(3):
th = threading.Thread(target=Customer)
th.start()
if __name__ == "__main__":
p_c_test()
从下图看出代码运行是不断生产,不断消费。
实战训练这次爬取的是www.doutula.com的斗图
http://www.doutula.com/photo/list/
导入对应的模块
import requests
from lxml import etree
from queue import Queue
import threading
import os
# 用于保存图片的下载方式
from urllib import request
这次采用类的继承方式来开多线程和队列的方式
# 定义生产者来生成表情的url
class Producer(threading.Thread):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)'
' Chrome/69.0.3497.100 Safari/537.36',
}
def __init__(self, page_queue, img_queue, *args, **kwargs):
# 继承threading.Thread的__init__方法
super(Producer, self).__init__(*args, **kwargs)
self.page_queue = page_queue
self.img_queue = img_queue
if not os.path.exists(r'D:\images\imgs'):
os.makedirs(r'D:\images\imgs')
剩余代码
分析xpath
def run(self):
while True:
if self.page_queue.empty():
break
url = self.page_queue.get()
self.parse_page(def parse_page(self, url):
def parse_page(self, url):
response = requests.get(url, headers=self.headers)
text = response.text
html = etree.HTML(text)
contents = html.xpath("//a[@class='col-xs-6 col-sm-3']")
for content in contents:
title = content.xpath(".//p[@style='display: none']/text()")[0]
href = content.xpath(".//img/@data-original")[0]
suffix = os.path.splitext(href)[1]
filename = title + suffix
self.img_queue.put((href, filename))
如果队列有东西就下载,没用就break
# 定义消费者
class Consumer(threading.Thread):
def __init__(self, page_queue, img_queue, *args, **kwargs):
super(Consumer, self).__init__(*args, **kwargs)
self.page_queue = page_queue
self.img_queue = img_queue
def run(self):
while True:
if self.img_queue.empty() and self.page_queue.empty():
break
img_url, filename = self.img_queue.get()
request.urlretrieve(img_url, 'D:\images\imgs\{}'.format(filename))
print(filename + '----下载完成')
最后开线程,爬呀爬
page_queue = Queue(100)
img_queue = Queue(1000)
for p in range(1, 101):
url = 'http://www.doutula.com/photo/list/?page={}'.format(p)
page_queue.put(url)
for x in range(5):
producer = Producer(page_queue, img_queue)
producer.start()
for x in range(5):
consumer = Consumer(page_queue, img_queue)
consumer.start()
不到10秒中,就2000张,哇哇,这速度
本节代码已上传,在公众号回复【斗图】
■ Over ■
最后,祝有所学习,有所成长
回复【1024】获取学习资料
转发,好看支持一下,感谢
你的转发,就是对我最大的支持