初学进程通信概念,以斗图啦为例加深印象。
构建一个Producer类获取每张图片的地址
class Producer(threading.Thread):
def __init__(self,page_queue,image_queue,image_name,*args,**kwargs):
#page_queue:存放图片页数的一个队列;image_queue:存放图片地址的一个队列;image_name:存放每张图片的一个队列.
super(Producer,self).__init__(*args,**kwargs)
# super 我暂时将其理解为 是调用Producer所继承父类的一个方法,之后将类Producer的对象转换为类threading.Thread的一个对象
self.page_queue = page_queue
self.img_queue = image_queue
self.img_name = image_name
self.headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
}
def run(self):
#run()方法,与start()都是启动进程的一个方法,但是start()可以启动多个进程,而run()方法不是启动一个新的线程,可以理解为只是主线程中一个执行何种操作的一个入口。
while True:
if self.page_queue.empty():
break
# 当每页都提取完数据后,也就是说存放页数的队列为空的时候,结束Producer
url = self.page_queue.get()
self.parse_page(url)
def parse_page(self,url):
try:
request = urllib.request.Request(url, headers=self.headers)
html = urllib.request.urlopen(request).read()
data = etree.HTML(html)
image_url = data.xpath("//img//@data-original")
image_name = data.xpath("//p[@style = 'display: none']/text()")
# 分别筛选出图片的地址以及名字
for i in range(len(image_url)):
self.img_queue.put(image_url[i])
self.img_name.put(image_name[i])
except:
pass
构建一个Consumer类下载每张图片
class Consumer(threading.Thread):
def __init__(self,page_queue,image_queue,image_name,*args,**kwargs):
super(Consumer,self).__init__(*args,**kwargs)
self.page_queue = page_queue
self.image_queue = image_queue
self.img_name = image_name
def run(self):
while True:
response = requests.get(self.image_queue.get())
if self.image_queue.empty():
break
with open("D:/斗图/"+self.img_name.get()+".jpg","wb") as f:
f.write(response.content)
完整程序
from lxml import etree
import urllib.request
import requests
import threading
from queue import Queue
import os
class Producer(threading.Thread):
def __init__(self,page_queue,image_queue,image_name,*args,**kwargs):
super(Producer,self).__init__(*args,**kwargs)
self.page_queue = page_queue
self.img_queue = image_queue
self.img_name = image_name
self.headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
}
def run(self):
while True:
if self.page_queue.empty():
break
url = self.page_queue.get()
self.parse_page(url)
def parse_page(self,url):
try:
request = urllib.request.Request(url, headers=self.headers)
html = urllib.request.urlopen(request).read()
data = etree.HTML(html)
image_url = data.xpath("//img//@data-original")
image_name = data.xpath("//p[@style = 'display: none']/text()")
for i in range(len(image_url)):
self.img_queue.put(image_url[i])
self.img_name.put(image_name[i])
except:
pass
class Consumer(threading.Thread):
def __init__(self,page_queue,image_queue,image_name,*args,**kwargs):
super(Consumer,self).__init__(*args,**kwargs)
self.page_queue = page_queue
self.image_queue = image_queue
self.img_name = image_name
def run(self):
while True:
response = requests.get(self.image_queue.get())
if self.image_queue.empty():
break
with open("D:/斗图/"+self.img_name.get()+".jpg","wb") as f:
f.write(response.content)
if __name__=="__main__":
page_queue = Queue(100) # 存放页数,队列容量不需要那么大
image_queue = Queue(500) #
image_name = Queue(500)
s1 = eval(input("爬几页的图?"))
if not os.path.exists("D:/斗图/"):
os.mkdir("D:/斗图/")
# os.mkdir 创建一个相对路径下的文件夹
# 如果要在一个自己指定的未知创建文件夹,只需将os.mkdir() 替换为 os.makedirs()即可
for x in range(1,s1):
url = 'http://www.doutula.com/photo/list/?page=%d'%x
page_queue.put(url)
for x in range(5):
t = Producer(page_queue,image_queue,image_name)
t.start()
for x in range(5):
t = Consumer(page_queue,image_queue,image_name)
t.start()
遇到的未解决问题
- [Errno 2] No such file or directory: ‘D:/斗图/牛///.jpg’
- [Errno 2] No such file or directory: ‘D:/斗图/嘿嘿/IN.jpg’
诸如此类的问题,需要在命名每一张图片时将 名字中含有类似 x/x.jpg 之间的若干个 / 删除,用到replace(’/’,’’),但是在1000张图片中只有很少量个此类的情况,如果每次都需要判断名字中是否含有 / 将增长下载时间。 - image_queue = Queue(500)
image_name = Queue(500)
在程序中规定了图片以及图片名字的容量为500,程序我运行了好多次进行比较,大概就是:
如果设一次性下载图片的数量在500之内,用的时间 t_1
设一次性下载图片的数量为1000,用的时间为 t_2
在测试中 t_2 大于 t_1 ,查的资料后,除与进程数有关外,还与队列的大小有关,如果将:
image_queue = Queue(500)
image_name = Queue(500)
调整为:
image_queue = Queue(1000)
image_name = Queue(1000)
则需要的时间 t_3 将小于 t_2。当数据的数量大于队列的容量时,超出容量的数据只能等待队列有空时,才能继续入列。除了增大队列容量外我还没有具体的方法。