python-16-coroutine datos de rastreo aiohttp asíncronos

Python utiliza corrutinas de un solo subproceso, de subprocesos múltiples y asíncronas para
rastrear una novela, y solo toma 5 segundos en el más rápido.
20 segundos Novelas completas con más de 2 millones de palabras y más de 600 capítulos
Python crawler-asynchronous crawler
usa python para reemplazar por lotes números de novelas de números en mayúsculas a números arábigos

1 Síncrono y asíncrono

1.1 Sincronización

La sincronización es ordenada.Para completar una determinada tarea, en el proceso de ejecución, se ejecuta paso a paso en orden hasta que se completa la tarea.

Los rastreadores son tareas intensivas de E/S. Cuando usamos la biblioteca de solicitudes de solicitudes para rastrear un sitio determinado, cuando la red está fluida y desbloqueada, la situación normal es la que se muestra en la siguiente figura: Pero antes de que la solicitud de red devuelva datos, el programa
inserte la descripción de la imagen aquí
es en un estado bloqueado, y el programa Mientras espera que se complete una operación, no puede continuar haciendo otras cosas, como se muestra en la siguiente figura:
inserte la descripción de la imagen aquí
Por supuesto, el bloqueo puede ocurrir en el programa de ejecución después de que el sitio responde. ser un programa de descarga, y la descarga lleva tiempo. Cuando el sitio no responde o el programa se bloquea en la descarga del programa, la CPU ha estado esperando y no ejecuta otros programas, entonces los recursos de la CPU se desperdician, lo que resulta en una baja eficiencia de nuestro rastreador.

1.2 Asíncrono

La asincronía es un modelo de concurrencia que es mucho más eficiente que el multiproceso. Está fuera de servicio. Para completar una determinada tarea, en el proceso de ejecución, no hay necesidad de comunicación y coordinación entre las diferentes unidades del programa, y ​​la tarea puede también ser completado Es decir, las unidades de programa no relacionadas pueden ser asíncronas. Como se muestra en la siguiente figura:
inserte la descripción de la imagen aquí
Cuando el programa de solicitud envía una solicitud de red 1 y recibe una respuesta de un sitio determinado, comienza a ejecutar el programa de descarga en el programa. Debido al tiempo requerido para la descarga u otras razones, está en un estado bloqueado, y el programa de solicitud y el programa de descarga son irrelevantes.unidad de programa, por lo que el programa solicitante envía la siguiente solicitud de red, que es asíncrona.

(1) Microscópicamente, las corrutinas asíncronas son conmutación de tarea por tarea, y las condiciones de conmutación son generalmente operaciones de IO; (
2) Macroscópicamente, las corrutinas asíncronas son múltiples tareas que se ejecutan juntas;
nota: todo lo que mencionamos anteriormente se implementa bajo el condición de hilo único.

1.3 Corrutinas

Las corrutinas, las corrutinas en inglés, son una existencia más liviana que los hilos. Así como un proceso puede tener varios subprocesos, un subproceso también puede tener varias corrutinas. Lo más importante es que la corrutina no es administrada por el kernel del sistema operativo, sino completamente controlada por el programa (es decir, ejecutada por el usuario).

La ventaja es que el rendimiento ha mejorado mucho y no consumirá recursos como el cambio de subprocesos. La sobrecarga de las rutinas es mucho menor que la de los hilos. La esencia de la rutina es de subproceso único. Suspende la operación de E/S sin consumir más recursos del sistema y luego continúa ejecutando otras tareas. Una vez que se completa la operación de E/S, vuelve a la tarea original para continuar con la ejecución.

2 rutinas

import requests #同步的网络请求模块
import re       #正则模块,用于提取数据
import asyncio  #创建并管理事件循环的模块
import aiofiles #可异步的文件操作模块
import aiohttp  #可异步的网络请求模块
import os       #可调用操作系统的模块

2.1 Biblioteca Coroutine asyncio

Obtenga una referencia a EventLoop directamente desde el módulo asyncio y coloque la rutina que se ejecutará en EventLoop, que realiza la rutina asíncrona. La rutina se declara como un método de rutina asíncrono a través de la sintaxis asíncrona, y la sintaxis await se declara como un objeto esperable de rutina asíncrona, que es la forma recomendada de escribir aplicaciones asíncronas.
(1) event_loop: El bucle de eventos es equivalente a un bucle infinito.Podemos registrar algunas funciones en este bucle de eventos, y cuando se cumplan las condiciones de ocurrencia, se llamará al método de procesamiento correspondiente.
(2) coroutine: la traducción al chino se llama coroutine, que a menudo se refiere al tipo de objeto coroutine en Python.Podemos registrar el objeto coroutine en el bucle de tiempo, y será llamado por el comando event. Podemos usar la palabra clave async para definir un método que no se ejecutará inmediatamente cuando se le llame, pero devolverá un objeto coroutine.
(3) tarea: tarea, que es una encapsulación adicional del objeto de rutina, incluidos todos los estados del objeto de rutina.
(4) futuro: representa el resultado de una tarea que se ejecutará o no en el futuro, de hecho, no tiene una diferencia esencial con la tarea.

2.1.1 Definir rutinas

# -*- coding: utf-8 -*-
import asyncio
# async定义的方法会变成一个无法直接执行的协程对象,
# 必须将此对象注册到事件循环中才可以执行。
async def execute(x):
    print('Number:', x)
coroutine = execute(1)
# 此时直接调用async定义的方法,返回的只是一个协程对象
print('Coroutine:', coroutine)
print('after execute')
# 使用get_event_loop()方法创建一个事件循环loop,
# 并调用loop对象的run_until_complete方法将协程对象注册到了事件循环中,才会触发定义的方法。
loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine)
print('after loop')

inserte la descripción de la imagen aquí

2.1.2 Definir tareas

# -*- coding: utf-8 -*-
import asyncio
# (1)async定义的方法会变成一个无法直接执行的协程对象
async def execute(x):
    print('Number:', x)
    return x
coroutine = execute(555)

# (2)使用get_event_loop()方法创建一个事件循环loop
loop = asyncio.get_event_loop()

# (3)将协程对象转化为task任务,此时的任务还是pending状态
# (3-1)方式一
task = loop.create_task(coroutine)
# (3-2)方式二
# task = asyncio.ensure_future(coroutine)
print('Task:', task)

# (4)将task任务注册到事件循环中,然后task状态变为了finished,
# result=555是execute()执行的结果
loop.run_until_complete(task)
print('Task:', task)
print('after loop')

inserte la descripción de la imagen aquí

2.1.3 Corrutina multitarea

Para ejecutar varias solicitudes, puede definir una lista de tareas y luego usar el método de espera en el paquete asyncio para ejecutar.

# -*- coding: utf-8 -*-
# @Time : 2023/5/12 15:48
# @Author : zb
# @File : xieCheng.py
# @Project : 爬虫

import asyncio
# (1)async定义的方法会变成一个无法直接执行的协程对象
async def execute(x):
    print('Number:', x)
    return x

# (2)创建task任务列表
tasks = []
for i in range(3):
    tasks.append(asyncio.ensure_future(execute(i)))

# (3)使用get_event_loop()方法创建一个事件循环loop
loop = asyncio.get_event_loop()

# (4)将task任务注册到事件循环中
loop.run_until_complete(asyncio.wait(tasks))

for task in tasks:
    print('Task Result:', task.result())

2.2 marco HTTP aiohttp

aiohttp es un marco HTTP basado en asyncio para servidores y clientes HTTP.

# -*- coding: utf-8 -*-
import aiohttp
import asyncio
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'
}


async def Main():
    async with aiohttp.ClientSession() as session:
        async with session.get('https://www.baidu.com',headers=headers) as response:
            html = await response.text()
            print(html)
loop = asyncio.get_event_loop()
loop.run_until_complete(Main())

De hecho, aiohttp.ClientSession() como sesión es equivalente a asignar solicitudes a la sesión, es decir, la sesión es equivalente a las solicitudes, y el envío de solicitudes de red, los parámetros entrantes y el contenido de respuesta devuelto son similares a la biblioteca de solicitudes de solicitudes, pero la biblioteca de solicitudes aiohttp necesita usar Async y await se declaran, y luego llamar al método asyncio.get_event_loop() para ingresar al bucle de eventos, y luego llamar al método loop.run_until_complete(Main()) para ejecutar el bucle de eventos hasta que Main el método termina de ejecutarse.

3 Comparación de velocidad de descarga

Se utilizan, respectivamente, el rastreo de subproceso único, el rastreo de subprocesos múltiples y el rastreo de corrutina asíncrono.
La dirección de la novela es http://www.doupo321.com/doupocangqiong/.
Los pasos son primero rastrear los subenlaces debajo de la página web y luego rastrear el contenido de cada capítulo de la novela a través de los subenlaces.

3.1 Hilo único

# -*- coding: utf-8 -*-
import time
import requests
from lxml import etree


def download(url, title):  # 下载内容
    resp = requests.get(url)
    resp.encoding = 'utf-8'
    html = resp.text
    tree = etree.HTML(html)
    body = tree.xpath("/html/body/div/div/div[4]/p/text()")
    body = '\n'.join(body)
    with open(f'斗破2/{title}.txt', mode='a+', encoding='utf-8') as fw:
        fw.write(body)


def geturl(url):  # 获取子链接
    resp = requests.get(url)
    resp.encoding = 'utf-8'
    html = resp.text
    tree = etree.HTML(html)
    lis = tree.xpath("/html/body/div[1]/div[2]/div[1]/div[3]/div[2]/ul/li")
    print("总共{}章".format(len(lis)))
    for li in lis:
        href = li.xpath("./a/@href")[0].strip('//')
        href = "http://"+href
        title = li.xpath("./a/text()")[0]
        download(href, title)
        print(title)


if __name__ == '__main__':
    url1 = "http://www.doupo321.com/doupocangqiong/"
    st = time.time()
    geturl(url1)
    ed = time.time()
    print("耗时:", ed-st)

Tomó alrededor de 30 minutos.
inserte la descripción de la imagen aquí

3.2 Multiproceso

# -*- coding: utf-8 -*-
import time
import requests
from lxml import etree
from concurrent.futures import ThreadPoolExecutor


def download(url, title):
    resp = requests.get(url)
    resp.encoding = 'utf-8'
    html = resp.text
    tree = etree.HTML(html)
    body = tree.xpath("/html/body/div/div/div[4]/p/text()")
    body = '\n'.join(body)
    with open(f'斗破1/{title}.txt', mode='w', encoding='utf-8')as fw:
        fw.write(body)


def geturl(url):
    # 获取每一章的链接
    resp = requests.get(url)
    resp.encoding = 'utf-8'
    html = resp.text
    tree = etree.HTML(html)
    lis1 = tree.xpath("/html/body/div[1]/div[2]/div[1]/div[3]/div[2]/ul/li")
    print("总共{}章".format(len(lis1)))
    return lis1


if __name__ == '__main__':
    url1 = "http://www.doupo321.com/doupocangqiong/"
    st = time.time()
    lis = geturl(url1)
    with ThreadPoolExecutor(1000) as t:  # 创建线程池,最多有1000个线程
        for li in lis:
            href = li.xpath("./a/@href")[0].strip('//')
            href = "http://" + href
            title = li.xpath("./a/text()")[0]
            t.submit(download, url=href, title=title)
            print(title)
    ed = time.time()
    print("耗时:", ed-st)

Solo tomó 22 segundos.
inserte la descripción de la imagen aquí

3.3 Corrutinas asíncronas

Instalar biblioteca dependiente pip instalar aiohttp
instalar biblioteca dependiente pip instalar aiofiles

# -*- coding: utf-8 -*-
import requests
import aiohttp
import asyncio
import aiofiles
from lxml import etree
import time


async def download(url,title,session):
    async with session.get(url) as resp:  # resp = requests.get()
        html = await resp.text()
        tree = etree.HTML(html)
        body = tree.xpath("/html/body/div/div/div[4]/p/text()")
        body = '\n'.join(body)
    async with aiofiles.open(f'斗破/{title}.txt', mode='w', encoding='utf-8')as f:  # 保存下载内容
        await f.write(body)


async def geturl(url):
    resp = requests.get(url)
    resp.encoding = 'utf-8'
    html = resp.text
    tree = etree.HTML(html)
    lis = tree.xpath("/html/body/div[1]/div[2]/div[1]/div[3]/div[2]/ul/li")
    print("总共{}章".format(len(lis)))
    tasks = []
    async with aiohttp.ClientSession() as session:  # request
        for li in lis:
            href = li.xpath("./a/@href")[0].strip('//')
            href = "http://"+href
            title = li.xpath("./a/text()")[0]
            print(title)
            # 插入异步操作
            tasks.append(asyncio.ensure_future(download(href, title, session)))
        await asyncio.wait(tasks)

if __name__ == '__main__':
    url1 = "http://www.doupo321.com/doupocangqiong/"
    st = time.time()
    loop = asyncio.get_event_loop()
    loop.run_until_complete(geturl(url1))
    ed = time.time()
    print("耗时:", ed-st)

Tarda unos 32 segundos
inserte la descripción de la imagen aquí
. Se puede ver que con subprocesos múltiples, una novela con más de 1600 capítulos se puede terminar en solo 22 segundos, pero los subprocesos múltiples tendrán una gran sobrecarga en el sistema; si se utilizan corrutinas asíncronas, la velocidad de rastreo será ligeramente más lenta, tarda unos 32 segundos, pero la sobrecarga del sistema es relativamente pequeña. Se recomienda utilizar el método de corrutina asincrónica, pero será mucho más lento rastrear con un solo subproceso. Tarda 30 minutos para terminar una novela, que no es muy recomendable.

Las novelas que tengo están fuera de servicio, y encontraré la manera de resolverlas más tarde.

4 Números en mayúsculas se reemplazan con números arábigos

# -*- coding: utf-8 -*-
import re

pattern = re.compile(u'第(.*)章')

mulu = u'''第一千五百四十六章 道韵高低'''

# constants for chinese_to_arabic
# 中文对应阿拉伯数字
CN_NUM = {
    
    
    '〇': 0, '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '零': 0,
    '壹': 1, '贰': 2, '叁': 3, '肆': 4, '伍': 5, '陆': 6, '柒': 7, '捌': 8, '玖': 9, '貮': 2, '两': 2,
}
# 中文对应单位
CN_UNIT = {
    
    
    '十': 10,
    '拾': 10,
    '百': 100,
    '佰': 100,
    '千': 1000,
    '仟': 1000,
    '万': 10000,
    '萬': 10000,
    '亿': 100000000,
    '億': 100000000,
    '兆': 1000000000000,
}


def chinese_to_arabic(cn):
    print(cn)
    unit = 0  # current
    ldig = []  # digest
    for cndig in reversed(cn):
        print(cndig)
        if cndig in CN_UNIT:
            unit = CN_UNIT.get(cndig)
            if unit == 10000 or unit == 100000000:
                ldig.append(unit)
                unit = 1

        else:
            dig = CN_NUM.get(cndig)
            if unit:
                dig *= unit
                unit = 0
            ldig.append(dig)

    if unit == 10:
        ldig.append(10)
    val, tmp = 0, 0
    for x in reversed(ldig):
        if x == 10000 or x == 100000000:
            val += tmp * x
            tmp = 0
        else:
            tmp += x
    val += tmp
    return val


print(pattern.findall(mulu))

Supongo que te gusta

Origin blog.csdn.net/qq_20466211/article/details/130640325
Recomendado
Clasificación