用协程爬取一部小说 | 【python系列】
协程
协程应该很多人都听过,但是协程能干什么或者为什么需要协程可能还不是很清楚,众所周知,当程序等待IO操作时会进入阻塞状态,这时这个时间CPU就会去做别的事,而如果像我这篇文章一样我需要去爬取一部小说并下载下来,300多章如果单纯使用单线程的话效率奇低并且等的到你发霉,所有我们有两个选择多线程和协程,多线程系统在线程等待IO的时候,会阻塞当前线程,切换到其它线程,这样在当前线程等待IO的过程中,其它线程可以继续执行。当系统线程较少的时候没有什么问题,但是当线程数量非常多的时候,却产生了问题。一是系统线程会占用非常多的内存空间,二是过多的线程切换会占用大量的系统时间。而协程刚好可以解决上述2个问题。协程运行在线程之上,当一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程,而且协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多。
找一篇你喜欢的小说
这里我找了很久终于找到了一篇可以免费看全章节的小说了,那我们就用它来当本章的例子试水。
分析
我们点进去f12一下
可以看到内容是被包在一个class为"Volume"的dl便签中,而我们需要获得他的href中的链接跳转到新网页去拿到他的小说内容,同时我们也可以从span标签中获取每个章节的标题。
然后我们点进链接去看看章节里的网页布局
可以看到文章的内容都被一个大的div给包裹起来了,div:"p"标签中的就是我们需要的小说内容。🆗到这里我们基本就可以知道大概的获取流程了,先从主页中获取全部章节内容的url链接,然后在进入每个章节链接中去获取到其中的内容。
实现
导库
这里我使用的是beautifulsoup
import requests
import asyncio
import aiohttp
import aiofiles
from bs4 import BeautifulSoup
import time
获取全部章节链接
async def get_url(url):
resp = requests.get(url)
resp.encoding = "utf-8"
html_data = BeautifulSoup(resp.text, "html.parser")
http = "https://www.17k.com"
tasks = []
dl_text = html_data.find_all("dl", class_="Volume") # 找到叫class叫Volume的dl标签
a_text = BeautifulSoup(str(dl_text), "html.parser").find_all('a') #找到全部的a标签
for need in a_text[1:]:
name = str(need.find_all('span', class_='ellipsis')) # 转化为str好做修改
href = need.get('href') # 获取片段链接
allhttp = http+href #进行拼接
n_name = name[43:len(name)-24]
# print(name[43:len(name)-24], http+href) # 对文章名进行优化修改删除多余的空格和代码
tasks.append(asyncio.create_task(download(allhttp, n_name))) #py3.8后的使用协程时要自行create_task,否则会报警告。将获取到的章节地址和文章名传给download任务
await asyncio.wait(tasks) # 挂起任务
下载每个章节的内容
async def download(url, title):
async with aiohttp.ClientSession() as session: # 这里得使用aiohttp来请求,用with的原因的他自己会关闭对话,不需自行写个close,下面的with同理
async with session.get(url) as resp:
txt = await resp.text("utf-8")
page = BeautifulSoup(txt, "html.parser")
table = page.find_all("div", class_="readAreaBox content")
con = table[0].text
# print(con)
async with aiofiles.open(f"xiaoshuo/{title}", mode="w", encoding="utf-8") as f:
await f.write(con)
这就是一个简单的下载任务由协程获取到链接后,再由协程下载到文件中。
全部代码
async def download(url, title):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
txt = await resp.text("utf-8")
page = BeautifulSoup(txt, "html.parser")
table = page.find_all("div", class_="readAreaBox content")
con = table[0].text
# print(con)
async with aiofiles.open(f"xiaoshuo/{title}", mode="w", encoding="utf-8") as f:
await f.write(con)
async def get_url(url):
resp = requests.get(url)
resp.encoding = "utf-8"
html_data = BeautifulSoup(resp.text, "html.parser")
http = "https://www.17k.com"
tasks = []
dl_text = html_data.find_all("dl", class_="Volume")
a_text = BeautifulSoup(str(dl_text), "html.parser").find_all('a')
for need in a_text[1:]:
name = str(need.find_all('span', class_='ellipsis'))
href = need.get('href')
allhttp = http+href
n_name = name[43:len(name)-24]
# print(name[43:len(name)-24], http+href)
tasks.append(asyncio.create_task(download(allhttp, n_name)))
await asyncio.wait(tasks)
if __name__ == '__main__':
url1 = "https://www.17k.com/list/667062.html"
# asyncio.run(get_url(url1))
time1 = time.time()
asyncio.get_event_loop().run_until_complete(get_url(url1))
time2 = time.time()
print(time2-time1) # 检查用时
最后为什么要用asyncio.get_event_loop().run_until_complete(get_url(url1))呢是因为用asyncio.run(get_url(url1))他会报错,详细原因请看RuntimeError: Event loop is closed,py3.8协程问题报错解决方法
总结
最后下载三百多章用时10秒左右,我觉得速度还算可以大家也可以试试不用协程来下载要多久。成果图
我这报了个错
看了一下发现是这一章节被锁定了所以这个网页内没有内容但是不影响程序执行。
另外大家觉得看这个有点不懂的话可以看我发的另一篇文章
20行代码爬取一篇小说,手把手把方法喂给你 | 【爬虫系列】看懂了就能看这篇文章进阶了。
- 点赞
- 收藏
- 关注作者
评论(0)