Python异步爬虫进阶——异步爬虫的实现

本文为学习笔记,部分内容为老师所写,非纯原创

目录

await

aiohttp 的使用

异步mysql的使用

用异步mysql把小说存数据库里


        要实现异步处理,得先要有挂起的操作(把需要等的操作先挂起来存档,存完档先去执行别的操作),当一个任务需要等待 IO 结果的时候,可以挂起当前任务,转而去执行其他任务,这样才能充分利用好资源,要实现异步,需要了解一下 await 的用法,使用 await 可以将耗时等待的操作挂起,让出控制权。当协程执行的时候遇到 await,时间循环就会将本协程挂起,转而去执行别的协程,直到其他的协程挂起或执行完毕。

await

        await 后面的对象必须是如下格式之一:

  • 一个原生 coroutine 对象。
  • 一个由 types.coroutine 修饰的生成器,这个生成器可以返回 coroutine 对象。
  • 一个包含 await 方法的对象返回的一个迭代器。

        使用 aiohttp 实现异步请求时,需要使用返回值时需要等待,因此要加await;定义一个函数,函数里有发请求的方法,函数用了await时,调用函数需要加await。

aiohttp 的使用

        aiohttp 是一个支持异步请求的库,利用它和 asyncio 配合我们可以实现异步请求操作。下面以访问博客里面的文章,并返回 reponse.text() 为例,实现异步爬虫。将请求库由 requests 改成了 aiohttp ,通过 aiohttp ClientSession 类的 get 方法进行请求异步操作。
from lxml import etree
import requests
import logging
# 日志
import time
import aiohttp
# 异步
import asyncio
# 协程
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')
# 日志模块基础设计,设置日志模块级别,level=logging.INFO(什么级别的会被记录,这里是INFO通知,有消息就记录)
# 后边format='%(asctime)s - %(levelname)s: %(message)s'是记录格式
url = 'https://blog.csdn.net/?spm=1001.2014.3001.4477'
start_time = time.time()
# 获取当前时间戳
def get_urls():
    # 获取博客里的文章链接
    headers = {"user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64)AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"}
    resp = requests.get(url, headers=headers)
    html = etree.HTML(resp.text)
    url_list = html.xpath("//div[@class='content']/a/@href")
    return url_list
async def request_page(url):
    logging.info('scraping %s', url)
    async with aiohttp.ClientSession() as session:
        response = await session.get(url)
    # aiohttp发请求方法session.get()相当于request.get()
        return await response.text()
def main():
    url_list = get_urls()
    tasks = [asyncio.ensure_future(request_page(url)) for url in url_list]
    # 列表生成式,遍历url_list,调用request_page方法
    loop = asyncio.get_event_loop()
    tasks = asyncio.gather(*tasks)
    # 上边列表里有一堆任务,需要用asyncio.gather(*tasks)整合成一个任务再执行
    loop.run_until_complete(tasks)
if __name__ == '__main__':
    main()
    end_time = time.time()
    logging.info('total time %s seconds', end_time - start_time)
       可以看到1.7s就执行完了, 当遇到阻塞式操作时,任务被挂起,程序接着去执行其他的任务,而不是傻傻地等待,这样可以充分利用 CPU 时间,而不必把时间浪费在等待 IO 上。

        对比多线程 ,协程会比多线程速度快(注意线程不是越多越快,过多也会变慢

异步mysql的使用

        Python操作 MysQL数据库大部分驱动都是同步的,也就是必须等待他的返回才会往下执行,如果一个 SQL执行得比较久,那么会直接卡死这个线程。因此这里我们可以选择异步的Python驱动来操作 MysQL 数据库。
        UUID 是 通用唯一识别码(Universally Unique Identifier)的缩写,是一种软件建构的标准,亦为开放软件基金会组织在分布式计算环境领域的一部分。其目的,是让分布式系统中的所有元素,都能有唯一的辨识信息,而不需要通过中央控制端来做辨识信息的指定。如此一来,每个人都可以创建不与其它人冲突的UUID。在这样的情况下,就不需考虑数据库创建时的名称重复问题。最广泛应用的UUID,是微软公司的全局唯一标识符(GUID),而其他重要的应用,则有Linux ext2/ext3文件系统、LUKS加密分区、GNOME、KDE、Mac OS X 等等。另外我们也可以在e2fsprogs包中的UUID库找到实现。
import asyncio
import aiomysql
import shortuuid
import pymysql
# 异步
# aiomysql做数据库连接的时候,需要这个loop对象
async def async_basic(loop):
    pool = await aiomysql.create_pool(
    host="127.0.0.1",
    port=3306,
    user="root",
    password="123456",
    db="ceshi",
    loop=loop
    )
    async with pool.acquire() as conn:
        async with conn.cursor() as cursor:
            for x in range(10000):
                content = shortuuid.uuid()
                sql = "insert into mybrank(brank) values('{}')".format(content)
            #执行SQL语句
                await cursor.execute(sql)
            await conn.commit()
    #关闭连接池
    pool.close()
    await pool.wait_closed()
if __name__ == '__main__':
    # 异步:数量大时用异步
    loop = asyncio.get_event_loop()
    loop.run_until_complete(async_basic(loop))
    # 同步:sync_basic()

用异步mysql把小说存数据库里

# 用异步mysql把小说存数据库里
import asyncio
import json
import time
import aiohttp
import logging
from aiohttp import ContentTypeError
# from motor.motor_asyncio import AsyncIOMotorClient
from lxml import etree
import aiomysql
from pymysql.converters import escape_string

CONCURRENCY = 4
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')
class Spider(object):
    def __init__(self):
        # 设置协程的数量
        self.semaphore = asyncio.Semaphore(CONCURRENCY)
    async def scrape_api(self, url):
        async with self.semaphore:
            logging.info('scraping %s', url)
            try:
                logging.info('scraping %s', url)
                # 发送请求
                async with self.session.get(url) as response:
                    # await asyncio.sleep(1)
                    # 返回请求结果
                    return await response.text(encoding='gbk')
            except ContentTypeError as e:
                logging.error('error occurred while scraping %s', url,exc_info=True)
    def save_file(self, tite,contents):
        op = open('1.txt','a+',encoding='utf8')
        op.write(tite[0]+'\n'+''.join(contents)+'\n')
        op.close()
    async def async_basic(self,title,contents):
        pool = await aiomysql.create_pool(
            host="localhost",
            port=3306,
            user="root",
            password="123456",
            db="ceshi",
            loop=loop
            )
        async with pool.acquire() as conn:
            async with conn.cursor() as cursor:
                # for x in range(10000):
                # content = shortuuid.uuid()
                # sql = "insert into mybrank(brank)values('{}')".format(content)
                sql = "INSERT INTO book(name,content) VALUES(%s, %s)"
                await cursor.execute(sql, (title[0], ''.join(contents)))
                # 执行SQL语句
                await conn.commit()
        # 关闭连接池
        pool.close()
        await pool.wait_closed()
    def scrape_detail(self, source):
        title = etree.HTML(source).xpath('//title/text()')
        contents = etree.HTML(source).xpath('//dd[@id="contents"]/text()')
        return title,contents
    async def main(self):
# 设置header
        headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36'
        }
        # aiohttp模块准备发送请求(主要加header头信息以及代理,cookie等头信息)
        self.session = aiohttp.ClientSession(headers=headers)
        # source = await self.scrape_api('https://www.23us.com/book/24/24664/')
        # 异步请求获取页面源码
        source = await self.scrape_api('https://www.72us.com/html/21/21578/')
        # 获取所有章节的的链接(缺少一半)
        chapter_href_list = etree.HTML(source).xpath('//td[@class="L"]/a/@href')
        # 生成抓取本章前十章节
        scrape_detail_tasks =[asyncio.ensure_future(self.scrape_api('https://www.72us.com/html/21/21578/'+href)) for href in chapter_href_list[:306]]
        content_list = await asyncio.gather(*scrape_detail_tasks)
        scrape_contents = [self.scrape_detail(contents) for contents in content_list]
# scrape_save = [self.save_file(title,contents) for title,contents in scrape_contents]
        [await self.async_basic(title,contents) for title,contents in scrape_contents]
        await self.session.close()
if __name__ == '__main__':
    begin_time = time.time()
    spider = Spider()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(spider.main())
    end_time = time.time()
    print(end_time-begin_time)

 

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值