더 빨리 긁을 수있는 방법


16

여기 작품은 API를에서 시작하는 사이트 긁어하는 것입니다 https://xxx.xxx.xxx/xxx/1.jsonhttps://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을 처리하는 데 걸리는 시간을 먼저 벤치마킹하려고 했습니까? 레코드 당 300ms가 걸린다고 가정하면 약 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
비동기 사용은 멀티 스레딩보다 빠르게 작동했습니다.
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


4

아마 당신이 찾고있는 것은 비동기 스크래핑입니다. 5 개의 URL (웹 사이트에 충돌하지 않도록 시도)과 같은 URL 배치를 생성하고 비동기 적으로 스크랩하는 것이 좋습니다. 비동기에 대해 많이 모른다면 라이브러리 asyncio에 대해 google. 나는 당신을 도울 수 있기를 바랍니다 :)


1
더 자세한 내용을 추가 할 수 있습니까?
Tek Nath

3

요청을 청크하고 MongoDB 대량 쓰기 작업을 사용하십시오.

  • 요청 그룹화 (그룹당 100 개의 요청)
  • 그룹을 반복
  • 비동기 요청 모델을 사용하여 데이터 가져 오기 (그룹의 URL)
  • 그룹 완료 후 DB 업데이트 (대량 쓰기 작업)

이렇게하면 다음과 같은 방법으로 많은 시간을 절약 할 수 있습니다. * MongoDB 쓰기 대기 시간 * 동기식 네트워크 호출 대기 시간

그러나 병렬 요청 수 (청크 크기)를 늘리지 마십시오. 서버의 네트워크로드가 증가하고 서버가이를 DDoS 공격으로 생각할 수 있습니다.

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

1
당신은 페치 요청과 그룹을 그룹화하는 코드를 도울 수
테크 나스를

3

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. txt로 많은 URL을 생성하십시오.
  2. aria2c -x16 -d ~/Downloads -i /path/to/urls.txt이 파일을 다운로드 하는 데 사용
  3. 로컬로 파싱

이것은 내가 지금까지 만든 가장 빠른 프로세스입니다.

웹 페이지 스크랩과 관련하여 한 번에 한 번에 한 페이지를 방문하는 대신 필요한 * .html을 다운로드하기도합니다. 실제로 차이는 없습니다. requestsor scrapy또는 과 같은 Python 도구를 사용하여 페이지를 방문하면 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를 늘리거나 줄이면 스레드를 변경할 수 없습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.