¿Cómo puedo raspar más rápido?

16

El trabajo aquí es para raspar una API un sitio que se inicia a partir https://xxx.xxx.xxx/xxx/1.jsonde https://xxx.xxx.xxx/xxx/1417749.jsony escribir exactamente a mongodb. Para eso tengo el siguiente código:

client = pymongo.MongoClient("mongodb://127.0.0.1:27017")
db = client["thread1"]
com = db["threadcol"]
start_time = time.time()
write_log = open("logging.log", "a")
min = 1
max = 1417749
for n in range(min, max):
    response = requests.get("https:/xx.xxx.xxx/{}.json".format(str(n)))
    if response.status_code == 200:
        parsed = json.loads(response.text)
        inserted = com.insert_one(parsed)
        write_log.write(str(n) + "\t" + str(inserted) + "\n")
        print(str(n) + "\t" + str(inserted) + "\n")
write_log.close()

Pero lleva mucho tiempo hacer la tarea. La pregunta aquí es cómo puedo acelerar este proceso.

Tek Nath
fuente
¿Primero trataste de comparar cuánto tiempo lleva procesar un solo json? Suponiendo que se requieren 300 ms por registro, puede procesar todos estos registros secuencialmente en aproximadamente 5 días.
tuxdna

Respuestas:

5

Asyncio también es una solución si no desea utilizar subprocesos múltiples

import time
import pymongo
import json
import asyncio
from aiohttp import ClientSession


async def get_url(url, session):
    async with session.get(url) as response:
        if response.status == 200:
            return await response.text()


async def create_task(sem, url, session):
    async with sem:
        response = await get_url(url, session)
        if response:
            parsed = json.loads(response)
            n = url.rsplit('/', 1)[1]
            inserted = com.insert_one(parsed)
            write_log.write(str(n) + "\t" + str(inserted) + "\n")
            print(str(n) + "\t" + str(inserted) + "\n")


async def run(minimum, maximum):
    url = 'https:/xx.xxx.xxx/{}.json'
    tasks = []
    sem = asyncio.Semaphore(1000)   # Maximize the concurrent sessions to 1000, stay below the max open sockets allowed
    async with ClientSession() as session:
        for n in range(minimum, maximum):
            task = asyncio.ensure_future(create_task(sem, url.format(n), session))
            tasks.append(task)
        responses = asyncio.gather(*tasks)
        await responses


client = pymongo.MongoClient("mongodb://127.0.0.1:27017")
db = client["thread1"]
com = db["threadcol"]
start_time = time.time()
write_log = open("logging.log", "a")
min_item = 1
max_item = 100

loop = asyncio.get_event_loop()
future = asyncio.ensure_future(run(min_item, max_item))
loop.run_until_complete(future)
write_log.close()
Frans
fuente
1
Usar async funcionó más rápido que multi threading.
Tek Nath
Gracias por la respuesta. Resultado interesante
Frans
10

Hay varias cosas que puedes hacer:

  1. Reutilice la conexión. Según el punto de referencia a continuación, es aproximadamente 3 veces más rápido
  2. Puede raspar en múltiples procesos en paralelo

Código paralelo desde aquí

from threading import Thread
from Queue import Queue
q = Queue(concurrent * 2)
for i in range(concurrent):
    t = Thread(target=doWork)
    t.daemon = True
    t.start()
try:
    for url in open('urllist.txt'):
        q.put(url.strip())
    q.join()
except KeyboardInterrupt:
    sys.exit(1)

Tiempos de esta pregunta para conexión reutilizable

>>> timeit.timeit('_ = requests.get("https://www.wikipedia.org")', 'import requests', number=100)
Starting new HTTPS connection (1): www.wikipedia.org
Starting new HTTPS connection (1): www.wikipedia.org
Starting new HTTPS connection (1): www.wikipedia.org
...
Starting new HTTPS connection (1): www.wikipedia.org
Starting new HTTPS connection (1): www.wikipedia.org
Starting new HTTPS connection (1): www.wikipedia.org
52.74904417991638
>>> timeit.timeit('_ = session.get("https://www.wikipedia.org")', 'import requests; session = requests.Session()', number=100)
Starting new HTTPS connection (1): www.wikipedia.org
15.770191192626953
keiv.fly
fuente
4

Lo que probablemente esté buscando es un raspado asincrónico. Le recomendaría que cree algunos lotes de URL, es decir, 5 URL (trate de no bloquear el sitio web) y que las elimine de forma asíncrona. Si no sabe mucho sobre async, busque google for the libary asyncio. Espero que te pueda ayudar :)

T Piper
fuente
1
¿Puedes agregar algunos detalles más?
Tek Nath
3

Intente fragmentar las solicitudes y utilice la operación de escritura masiva MongoDB.

  • agrupar las solicitudes (100 solicitudes por grupo)
  • Iterar a través de los grupos
  • Utilice el modelo de solicitud asíncrono para obtener los datos (URL en un grupo)
  • Actualice la base de datos después de completar un grupo (operación de escritura masiva)

Esto podría ahorrar mucho tiempo de las siguientes maneras: * latencia de escritura MongoDB * latencia de llamadas de red síncrona

Pero no aumente el recuento de solicitudes paralelas (tamaño de fragmento), aumentará la carga de red del servidor y el servidor podría pensar que esto es un ataque DDoS.

  1. https://api.mongodb.com/python/current/examples/bulk.html
thuva4
fuente
1
¿Puede ayudarme con el código para agrupar las solicitudes y la búsqueda de grupo?
Tek Nath
3

Suponiendo que la API no lo bloqueará y que no hay límites de velocidad, este código debería hacer que el proceso sea 50 veces más rápido (tal vez más porque todas las solicitudes se envían ahora usando la misma sesión).

import pymongo
import threading

client = pymongo.MongoClient("mongodb://127.0.0.1:27017")
db = client["thread1"]
com = db["threadcol"]
start_time = time.time()
logs=[]

number_of_json_objects=1417750
number_of_threads=50

session=requests.session()

def scrap_write_log(session,start,end):
    for n in range(start, end):
        response = session.get("https:/xx.xxx.xxx/{}.json".format(n))
        if response.status_code == 200:
            try:
                logs.append(str(n) + "\t" + str(com.insert_one(json.loads(response.text))) + "\n")
                print(str(n) + "\t" + str(inserted) + "\n")
            except:
                logs.append(str(n) + "\t" + "Failed to insert" + "\n")
                print(str(n) + "\t" + "Failed to insert" + "\n")

thread_ranges=[[x,x+number_of_json_objects//number_of_threads] for x in range(0,number_of_json_objects,number_of_json_objects//number_of_threads)]

threads=[threading.Thread(target=scrap_write_log, args=(session,start_and_end[0],start_and_end[1])) for start_and_end in thread_ranges]

for thread in threads:
    thread.start()
for thread in threads:
    thread.join()

with open("logging.log", "a") as f:
    for line in logs:
        f.write(line)
Ibrahim Dar
fuente
2

Tuve la misma pregunta hace muchos años. Nunca estoy satisfecho con las respuestas basadas en Python, que son bastante lentas o demasiado complicadas. Después de cambiar a otras herramientas maduras, la velocidad es rápida y nunca vuelvo.

Recientemente utilizo estos pasos para acelerar el proceso de la siguiente manera.

  1. generar un montón de URL en txt
  2. utilizar aria2c -x16 -d ~/Downloads -i /path/to/urls.txtpara descargar estos archivos
  3. analizar localmente

Este es el proceso más rápido que encontré hasta ahora.

En términos de raspar páginas web, incluso descargo el * .html necesario, en lugar de visitar la página de vez en cuando, lo que en realidad no hace ninguna diferencia. Cuando visitas la página, con herramientas de Python como requestso scrapyo urllib, todavía almacena en caché y descarga todo el contenido web para ti.

anónimo
fuente
1

Primero crea una lista de todos los enlaces porque todos son iguales solo cambia iterala.

list_of_links=[]
for i in range(1,1417749):
    list_of_links.append("https:/xx.xxx.xxx/{}.json".format(str(i)))

t_no=2
for i in range(0, len(list_of_links), t_no):
    all_t = []
    twenty_links = list_of_links[i:i + t_no]
    for link in twenty_links:
        obj_new = Demo(link,)
        t = threading.Thread(target=obj_new.get_json)
        t.start()
        all_t.append(t)
    for t in all_t:
        t.join()

class Demo:
    def __init__(self, url):
        self.json_url = url

def get_json(self):
    try:
       your logic
    except Exception as e:
       print(e)

Simplemente aumentando o disminuyendo t_no puede cambiar no de hilos.

mobin alhassan
fuente