Nasıl daha hızlı kazımak


16

Burada iş bir API itibaren başlar o bir site kazımak için olduğu https://xxx.xxx.xxx/xxx/1.jsonkadar https://xxx.xxx.xxx/xxx/1417749.jsonve MongoDB tam olarak yazın. Bunun için aşağıdaki kod var:

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

Ancak görevi yapmak çok zaman alıyor. Burada soru bu süreci nasıl hızlandırabilirim.


Önce tek json'u işlemenin ne kadar sürdüğünü karşılaştırmaya çalıştınız mı? Kayıt başına 300 ms sürdüğü varsayılarak, tüm bu kayıtları yaklaşık 5 gün içinde sırayla işleyebilirsiniz.
tuxdna

Yanıtlar:


5

asyncio, çoklu iş parçacığı kullanmak istemiyorsanız da bir çözümdür

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
Async kullanımı çoklu iş parçacığından daha hızlı çalıştı.
Tek Nath

Geri dönüşünüz için teşekkür ederiz. İlginç sonuç.
Frans

10

Yapabileceğiniz birkaç şey var:

  1. Bağlantıyı yeniden kullanın. Aşağıdaki karşılaştırmaya göre yaklaşık 3 kat daha hızlı
  2. Paralel olarak birden çok işlemde kazıma yapabilirsiniz

Buradan paralel kod

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)

Yeniden kullanılabilir bağlantı için bu sorudaki zamanlamalar

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

Muhtemelen aradığınız asenkron kazımadır. Bazı URL'leri, yani 5 URL'yi oluşturmanızı öneririm (web sitesini bozmamaya çalışın) ve bunları eşzamansız olarak kazıyın. Async hakkında fazla bilginiz yoksa, libary asyncio için google. Umarım sana yardım edebilirim :)


1
Daha fazla ayrıntı ekleyebilir misiniz?
Tek Nath

3

İstekleri yığınlamaya çalışın ve MongoDB toplu yazma işlemini kullanın.

  • istekleri gruplama (grup başına 100 istek)
  • Gruplar arasında yineleme
  • Verileri almak için eşzamansız istek modelini kullanma (gruptaki URL)
  • Bir grubu tamamladıktan sonra DB'yi güncelleyin (Toplu yazma işlemi)

Bu, aşağıdaki şekillerde çok zaman kazandırabilir * MongoDB yazma gecikmesi * senkron ağ araması gecikmesi

Ancak paralel istek sayısını (Chunk boyutu) artırmayın, Sunucunun ağ yükünü artıracaktır ve sunucu bunu bir DDoS saldırısı olarak düşünebilir.

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

1
İstekleri gruplama ve grup getirme koduyla yardımcı olabilir misiniz
Tek Nath

3

API tarafından engellenmeyeceğinizi ve ücret sınırlaması olmadığını varsayarsak, bu kod işlemi 50 kat daha hızlı yapmalıdır (belki de daha fazlası, tüm isteklerin aynı oturum kullanılarak gönderilmesi nedeniyle).

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

Yıllar önce aynı soruyu sormuştum. Oldukça yavaş veya çok karmaşık olan python tabanlı cevaplardan asla memnun kalmadım. Diğer olgun araçlara geçtikten sonra hız hızlı ve asla geri dönmem.

Son zamanlarda süreci aşağıdaki gibi hızlandırmak için bu adımları kullanıyorum.

  1. txt içinde bir grup url oluşturmak
  2. aria2c -x16 -d ~/Downloads -i /path/to/urls.txtbu dosyaları indirmek için kullan
  3. yerel olarak ayrıştır

Bu şimdiye kadar bulduğum en hızlı süreç.

Web sayfalarını kazıma açısından, her seferinde sayfayı bir kez ziyaret etmek yerine gerekli * .html dosyasını indiriyorum, ki bu aslında hiçbir fark yaratmıyor. Sayfayı ziyaret ettiğinizde requestsveya scrapyveya python araçlarıyla sayfayı ziyaret ettiğinizde urllib, tüm web içeriğini önbelleğe alır ve sizin için indirir.


1

Öncelikle tüm bağlantıların listesini oluşturun, çünkü hepsi aynıdır, sadece tekrarlayın.

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)

Sadece t_no değerini arttırarak veya azaltarak ipliklerin sayısını değiştirebilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.