Ускорение парсера на питоне

Рейтинг: 1Ответов: 1Опубликовано: 16.04.2023

Я решил переделать асинхронный парсер с нуля. Убрал всё что мешало, а именно bs4 и заменил его на lxml(Xpath). Работа моего парсера заключается в сборе данных с сайта animedia, но пока для теста я беру лишь заголовки h1. В чём проблема. Проблема в скорости этого парсера если мы возьмём 1 страницу он обработает её за 1 секунду, если возьмём 25 он начнёт тормозить и это при 25 страницах, а их 103! Помогите разобраться как ускорить парсер.

import asyncio
import aiohttp
import time
from lxml import html

async def download(url):
   async with asyncio.Semaphore(3):#Более удачный 3
       async with aiohttp.ClientSession() as session:
           async with session.get(url) as r:
               try:
                   tree = html.fromstring(await r.text())
                   title = tree.xpath("//article[@class='film-wr']/h1/text()")
                   
                   print(title)
               except Exception:
                   pass

async def getpage(url):
   async with asyncio.Semaphore(2): #Более удачный 2 
       async with aiohttp.ClientSession() as session:
           async with session.get(url) as r:
               tasks = []
               tree = html.fromstring(await r.text())
               d = tree.xpath("//div[@class='c1-item']/a/@href")
               for link in d:
                   if link.startswith("/"):
                       link = "https://amedia.online" + link
                   #print(link)
                   task = asyncio.create_task(download(link))
                   tasks.append(task)
               await asyncio.gather(*tasks)
                       
                   

async def main():
   t0 = time.monotonic()
   tasks = []
   for i in range(1,25):
       url = "https://amedia.online/anime/page/"+str(i)
       task = asyncio.create_task(getpage(url))
       tasks.append(task)
   await asyncio.gather(*tasks)
   print("Время: "+str(time.monotonic() - t0))


if __name__ == "__main__":
   asyncio.run(main())

Ответы

▲ 2

Есть несколько мест, которые снижают производительность:

  • В вашем коде вы создаете новый ClientSession для каждой страницы. Это может быть накладно, особенно если вы обрабатываете много страниц. Лучше создать один ClientSession и повторно использовать его для каждого запроса.

  • Если число страниц больше, чем количество запросов, которые можно выполнить одновременно, вы будете тратить время на ожидание выполнения запросов, что может снижать производительность. Вам может помочь увеличение количества семафоров и уменьшение количества страниц, обрабатываемых каждым заданием.

  • Использование asyncio.gather() может привести к накоплению большого количества объектов в памяти, особенно если вы обрабатываете много страниц. Лучше использовать итераторы async for, чтобы избежать накопления объектов в памяти.

Вот пример, как это можно сделать:

import asyncio
import aiohttp
import time
from lxml import html

async def download(session, url):
    async with session.get(url) as r:
        try:
            tree = html.fromstring(await r.text())
            title = tree.xpath("//article[@class='film-wr']/h1/text()")
            print(title)
        except Exception:
            pass

async def getpage(session, url):
    async with session.get(url) as r:
        tree = html.fromstring(await r.text())
        d = tree.xpath("//div[@class='c1-item']/a/@href")
        tasks = []
        for link in d:
            if link.startswith("/"):
                link = "https://amedia.online" + link
            task = asyncio.create_task(download(session, link))
            tasks.append(task)
        await asyncio.gather(*tasks)

async def main():
    t0 = time.monotonic()
    async with aiohttp.ClientSession() as session:
        tasks = []
        for i in range(1, 25):
            url = f"https://amedia.online/anime/page/{i}"
            tasks.append(asyncio.create_task(getpage(session, url)))
        await asyncio.gather(*tasks)
    print("Время: " + str(time.monotonic() - t0))

if __name__ == "__main__":
    asyncio.run(main())