• python爬虫进阶篇(异步)


            学习完前面的基础知识后,我们会发现这些爬虫的效率实在是太低了。那么我们需要学习一些新的爬虫方式来进行信息的获取。

    异步

            使用python3.7后的版本中的异步进行爬取,多线程虽然快,但是异步才是爬虫真爱。

    基本概念讲解

    1.什么是异步?

            异步是指在程序执行过程中,当遇到耗时的操作时,不会等待这个操作完成才继续执行后面的代码,而是先去执行其他的操作,等到耗时的操作完成后再处理它的结果。这种方式能够提高程序的并发性响应性。在传统的同步编程中,当程序执行到一个耗时的操作时(比如文件读写、网络请求等),程序会被阻塞,直到这个操作完成才会继续往下执行。这样会导致程序不能充分利用计算资源,同时也会降低程序的响应速度。而在异步编程中,当遇到耗时操作时,程序会先切换到执行其他任务,等到耗时操作完成后再回来处理结果。这样可以让程序在等待耗时操作的同时继续执行其他任务,提高了程序的并发能力和整体性能。

            Python中的异步编程通常使用async/await关键字来定义异步事件,配合asyncio模块和一些第三方库(比如aiohttp、aiofiles等)来实现异步IO操作。异步编程在网络编程、Web开发、爬虫等领域有着广泛的应用。

    并发性

            并发性是指在同一时间段内,有多个任务在同时执行。在计算机领域,这通常是指多个线程或进程在同时执行,从而提高了程序的效率和性能。在传统的单线程编程中,程序只能按照顺序执行代码,不能同时执行多个任务,这会导致程序效率低下,特别是当遇到大量IO操作时更为明显。

            而在多线程或多进程编程中,多个任务可以同时运行,从而可以充分利用计算机的多核处理器和其他硬件资源,提高了程序的效率和性能。同时,多线程/多进程编程也可以使得程序更加稳定,因为如果某个线程/进程崩溃或阻塞,其他线程/进程仍然可以继续执行。
            需要注意的是,并发性不同于并行性。并发性是指多个任务在同一时间段内交替执行,而并行性是指多个任务在同一时刻同时执行。并行性需要硬件支持,例如多核处理器、分布式系统等。

    I/O操作的概念
            I/O操作是指输入/输出操作,是计算机领域中用来描述数据从外部设备(如磁盘、网络、键盘、显示器等)到内存或相反方向的数据传输过程。在计算机程序中,I/O操作通常涉及到读取或写入文件、网络通信、用户输入输出等操作。

    常见的I/O操作
    • - 从磁盘读取文件到内存
    • - 从网络接收数据
    • - 向磁盘写入文件
    • - 向网络发送数据
    • - 从键盘获取用户输入
    • - 向屏幕输出数据

    异步与多线程的区别

            多线程和异步都可以提高程序的并发性和响应性,但在不同的场景下可能会有不同的表现。

            多线程适合CPU密集型计算任务,因为它可以充分利用计算机的多核处理器,同时执行多个任务,从而提高程序的效率和性能。但是,在多线程编程中,线程之间需要共享内存,这可能会带来线程安全等问题,需要开发者自己管理线程之间的同步和互斥。

            异步适合I/O密集型任务,因为它可以在等待I/O操作的同时,继续执行其他任务,从而充分利用时间片,提高程序的并发性和响应性。异步编程通常使用事件循环机制,在一个线程中执行多个任务,并通过回调函数等方式处理异步事件。但是,在异步编程中,需要使用特定的异步库和语法,如async/await关键字、协程等,对新手来说有一定的学习。

    python中的异步

    准备工作

    导包,准备好工具

    1. 异步
    2. pip install asyncio
    3. 异步的文件操作
    4. pip install aiofiles
    5. 异步的网路请求
    6. pip install aiohttp

    装好之后我们需要学习一些基本的方法。

    学习基本语法

    1.asyncio的使用

    1. await关键字:

      • await用于暂停当前协程的执行,等待一个异步操作的完成,并获取其结果。
      • 在使用await时,必须将其放在一个async修饰的函数内部,以指示该函数是一个协程函数。
      • await只能在协程函数内部使用,不能在普通函数或全局作用域中使用。
    2. async关键字:

      • async用于修饰一个函数,表示该函数是一个协程函数。
      • 协程函数可以通过await关键字来暂停执行,并在等待异步操作完成后继续执行。
      • 协程函数内部可以包含多个await语句,用于等待不同的异步操作。
    3. asyncio.wait()函数:

      • asyncio.wait()函数用于等待一组协程的完成。
      • 该函数接受一个可迭代对象(如列表或集合),其中包含要等待的协程对象。
      • asyncio.wait()函数返回两个集合,分别表示已完成和未完成的任务。
    4. loop.run_until_complete()方法:
      • loop.run_until_complete()方法用于执行一个协程,直到它完成。
      • 在使用该方法时,必须将协程对象作为参数传递给它。
      • run_until_complete()方法会阻塞当前线程,直到协程执行完成或发生异常。
    5. loop.create_task()方法:
      • loop.create_task()方法用于创建一个协程任务,并将它加入事件循环中等待执行。
      • 该方法接受一个协程函数作为参数,并返回一个Task对象。
      • Task对象表示一个可调度的协程,可以通过await语句来等待其执行完成。

    aiofiles

    aiofiles是一个用于在异步代码中进行文件 I/O 操作的库。它提供了异步版本的文件读取和写入操作,与标准库中的open()函数不同,aiofiles中的函数返回awaitable对象,可以在异步函数中使用await关键字来等待文件操作完成。

    1. import asyncio
    2. import aiofiles
    3. async def read_and_print_file():
    4. async with aiofiles.open('example.txt', mode='r') as file:
    5. content = await file.read()
    6. print(content)
    7. async def write_to_file():
    8. async with aiofiles.open('example.txt', mode='w') as file:
    9. await file.write('Hello, aiofiles!')
    10. # 在事件循环中执行异步文件读写操作
    11. async def main():
    12. await write_to_file()
    13. await read_and_print_file()
    14. loop = asyncio.get_event_loop()
    15. loop.run_until_complete(main())

    记得文件操作属于i/o阻塞

    aiohttp

    aiohttp是一个用于在异步代码中进行HTTP请求的库。它提供了异步的HTTP客户端和服务器,能够高效地处理大量的并发请求。和request的使用一样

    1. import aiohttp
    2. import asyncio
    3. async def fetch_content(url):
    4. async with aiohttp.ClientSession() as session:
    5. async with session.get(url) as response:
    6. return await response.text()
    7. async def main():
    8. url = 'https://jsonplaceholder.typicode.com/posts/1'
    9. content = await fetch_content(url)
    10. print(content)
    11. loop = asyncio.get_event_loop()
    12. loop.run_until_complete(main())

    实战:只看不练假把式,直接干!基础没啥讲的,爬虫会用就行

    这次案例,随意教学了,找一个新的网站实现爬取。中间出现错误的情况我也会直接列出来(我也是菜鸡,只是帮助大家入门的)。给大家分享一下我的思路和解决。

    本博客只用于教学爬虫,决定爬取一个:

    极简壁纸_海量电脑桌面壁纸美图_4K超高清_最潮壁纸网站

    合理使用,这个网站是免费的并且还是免登录(良心网站,请求一两次就行,别一直搞(哭了,现在看我写这句话真是讽刺)),较为容易(容易个der,给我看懵逼了)

    兄弟们这个案例当乐子看。

    1.准备工作,了解网站结构,查看是否可以直接爬取。

    这个主要是看源码中是否和前端调试工具中的结构一样,我们发现,调试工具中有的 是一个a中存在一个链接,但是我们点击打开发现是一个404页面

    此时我以为这个是无效链接,然后我直接去看了网络请求,发现网络请求是可以获得图片的,但是在找url的关系时,我发现直接请求a中的地址也是可行的。

    我们试一下发送请求,看看能不能获取到这张图片。

    直接一顿操作

    1. import requests
    2. url = "https://api.zzzmh.cn/bz/v3/getUrl/c071cdc46f0c4867a1d52d0cb51fc6d629"
    3. headers = {
    4. "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
    5. }
    6. response = requests.get(url,headers=headers).content
    7. print(response)

    我们发现出现了403界面源码,这就有点不对劲了。

    那么我们学过的东西无法解决,表明需要学习新的知识了

    在网页请求的头部中,包含了一个名为"Referer"的字段,这个字段通常用来标识当前请求是从哪个页面跳转过来的,即上一个网页的地址。这对于网站分析和统计访问来源非常有用,同时也可以在一定程度上用于防盗链和安全验证。在实际开发中,服务器端可以通过检查"Referer"字段来确定请求的来源,并做出相应的处理,例如允许或拒绝特定来源的请求。同时,网站管理员也可以利用这个字段来分析用户的访问行为和流量来源,为网站运营和优化提供参考依据。

    搜嘎,现在我们在headers中加入Referer来测试一下

    直接出现,现在把他写入文件中试一下,

    1. import requests
    2. url = "https://api.zzzmh.cn/bz/v3/getUrl/c071cdc46f0c4867a1d52d0cb51fc6d629"
    3. headers = {
    4. "Referer":"https://bz.zzzmh.cn/",
    5. "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
    6. }
    7. response = requests.get(url,headers=headers).content
    8. with open("壁纸.jpg",'wb') as file:
    9. file.write(response)

    直接成功:网页请求也是可行的,但是在拼接url的时候还得来到这里。

    为什么我们点击页面链接发现时进入一个404呢,我感觉是因为点击后的并没有发送请求,无法访问。

    如何批量获取这些数据? 我们复制链接进源码看一下,发现并没有这段链接,那么这个需要找js代码,观察是否需要进行解密。寄了,我看蒙蔽了,大哥这是个人站?这么难吗?给我直接看蒙蔽了,js学是学过,但是那都是基础,后悔了早知道选哪个需要登录的了,不行我都干了四千多字了,怎么说也得爬几个。哥几个别爬了,我找半小时了。太难看了,我纯纯弱智,找这个爬。这反爬比爬网易云免费音乐还难,看这个过过眼瘾。看不懂没关系,我也不会。等我后续把js逆向学明白再带大家做这个。(其实后面介绍的selenium完全可以爬取这些链接,但是缺点就是速度太慢了。)
    爬虫案例1:js逆向获取极简壁纸的高清壁纸_爬虫爬取极简壁纸_活火石的博客-CSDN博客

    补充,使用自动化工具也可以爬取,就是速度太慢了。(其实也不算很慢,这里就打开的时候比较慢 有2秒等待时间,但是获取图片采用的是异步获取和处理,速度还是很快的。)

    1. import time
    2. from selenium.webdriver.common.by import By
    3. from selenium import webdriver
    4. from selenium.webdriver.common.keys import Keys
    5. import requests
    6. import asyncio
    7. import aiofiles
    8. import aiohttp
    9. headers = {
    10. 'Referer': 'https://bz.zzzmh.cn/',
    11. "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
    12. }
    13. async def download(href,count):
    14. print(f"第{count}图片开始缓存")
    15. try:
    16. async with aiohttp.ClientSession() as session:
    17. async with session.get(href,headers=headers) as p:
    18. data = await p.read()
    19. async with aiofiles.open(f"D:\桌面\pythoncode\爬虫案例\Selenium入门\极简壁纸\{count}.jpg",'wb') as file:
    20. await file.write(data)
    21. print(f"第{count}图片缓存成功")
    22. except:
    23. print(f"第{count}图片缓存失败")
    24. async def main():
    25. web = webdriver.Chrome()
    26. web.get("https://bz.zzzmh.cn/index")
    27. time.sleep(3)
    28. img_List = web.find_elements(by="xpath",value='//div[@class="img-box"]')
    29. count = 1
    30. task = []
    31. for i in img_List:
    32. src = i.find_element(by="xpath",value='./span[@class="down-span"]/a')
    33. src= src.get_attribute('href')
    34. print(src)
    35. t = asyncio.create_task(download(src,count))
    36. task.append(t)
    37. count+=1
    38. return await asyncio.wait(task)
    39. if __name__=="__main__":
    40. asyncio.run(main())

    案例2

    上面那个案例给我整吐了,不行了,换回老朋友笔趣阁。

    神秘复苏最新章节_神秘复苏全文在线阅读_佛前献花的小说_笔趣阁

    1.查看网页源代码和检查中的链接是否一致 

    直接爬取每个章节的内容,然后装填进一个数组中,我们爬取这些章节小说可以使用异步来进行。所以现在只需要解析出链接,然后交给异步即可。

    注意此时的编码方式

    获取请求链接
     

    1. import requests
    2. import aiofiles
    3. import aiohttp
    4. from lxml import etree
    5. import asyncio
    6. async def main():
    7. url = "https://www.bige3.cc/book/66/"
    8. headers = {
    9. "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
    10. }
    11. response = requests.get(url)
    12. response.encoding = 'utf-8'
    13. en = etree.HTML(response.text)
    14. title_List = en.xpath("//div[@class='listmain']/dl//dd")
    15. print(title_List)
    16. if __name__=="__main__":
    17. asyncio.run(main())

    解析链接和上个一样,区别在于此次获取每个章节的内容采用aiohttp 写入文件使用aiofiles 需要再阻塞前加入等待 await

    装填链接

    1. import requests
    2. import aiofiles
    3. import aiohttp
    4. from lxml import etree
    5. import asyncio
    6. async def download(url):
    7. pass
    8. async def main():
    9. url = "https://www.bige3.cc/book/66/"
    10. headers = {
    11. "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
    12. }
    13. response = requests.get(url)
    14. response.encoding = 'utf-8'
    15. en = etree.HTML(response.text)
    16. task = []
    17. title_List = en.xpath("//div[@class='listmain']/dl//dd")
    18. for i in title_List:
    19. src = i.xpath("./a/@href")[0]
    20. src = "https://www.bige3.cc/" + src
    21. t = asyncio.create_task(download(src))
    22. task.append(t)
    23. return await asyncio.wait(task)
    24. if __name__=="__main__":
    25. asyncio.run(main())

     我们要注意asyncio.wait()这个过程需要等待所以加入了await 

    下载内容

    直接看界面,源码中存在小说内容,所以直接爬取就行。

     直接爬取:注意i/o阻塞的位置加入await即可,就是和之前的相比加入了一个async而已,没啥区别

    1. import requests
    2. import aiofiles
    3. import aiohttp
    4. from lxml import etree
    5. import asyncio
    6. async def download(url):
    7. try:
    8. print("小说开始下载")
    9. async with aiohttp.ClientSession() as session:
    10. async with session.get(url) as r:
    11. response = await r.text()
    12. en = etree.HTML(response)
    13. file_Title = en.xpath('//h1[@class="wap_none"]/text()')[0]
    14. file_Content = en.xpath('//*[@id="chaptercontent"]/text()')
    15. file_Content = ("".join(file_Content)).replace("\u3000","\n")
    16. file_Title = f"D:\桌面\pythoncode\爬虫教学\神秘复苏\{file_Title}.txt"
    17. async with aiofiles.open(file_Title,'w',encoding='utf-8') as file:
    18. await file.write(file_Content)
    19. print("小说下载成功")
    20. except:
    21. print("下载失败")
    22. async def main():
    23. url = "https://www.bige3.cc/book/66/"
    24. headers = {
    25. "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
    26. }
    27. response = requests.get(url)
    28. response.encoding = 'utf-8'
    29. en = etree.HTML(response.text)
    30. task = []
    31. title_List = en.xpath("//div[@class='listmain']/dl//dd")
    32. for i in title_List:
    33. src = i.xpath("./a/@href")[0]
    34. src = "https://www.bige3.cc/" + src
    35. t = asyncio.create_task(download(src))
    36. task.append(t)
    37. return await asyncio.wait(task)
    38. if __name__=="__main__":
    39. asyncio.run(main())

    直接轻轻松松爬取一本小说且顺序是有序的。期待下次更新。

  • 相关阅读:
    读书笔记:《你拿什么定义自己》
    ffmpeg v4l2集成分析
    软件测试之集成测试
    vue+element-plus完美实现跨境电商商城网站
    买卖股票的最好时机(二)
    【vue设计与实现】渲染器 2-自定义渲染器
    C++11 - 7 - 线程库
    使用RobustPCA 进行时间序列的异常检测
    使用Azure AI Search和LlamaIndex构建高级RAG应用
    tensorflow object detection api安装教程
  • 原文地址:https://blog.csdn.net/screamn/article/details/134617988