Bagaimana saya bisa mengikis lebih cepat


16

Pekerjaan di sini adalah untuk mengikis API situs yang dimulai dari https://xxx.xxx.xxx/xxx/1.jsonke https://xxx.xxx.xxx/xxx/1417749.jsondan menulisnya persis ke mongodb. Untuk itu saya punya kode berikut:

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

Tetapi butuh banyak waktu untuk melakukan tugas itu. Pertanyaan di sini adalah bagaimana saya bisa mempercepat proses ini.


Apakah Anda pertama kali mencoba melakukan benchmark berapa lama untuk memproses json tunggal? Dengan asumsi dibutuhkan 300 ms per record, Anda dapat memproses semua record ini secara berurutan dalam waktu sekitar 5 hari.
tuxdna

Jawaban:


5

asyncio juga merupakan solusi jika Anda tidak ingin menggunakan 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
Menggunakan async bekerja lebih cepat daripada multi threading.
Tek Nath

Terima kasih untuk umpan baliknya. Hasil yang menarik.
Frans

10

Ada beberapa hal yang bisa Anda lakukan:

  1. Gunakan kembali koneksi. Menurut patokan di bawah ini sekitar 3 kali lebih cepat
  2. Anda dapat mengikis dalam banyak proses secara paralel

Kode paralel dari sini

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)

Pengaturan waktu dari pertanyaan ini untuk koneksi yang dapat digunakan kembali

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

Apa yang mungkin Anda cari adalah goresan asinkron. Saya akan merekomendasikan Anda untuk membuat beberapa kumpulan url, yaitu 5 url (cobalah untuk tidak chrash situs web), dan mengikisnya asinkron. Jika Anda tidak tahu banyak tentang async, google for the libary asyncio. Saya berharap bisa membantumu :)


1
Bisakah Anda menambahkan lebih banyak detail?
Tek Nath

3

Cobalah untuk memotong permintaan dan menggunakan operasi penulisan massal MongoDB.

  • kelompokkan permintaan (100 permintaan per kelompok)
  • Iterasi melalui kelompok
  • Gunakan model permintaan tidak sinkron untuk mengambil data (URL dalam grup)
  • Perbarui DB setelah menyelesaikan grup (Operasi penulisan massal)

Ini mungkin menghemat banyak waktu dengan cara berikut * MongoDB menulis latensi * latensi panggilan jaringan sinkron

Tetapi jangan meningkatkan jumlah permintaan paralel (ukuran Chunk), Ini akan meningkatkan beban jaringan server dan server mungkin berpikir ini sebagai serangan DDoS.

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

1
Dapatkah Anda membantu dengan kode untuk mengelompokkan permintaan dan pengambilan grup
Tek Nath

3

Dengan asumsi bahwa Anda tidak akan diblokir oleh API dan tidak ada batasan nilai, kode ini harus membuat proses 50 kali lebih cepat (mungkin lebih karena semua permintaan sekarang dikirim menggunakan sesi yang sama).

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

Saya kebetulan memiliki pertanyaan yang sama bertahun-tahun yang lalu. Saya tidak pernah puas dengan jawaban berbasis python, yang cukup lambat atau terlalu rumit. Setelah saya beralih ke alat dewasa lainnya, kecepatannya cepat dan saya tidak pernah kembali.

Baru-baru ini saya menggunakan langkah-langkah tersebut untuk mempercepat proses sebagai berikut.

  1. menghasilkan banyak url di txt
  2. menggunakan aria2c -x16 -d ~/Downloads -i /path/to/urls.txt untuk mengunduh file-file ini
  3. parsing secara lokal

Ini adalah proses tercepat yang saya lakukan sejauh ini.

Dalam hal menggores halaman web, saya bahkan mengunduh * .html yang diperlukan, alih-alih mengunjungi halaman itu satu per satu, yang sebenarnya tidak membuat perbedaan. Ketika Anda menekan mengunjungi halaman, dengan alat python seperti requestsatau scrapyatau urllib, itu masih menyimpan dan mengunduh seluruh konten web untuk Anda.


1

Pertama buat daftar semua tautan karena semuanya sama saja ubah iterate it.

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)

Dengan hanya menambah atau mengurangi t_no Anda tidak dapat mengubah utas ..

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.