用协程爬取一部小说 | 【python系列】

举报
Aasee 发表于 2022/02/21 21:44:49 2022/02/21
【摘要】 协程协程应该很多人都听过,但是协程能干什么或者为什么需要协程可能还不是很清楚,众所周知,当程序等待IO操作时会进入阻塞状态,这时这个时间CPU就会去做别的事,而如果像我这篇文章一样我需要去爬取一部小说并下载下来,300多章如果单纯使用单线程的话效率奇低并且等的到你发霉,所有我们有两个选择多线程和协程,多线程系统在线程等待IO的时候,会阻塞当前线程,切换到其它线程,这样在当前线程等待IO的过...

协程

协程应该很多人都听过,但是协程能干什么或者为什么需要协程可能还不是很清楚,众所周知,当程序等待IO操作时会进入阻塞状态,这时这个时间CPU就会去做别的事,而如果像我这篇文章一样我需要去爬取一部小说并下载下来,300多章如果单纯使用单线程的话效率奇低并且等的到你发霉,所有我们有两个选择多线程和协程,多线程系统在线程等待IO的时候,会阻塞当前线程,切换到其它线程,这样在当前线程等待IO的过程中,其它线程可以继续执行。当系统线程较少的时候没有什么问题,但是当线程数量非常多的时候,却产生了问题。一是系统线程会占用非常多的内存空间,二是过多的线程切换会占用大量的系统时间。而协程刚好可以解决上述2个问题。协程运行在线程之上,当一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程,而且协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多。

找一篇你喜欢的小说

这里我找了很久终于找到了一篇可以免费看全章节的小说了,那我们就用它来当本章的例子试水。
image.png

分析

我们点进去f12一下
image.png
可以看到内容是被包在一个class为"Volume"的dl便签中,而我们需要获得他的href中的链接跳转到新网页去拿到他的小说内容,同时我们也可以从span标签中获取每个章节的标题。
然后我们点进链接去看看章节里的网页布局
image.png
可以看到文章的内容都被一个大的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秒左右,我觉得速度还算可以大家也可以试试不用协程来下载要多久。成果图
image.png

image.png

我这报了个错
image.png
看了一下发现是这一章节被锁定了所以这个网页内没有内容但是不影响程序执行。
另外大家觉得看这个有点不懂的话可以看我发的另一篇文章
20行代码爬取一篇小说,手把手把方法喂给你 | 【爬虫系列】
看懂了就能看这篇文章进阶了。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。