这段时间学习了一个简单的爬虫项目,其中用到了多线程和生产者消费者模式,在这里总结下学习心得。
基础知识:
线程与进程:
进程是操作系统分配资源的最小单元, 线程是操作系统调度的最小单元。
一个应用程序至少包括1个进程,而1个进程包括1个或多个线程,线程的尺度更小。
每个进程在执行过程中拥有独立的内存单元,而一个线程的多个线程在执行过程中共享内存
并发与并行:
并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。
并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
死锁条件:
产生死锁的四个必要条件:
1 互斥条件:
进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
2 不可剥夺条件:
进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
3 请求与保持条件:
进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
4 循环等待条件:
存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。
资源分配图含圈而系统又不一定有死锁的原因是同类资源数大于1。但若系统中每类资 源都只有一个资源,则资源分配图含圈就变成了系统出现死锁的充分必要条件。
只要上述条件之一不满足,就不会发生死锁。
生产者消费者模式:
死锁:
死锁是指两个或两个以上的进程(线程)在运行过程中因争夺资源而造成的一种僵局(Deadly-Embrace) ) ,若无外力作用,这些进程(线程)都将无法向前推进。
项目:
环境:python3
生产者消费者设计模式:
生产者:专门用来获取表情包url链接
消费者:从链接中下载图片
全局变量:表情报链接列表
爬虫的流程分析:
- 请求数据:requests库(这个库可以非常方便的去请求网络数据)
- 安装方法:pip install requests
- 将请求下来的数据解析出来,获取我们想要的数据,把不想要的数据抛弃掉。
- BeautifulSoup:pip install bs4
- lxml:pip install lxml
- 将解析后的数据保存下来。如果是文字类型的,可以保存到文件中或者是数据库中或者缓存中都可以。如果是文件类型,比如图片,视频,那么可以保存到硬盘中。
代码:
'''
图片html示例:
<a class="col-xs-6 col-sm-3" href="http://www.doutula.com/photo/2250142" style="padding:5px;">
<img referrerpolicy="no-referrer" src="//static.doutula.com/img/loader.gif?32" data-original="http://ww1.sinaimg.cn/bmiddle/9150e4e5gy1g6c23kx7dfj203p03p3yc.jpg" alt="吉祥物这个弟弟" class="img-responsive lazy image_dta" data-backup="http://img.doutula.com/production/uploads/image/2019/08/25/20190825729166_CJvrVi.jpg">
<p style="display: none">吉祥物这个弟弟</p>
</a>
'''
from bs4 import BeautifulSoup
import requests
from urllib import request
import os
import threading
#全局变量
PAGE_URLS = []
IMG_URLS = []
gLock = threading.Lock()
#伪装头部,从谷歌浏览器获取
headers = {
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36"
}
#生产者
def producer():
while True:
gLock.acquire()
if len(PAGE_URLS) == 0:
gLock.release()
break
#pop出列表最后一个元素
page_url = PAGE_URLS.pop()
gLock.release()
#2. 对请求身份进行伪装,使用lxml库解析html文件
response = requests.get(page_url,headers=headers)
text = response.text
soup = BeautifulSoup(text,'lxml')
#3.找到图片class->找到图片url(参考图片html示例)
img_list = soup.find_all("img",attrs={"class":"img-responsive lazy image_dta"})
for img in img_list:
img_url = img['data-original']
IMG_URLS.append(img_url)
#消费者
def consumer():
while True:
gLock.acquire()
if len(PAGE_URLS) == 0 and len(IMG_URLS) == 0:
gLock.release()
break
if len(IMG_URLS)>0:
img_url = IMG_URLS.pop()
else:
img_url = ''
gLock.release()
#windows‘\‘ mac/Linux’/‘ 跨平台表示法os.path.join
#[-1]表示最后一个元素
#4.将解析后的数据保存到img文件下
if(img_url):
filename = img_url.split("/")[-1]
fullpath = os.path.join("img",filename)
request.urlretrieve(img_url,fullpath)
print("%sDownload Success"%filename)
def main():
# 1. 将每一页的url存到PAGE_URLS数组里
for x in range(1,100):
page_url = "http://www.doutula.com/photo/list/?page="+str(x)
PAGE_URLS.append(page_url)
for x in range(5):
th = threading.Thread(target = producer)
th.start()
for x in range(5):
th = threading.Thread(target = consumer)
th.start()
if __name__ == '__main__':
main()