Comment puis-je gratter plus vite


16

Le travail ici consiste à supprimer une API d'un site qui commence à partir https://xxx.xxx.xxx/xxx/1.jsonde https://xxx.xxx.xxx/xxx/1417749.jsonet à 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.


Avez-vous d'abord essayé de comparer le temps nécessaire pour traiter un seul json? En supposant que cela prenne 300 ms par enregistrement, vous pouvez traiter tous ces enregistrements de manière séquentielle en environ 5 jours.
tuxdna

Réponses:


5

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

1
L'utilisation d'Async a fonctionné plus rapidement que le multi-threading.
Tek Nath

Merci pour les commentaires. Résultat intéressant.
Frans

10

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 d' 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


4

Ce que vous recherchez probablement, c'est le grattage asynchrone. Je vous recommande de créer des lots d'URL, c'est-à-dire 5 URL (essayez de ne pas chrasher le site), 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 :)


1
Pouvez-vous ajouter plus de détails.
Tek Nath

3

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

1
Pouvez-vous aider avec le code pour regrouper les demandes et l'extraction de groupe
Tek Nath

3

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)

2

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'URL en txt
  2. utiliser aria2c -x16 -d ~/Downloads -i /path/to/urls.txtpour 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 cliquez sur visiter la page, avec des outils python comme requestsou scrapyou urllib, il met toujours en cache et télécharge tout le contenu Web pour vous.


1

Créez d'abord une liste de tous les liens, car tous sont identiques, il suffit de le modifier.

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.