Як можна швидше вискоблювати


16

Робота тут , щоб очистити АНІ сайт , який починається з https://xxx.xxx.xxx/xxx/1.jsonдо https://xxx.xxx.xxx/xxx/1417749.jsonі записати його точно MongoDB. Для цього у мене є такий код:

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

Але для виконання цього завдання потрібно багато часу. Тут питання, як я можу прискорити цей процес.


Ви вперше спробували порівняти, скільки часу потрібно для обробки одного json? Якщо припустимо, що це займає 300 мс на запис, ви можете обробляти всі ці записи послідовно протягом приблизно 5 днів.
tuxdna

Відповіді:


5

asyncio також є рішенням, якщо ви не хочете використовувати багатопоточну нитку

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 працювало швидше, ніж багатопоточна.
Tek Nath

Дякуємо за відгук. Цікавий результат.
Франс

10

Можна зробити кілька речей:

  1. Повторне використання з'єднання. Згідно з нижчим показником, це приблизно в 3 рази швидше
  2. Ви можете вискоблювати паралельно декілька процесів

Паралельний код звідси

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)

Терміни з цього питання для багаторазового підключення

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

6

Ви можете покращити свій код з двох аспектів:

  • Використовуючи a Session, щоб з'єднання не було впорядковано під час кожного запиту і залишалося відкритим;

  • Використання паралелізму у коді з asyncio;

Подивіться тут https://pawelmhm.github.io/asyncio/python/aiohttp/2016/04/22/asyncio-aiohttp.html


2
Чи можете ви додати ще детальну інформацію?
Tek Nath

4

Напевно, ти шукаєш асинхронний вискоблювання. Я рекомендую вам створити кілька пакетів URL-адрес, тобто 5 URL-адрес (намагайтеся не руйнувати веб-сайт), і скребте їх асинхронно. Якщо ви багато чого не знаєте про асинхронізацію, google для асинціо лібарії. Сподіваюся, я міг би вам допомогти :)


1
Чи можете ви додати ще детальну інформацію.
Tek Nath

3

Спробуйте скинути запити і скористатися операцією запису об'ємного запису MongoDB.

  • групувати запити (100 запитів на групу)
  • Ітерація через групи
  • Використовуйте асинхронну модель запиту для отримання даних (URL у групі)
  • Оновіть БД після завершення групи (операція масового запису)

Це може заощадити багато часу такими способами * затримка запису MongoDB * затримка синхронного виклику в мережі

Але не збільшуйте кількість паралельних запитів (розмір Chunk), це збільшить завантаженість мережі сервера і сервер може вважати це атакою DDoS.

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

1
Чи можете ви допомогти з кодом для групування запитів та отримання групу
Tek Nath

3

Якщо припустити, що API не буде заблоковано API і немає обмежень швидкості, цей код повинен зробити процес в 50 разів швидшим (можливо, більше, тому що всі запити зараз надсилаються за допомогою одного і того ж сеансу).

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

У мене виникло те саме питання багато років тому. Я ніколи не задоволений відповідями на основі пітона, які є досить повільними або занадто складними. Після переходу на інші зрілі інструменти швидкість швидка, і я ніколи не повертаюся.

Останнім часом я використовую такі кроки, щоб прискорити процес наступним чином.

  1. генерувати купу URL-адрес у txt
  2. використовувати aria2c -x16 -d ~/Downloads -i /path/to/urls.txtдля завантаження цих файлів
  3. розбирати локально

Це найшвидший процес, який я придумав поки що.

Що стосується скребкування веб-сторінок, я навіть завантажую необхідний * .html, замість того, щоб відвідувати сторінку раз за раз, що насправді не має ніякого значення. Коли ви натиснете на сторінку, з пітона інструментами , такими як requestsабо scrapyчи urllib, це ще кешем і завантажити весь веб - контент для вас.


1

Спочатку створіть список усіх посилань, оскільки всі однакові, просто змініть його ітерайте.

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)

Просто збільшуючи або зменшуючи t_no, ви можете змінити відсутність потоків ..

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.