爬虫
前言
线程与进程的讲解:
进程是一个资源单位,每一个进程至少有一个线程,而线程是一个执行单位。
而一个程序若是只有一个线程,那么该程序将会串行运行,如果要爬取大量的数据那么执行效率将会显得比较低下。
一、多线程爬虫:
对于大量数据要多次发送请求可以利用python中内置库threaing开启多个线程,但是这样子的话就只能手动一个一个的开启线程,所以还有一种方法使用线程池来进行多线程操作from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(50) as T:
这样就开启了50个线程的线程池
比如下面一个场景:
要爬取一个网站多页的数据,而该网站每一页url的请求体中有第几页的参数
所以想要爬取50页就遍历(1,52)即可。如果此时并没开启线程池去请求50次,就很慢,但是如果开启了50个线程那么就可以同时去进行,速度自然快得多得多了
样例代码:
import requests
from concurrent.futures import ThreadPoolExecutor
def DownLoad_OnePage(Url,i):
headers={
'User-Agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
}
data={
"current":i
}
resp=requests.post(Url,data=data)
for price in (resp.json()['list']):
data_row=price['prodName']+':'+price['avgPrice']
print(f'第{i}页数据{data_row}爬取完毕')
f.write(data_row)
f.write('\n')
if __name__=='__main__':
f = open('data.txt', 'w', encoding='utf-8')
with ThreadPoolExecutor(50) as T:
for i in range(1,50):
T.submit(DownLoad_OnePage,Url="http://www.xinfadi.com.cn/getPriceData.html",i=i)
print('爬取完毕')
f.close()
二、多任务异步协程爬虫
概述:
当程序中遇到了如input,sleep等会阻塞程序运行的操作时,使用协程就可以跳过该阻塞运行其他的代码,再当阻塞停止时返回。
而requests.get(**)在网络请求返回数据之前,程序也是处于阻塞状态的一般情况下,当程序处于IO操作的时候。线程都会处于阻塞状态
协程:
当程序遇见了IO操作的时候,可以选择性的切换到其他任务上.
在微观上是一个任务一个任务的进行切换.切换条件一般就是IO操作
在宏观上,我们能看到的其实是多个任务一起在执行
例如:
import asyncio
import time
async def func1():
print("协程1")
# time.sleep(4) 当程序出现了同步的操作,异步就断掉了。
await asyncio.sleep(4) # 阻塞时进行await
print("协程1")
async def func2():
print("协程2")
# time.sleep(3)
await asyncio.sleep(3)
print("协程2")
async def func3():
print("协程3")
await asyncio.sleep(2)
print("协程3")
async def main():
tasks=[
func1(),
func2(),
func3()
] # 添加多任务异步协程
await asyncio.wait(tasks)
if __name__=='__main__':
# 主线程最好不出现协程,所以应在写一个main函数在主线程运行
t1=time.time()
asyncio.run(main()) # 协程运行
t2=time.time()
print(t2-t1)
这一段代码如果没有利用协程,三个time.sleep()
至少也会运行9s了
但是这段代码只会运行4s多一点点。
而实际上多任务异步协程进行爬取时模板与上面样例实际是差不多的
实战样例:
import asyncio
import aiohttp
import requests
import aiofile
import json
async def aioDownLoad(cid,b_id,title):
data = {
"book_id" :b_id,
"cid":f"{b_id}|{cid}",
"need_bookinfo":1
}
data = json.dumps(data)
url = "https://dushu.baidu.com/api/pc/getChapterContent?data={%22book_id%22:%22"+str(b_id)+"%22,%22cid%22:%22"+str(b_id)+"|"+str(cid)+"%22,%22need_bookinfo%22:1}"
async with aiohttp.ClientSession() as Session:
async with Session.get(url) as resp:
dic = await resp.json()
async with aiofile.AIOFile(f'novel/{title}.txt','w','utf-8') as f:
await f.write(dic['data']['novel']['content'])
async def getCatlog(url):
resp = requests.get(url)
dic=resp.json()
tasks = []
for item in dic['data']['novel']['items']:
title = item['title']
cid = item['cid']
tasks.append(aioDownLoad(cid,b_id,title))
await asyncio.wait(tasks)
if __name__ == '__main__':
b_id='4306063500'
url='https://dushu.baidu.com/api/pc/getCatalog?data={"book_id":"'+b_id+'"}'
asyncio.run(getCatlog(url))
aiohttp是python的⼀个⾮常优秀的第三⽅异步http请求库
举一反三aiofile就是第三方异步文件操作库
这两个的操作与requests和文件操作都是极其类似的,看上面的样例就能很容易理解。
总结
提示:这里对文章进行总结:
除了爬取多页这种要发送大量请求的的案例可以用多任务异步协程节省大量时间外,还有就是下载到本地也会处于阻塞状态,例如在爬取下载视频的时候,要知道现在视频都是分割成了海量几秒的视频放在一个m3u8文件中,所以在下载的时候也可以多任务异步协程同时下载。