web-dev-qa-db-fra.com

Comment puis-je gratter plus vite

Le travail ici consiste à supprimer une API d'un site qui commence à partir de https://xxx.xxx.xxx/xxx/1.json à https://xxx.xxx.xxx/xxx/1417749.json et l'écrire exactement sur mongodb. Pour cela j'ai le code suivant:

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()

Mais cela prend beaucoup de temps pour faire la tâche. La question ici est de savoir comment puis-je accélérer ce processus.

16
Tek Nath

asyncio est également une solution si vous ne souhaitez pas utiliser le multi-threading

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()
5
Frans

Vous pouvez faire plusieurs choses:

  1. Réutiliser la connexion. Selon l'indice de référence ci-dessous, il est environ 3 fois plus rapide
  2. Vous pouvez gratter dans plusieurs processus en parallèle

Code parallèle de ici

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)

Timings de cette question pour une connexion réutilisable

>>> 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
10
keiv.fly

Vous pouvez améliorer votre code sur deux aspects:

  • Utiliser un Session, afin qu'une connexion ne soit pas réorganisée à chaque demande et reste ouverte;

  • Utiliser le parallélisme dans votre code avec asyncio;

Jetez un œil ici https://pawelmhm.github.io/asyncio/python/aiohttp/2016/04/22/asyncio-aiohttp.html

6
albestro

Ce que vous recherchez probablement, c'est le grattage asynchrone. Je vous recommanderais de créer des lots d'URL, c'est-à-dire 5 URL (essayez de ne pas effacer le site Web), et de les gratter de manière asynchrone. Si vous ne savez pas grand-chose sur async, google pour l'asyncio libary. J'espère pouvoir vous aider :)

4
T Piper

Essayez de fragmenter les demandes et utilisez l'opération d'écriture en bloc MongoDB.

  • regrouper les demandes (100 demandes par groupe)
  • Itérer à travers les groupes
  • Utiliser un modèle de demande asynchrone pour récupérer les données (URL dans un groupe)
  • Mettre à jour la base de données après avoir terminé un groupe (opération d'écriture en bloc)

Cela peut vous faire gagner beaucoup de temps des manières suivantes * Latence d'écriture MongoDB * Latence des appels réseau synchrones

Mais n'augmentez pas le nombre de requêtes parallèles (taille de bloc), cela augmentera la charge réseau du serveur et le serveur pourrait penser cela comme une attaque DDoS.

  1. https://api.mongodb.com/python/current/examples/bulk.html
3
thuva4

En supposant que vous ne serez pas bloqué par l'API et qu'il n'y a pas de limite de débit, ce code devrait rendre le processus 50 fois plus rapide (peut-être plus car toutes les demandes sont désormais envoyées en utilisant la même session).

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)
3
Ibrahim Dar

J'ai eu la même question il y a de nombreuses années. Je ne suis jamais satisfait des réponses basées sur python, qui sont assez lentes ou trop compliquées. Après avoir basculé vers d'autres outils matures, la vitesse est rapide et je ne reviens jamais.

Récemment, j'utilise ces étapes pour accélérer le processus comme suit.

  1. générer un tas d'urls en txt
  2. utilisation aria2c -x16 -d ~/Downloads -i /path/to/urls.txt pour télécharger ces fichiers
  3. analyser localement

C'est le processus le plus rapide à ce jour.

En ce qui concerne le raclage des pages Web, je télécharge même le fichier * .html nécessaire, au lieu de visiter la page une fois à la fois, ce qui ne fait aucune différence. Lorsque vous appuyez sur visiter la page, avec python outils comme requests ou scrapy ou urllib, il cache toujours et télécharge tout le contenu Web pour vous.

2
anonymous

Créez d'abord une liste de tous les liens, car tous sont identiques, modifiez-la simplement.

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)

En augmentant ou en diminuant simplement t_no, vous pouvez modifier le nombre de threads.

1
mobin alhassan