文章目录
- 前言
- 页面分析
- 设计代码
前言
欢迎回来兄弟们,想必各位都已经过了一遍上篇文章了吧,没过的兄弟们可以移步上篇文章指点一二,链接: 爬虫日常练习-艾图网单页面图片爬取
感谢xdm
昨天的内容想必已经挑起了大家的胃口,我们趁热打铁。一个页面上就那么一点图片,光下载这么点多没有意思。我们直接把所有页面图片全部下载下来
页面分析
书接上文,我们对页面再次进行分析,艾图网链接:艾图网
我们可以看到在页面的最底部有一个页面条,一共有24页内容,每一页图片链接的获取方式都和第一页相同。我们不经设想,如果在这里获取到每一个页面的url,并循环请求获取图片链接,那么就可以将所有图片下载下来
我们观察二三两个页面,发现在顶部的url只有一处发生了变化,index_后面的参数会跟着页面变化
观察第一页发现url也可以更改为这种格式
并且检查页面发现并没有发现页面的url,这个时候直接大胆下判断,通过改变index_后的参数来切换页面
设计代码
我们只需要在前篇文章的代码上小小的修改这么一两处就可以实现该功能
if __name__ == '__main__':
for i in range(1, 25):
url = f"https://www.iituku.com/lvyou/index_{i}.html?sort=0"
pic_url_list = get_pic_url(url)
download(pic_url_list)
就只需要加上那么一个循环,为了规范,我们在目录下创建一个新的文件夹,并更改下载路径。
def download(list):
for src in list:
resp = requests.get(src)
pic_name = src.split('/')[-1]
with open('./aitu_pic/'+pic_name, mode='wb') as f:
f.write(resp.content)
print('下载完成')
为了查看下载速度,我们再添加time模块
if __name__ == '__main__':
start_time = time.time()
for i in range(1, 25):
url = f"https://www.iituku.com/lvyou/index_{i}.html?sort=0"
pic_url_list = get_pic_url(url)
download(pic_url_list)
end_time = time.time()
print(end_time-start_time)
运行结果展示如下
我们可以看到在一分钟以内已经全部下载好了旅游模块的所有图片。这个速度相对于手工一张一张的下载可谓是天差地别。可老哥们觉得这就是它的极限了吗?
不不不,太小看我们啦xd。接下来就不得不提一嘴线程与协程了,但是我现在不说,哎嘿,就是玩。驴还得休息呢,人不得睡觉。明天在更,拜拜兄弟们
哈哈哈哈开个玩笑xdm,咋可是实实在在的老实人
言归正传,如何去提升这类爬虫的运行速度呢?这个时候就要引入线程与进程的概念了
我们可以打开任务浏览器,可以看到我们的电脑正在运行多个进程。如果在这里详细介绍的话占用的篇幅过多,后期可以新开一个模块介绍,这里不再多说。只简单的阐述他们的关系。
进程是操作系统资源分配的基本单位。而线程是处理器任务调度的执行的基本单位。一个进程可以包含多个进程。细心的同学肯定能想到,通过多个进程调用系统资源肯定不如通过多个线程调用进程的资源来的迅速。我们可以通过本次实验测试一下
首先第一步就是先调用需要的进程池与线程池,以及可以在进程间传递参数的Queue队列
from multiprocessing import Process,Queue
from concurrent.futures import ThreadPoolExecutor
我们首先确立一个基本思路。设立两个进程,一个用来获取页面所有的图片链接,一个用来下载图片
。根据这个思路来设计代码
if __name__ == '__main__':
start_time = time.time()
q = Queue()
p1 = Process(target=get_pic_url, args=(q,)) #进程一用来获取图片链接
p2 = Process(target=download, args=(q,)) # 进程二用来下载图片
然后对get_pic_url函数做些更改
def get_pic_url(q):
for i in range(1, 25):
url = f"https://www.iituku.com/lvyou/index_{i}.html?sort=0"
resp = requests.get(url)
resp.encoding = 'utf-8'
tree = etree.HTML(resp.text)
pic_url_string = tree.xpath('//html/body/script[2]/text()')[0]
obj = re.compile(r'var imagesarr=\"(.*?)\";')
data = obj.findall(pic_url_string)[0]
data = str(data).replace('"', '')
data_list = data.split('}')
# pic_url_list = []
for li in data_list:
http = re.findall(r'picture:(.*?)/nu', li)
if not http:
continue
else:
http_str = http[0]
http = http_str.replace('\\', '')
# pic_url_list.append(http)
q.put(http)
print(f'{http}已加载队列')
我们可以看到将页面网址的重要参数改为循环可变参数,借此完成不同页面数据的爬取。并且采用了multiprocessing的queue队列来完成不同进程之间的数据传递,这个时最重要的一步,我们会通过它来向专门下载图片的进程传递图片链接。
接着编写一个线程池来分散流量
def download_pic(q):
with ThreadPoolExecutor(10) as t:
while True:
if q.empty(): # 判断队列中是否有数值,没有就跳出循环
break
else:
src = q.get()
t.submit(download,src)
以及对download函数做一些修改
def download(src):
resp = requests.get(src)
pic_name = src.split('/')[-1]
with open('./aitu_pic/'+pic_name, mode='wb') as f: # 在文件夹下新建aitu_pic文件夹存储图片文件
f.write(resp.content)
print(f'{pic_name}下载完成')
至此大部分代码全部编写完成。但心急的小伙伴如果这个时候立马运行的话。估计会受到不小的打击。
包括我本人也在这里耗费了数个小时才一点一点实验出来,虽然不难,但很恶心人。在这里无私分享给大家,希望大家不用在额外花费这么多时间
大家可以注意看我的代码,在p1启动后,p2随之启动。理想中的状态应该是下面的网址加载到队列后,p2会立马提取到队列中的链接并通过线程池进行下载操作。然而我们在运行栏翻了半天也只有加载队列信息而没有下载提示。这就很伤脑筋了,忙活半天只有链接没有下载。
那么为什么我们编写的程序没有按照想要的效果运行呢,查阅半天也找不到为什么,一开始怀疑是不是queue.get的阻塞导致进程死锁,排查了一下也没找出问题。我就自我揣摩了一下,怀疑原因可能很简单,因为进程是异步运行的。大家可以看下面的一行代码
if q.empty():
break
这是一个简单的判断,用来判断队列里面是否有数值。我们的本意是要用它来做进程的结束判断,但是在这里反而成了一个巨大的bug。由于两个进程是异步运行的。而由于p1的代码要实现的步骤较多,导致在p1还没有进行到向队列传输数据时,p2已经开始向q队列请求。这就导致了p2直接跳过了线程池向download函数发送请求循环。最后p1只能不断的向q队列传输参数。而p2早就停止了对q的请求,根本就不会发送下载请求。因此我们需要错开p1与p2。在p1启动时,强制休眠一段时间在启动p2.
p1.start()
time.sleep(4)
p2.start()
p1.join()
p2.join()
我们再来重新看下运行效果
可以看到,我们只用了11秒的时间就系在了一个网站某模块的全部图片
先把完整代码献给xdm
import time
import requests
from lxml import etree
import re
from multiprocessing import Process, Queue
from concurrent.futures import ThreadPoolExecutor
def get_pic_url(q):
for i in range(1, 25):
url = f"https://www.iituku.com/lvyou/index_{i}.html?sort=0"
resp = requests.get(url)
resp.encoding = 'utf-8'
tree = etree.HTML(resp.text)
pic_url_string = tree.xpath('//html/body/script[2]/text()')[0]
obj = re.compile(r'var imagesarr=\"(.*?)\";')
data = obj.findall(pic_url_string)[0]
data = str(data).replace('"', '')
data_list = data.split('}')
# pic_url_list = []
for li in data_list:
http = re.findall(r'picture:(.*?)/nu', li)
if not http:
continue
else:
http_str = http[0]
http = http_str.replace('\\', '')
# pic_url_list.append(http)
q.put(http)
print(f'{http}已加载队列')
def download(src):
resp = requests.get(src)
pic_name = src.split('/')[-1]
with open('./aitu_pic/'+pic_name, mode='wb') as f:
f.write(resp.content)
print(f'{pic_name}下载完成')
def download_pic(q):
with ThreadPoolExecutor(10) as t:
while True:
if not q.empty():
src = q.get()
t.submit(download, src)
else:
break
if __name__ == '__main__':
q = Queue()
p1 = Process(target=get_pic_url, args=(q,)) # 进程一用来获取图片链接
p2 = Process(target=download_pic, args=(q,)) # 进程二用来下载图片
start_time = time.time()
p1.start()
time.sleep(4)
p2.start()
p1.join()
p2.join()
end_time = time.time()
print(end_time-start_time)
相比较与上面下载图片的一分钟,多进程与线程无疑带来了巨大的速度提升,这11秒期间甚至包括了我们设置的4秒强制休眠。时间多的朋友也可以试着把p1省略掉,尝试单进程多线程会不会提高速度。下次内容暂定为利用协程下载图片,争取吃透这一块内容。就说这么多,拜拜兄弟们