ฉันจะขูดเร็วขึ้นได้อย่างไร


16

การทำงานที่นี่คือการขูด API ที่เว็บไซต์ที่เริ่มต้นจากการ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 เดี่ยว? สมมติว่าใช้เวลา 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
การใช้ 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 (พยายามไม่ chrash เว็บไซต์) และขูดพวกมันแบบอะซิงโครนัส หากคุณไม่รู้เกี่ยวกับ async มากนัก google สำหรับ asyncio ของ libary ฉันหวังว่าฉันจะช่วยคุณ :)


1
คุณช่วยเพิ่มรายละเอียดเพิ่มเติมได้ไหม
Tek Nath

3

ลองแยกการร้องขอและใช้การดำเนินการเขียนเป็นกลุ่ม MongoDB

  • จัดกลุ่มคำขอ (100 คำขอต่อกลุ่ม)
  • วนซ้ำผ่านกลุ่มต่างๆ
  • ใช้โมเดลคำขอแบบอะซิงโครนัสเพื่อดึงข้อมูล (URL ในกลุ่ม)
  • อัพเดตฐานข้อมูลหลังจากเสร็จสิ้นกลุ่ม (การดำเนินการเขียนจำนวนมาก)

วิธีนี้อาจช่วยประหยัดเวลาได้มากในวิธีต่อไปนี้ * MongoDB write latency * เวลาแฝงการโทรเครือข่ายแบบซิงโครนัส

แต่อย่าเพิ่มจำนวนการร้องขอแบบขนาน (ขนาด Chunk) มันจะเพิ่มโหลดเครือข่ายของเซิร์ฟเวอร์และเซิร์ฟเวอร์อาจคิดว่านี่เป็นการโจมตี DDoS

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

1
คุณสามารถช่วยด้วยรหัสสำหรับการจัดกลุ่มการร้องขอและการดึงกลุ่ม
Tek Nath

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. สร้าง 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 คุณก็สามารถเปลี่ยนจำนวนกระทู้ได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.